The NOFS / DNNI Voice Database Format A verified reference for Synthesizer V AI voicebanks · version 7

Abstract. voice.nofs is the voice database read by Synthesizer V Studio. This document describes its container, the DNNI record layer nested inside it, the record tree that layer encodes, the int16 quantisation used for weight tensors, the phoneme dictionaries, the configuration blob, and the small set of per-voice arrays that decide which singer you hear.

A voicebank is not a model of one singer. It is a shared backbone — byte-identical across banks of the same generation — steered by roughly 1,500 bytes of per-voice data. Of that, three 32-float vectors (396 bytes) carry vocal identity.

Everything here was derived by parsing two shipping banks from first principles and diffing them record by record: GUMI AI v101 (INTERNET Co., Ltd., 40,536,086 B) and Kasane Teto v104 (AHS Co., Ltd., 40,536,421 B). Two files is a narrow base, so any claim that is really a population statistic is marked as such. The manual also incorporates the earliest known description of the format, by ATroubledSnake; section 20 reconciles the two, line by line.

What changed in version 7

Version 7 is a correction pass over version 6, done by re-deriving every number in the document from the two banks with a parser written independently of the one that produced version 6. Most of version 6 survived intact. Eleven claims did not, and one whole section — the configuration blob — was wrong in structure and is now fully decoded. Section 19 lists every change with the evidence. The figure system was also rebuilt: see section 4 for the naming rules, which existed only implicitly before and were applied inconsistently.

How to read this document

Claims carry a grade. [V] means verified here against both banks, byte by byte. [I] is an inference from structure that is consistent but not proven. [U] is unknown — the field is described but its meaning is not established. [V*] was established by rendering audio in earlier work on this format and has not been re-tested here. [S] comes from ATroubledSnake's original document and could not be checked with the two files available.

Every zoom chain starts from the same picture of the whole file, with the byte range about to be magnified outlined in red and dashed lines carrying it down to the next level. Every level tiles its span exactly — the figure generator refuses to emit a level whose boxes leave a gap — so a run of records that is not drawn individually appears as a dashed box that says how many records and how many bytes it stands for. Offsets are given twice: the grey row is the absolute file offset, the blue row is the offset relative to the enclosing structure.

Contents

1.Where the file comes from
2.The file at a glance
3.Names, sizes, and how to read the figures
4.The container header
5.The property chain
6.Sections
7.The DNNI record
8.The record stream is a tree
9.Quantised tensors
10.Float32 records
11.Per-voice data — the singer
12.Phoneme dictionaries
13.Network architecture
14.The configuration blob
15.Backbone generations and model digests
16.The trailer blobs
17.Behaviour under modification
18.Reconciliation with the original documentation
19.Corrections to version 6
20.Verification
21.Open questions
22.Experiments that would settle the remaining questions
23.Reference implementation
24.Scope, and credit
25.Tag glossary
26.Quick reference

1. Where the file comes from

[S] A voicebank ships as a .svpk file, which is an ordinary ZIP archive. Inside it are voice.nofs and a handful of assets: profile.png, background.png, license.txt and package-config. Everything in this document concerns voice.nofs alone. The two files studied here were supplied already extracted, so the archive layout is quoted from the original documentation rather than re-checked.

[S] Not every voice.nofs is a neural voice. The pre-AI parametric banks use the same magic number and the same 0x1000-byte reserved head, but hold formant parameter arrays and encoded audio instead of a record stream. The reliable test is to look for a DNNI marker: if 0x7FCA40FF or 0x7FCA41FF appears, the bank is neural. This document describes only the neural layout.

2. The file at a glance

Drawn from GUMI AI v101. Kasane Teto v104 differs by 335 bytes in total and the structure is identical; section 19.4 breaks that difference down. Bands are cut on chain-record boundaries, so they tile the file exactly and each printed size is that band's true size.

LEVEL 0 - THE WHOLE FILE, 40,536,086 B, 22 CHAIN RECORDS the four sections are to scale; the three metadata bands are widened to fit their labels head 4,560 B section 0 - f0 8,111,519 B config 1,947 B section 1 - timing 4,328,069 B section 2 - acoustic 22,252,857 B section 3 - vocoder 5,836,582 B trailer 552 B 0x0000000 0x00011D0 0x07BD76F 0x07BDF0A 0x0BDE98F 0x21176C8 0x26A85EE 0x26A8816

Figure 1 The whole file. Every band is a whole number of chain records, so the bands tile the file exactly and each printed size is that band's true size. The three metadata bands hold 7,059 bytes between them. Section 11 marks the 1,500 bytes of this picture that belong to the singer.

container / framing
no payload; a marker and a count
structural node
size 0; carries only a child count
layer descriptor
five u32 words: kernel, stride, padding, dilation, groups
string table
count, then length-prefixed strings
quantised tensor
float32 scales, then an int16 matrix
per-voice array
count, then 32 or 128 float32
elided run
records not drawn individually; the box says how many

2.1 Where the bytes go

contentbytesshare
int16 weight tensors39,204,80096.716%
float32 per-row scales430,8721.063%
other record payloads: float32 arrays, shape words, phoneme tables866,4012.137%
DNNI record headers (1338 x 20 B)26,7600.066%
reserved head: fixed fields, hash index, padding4,0960.010%
gap between data_offset and the chain16<0.001%
property-chain framing (22 entries)6390.002%
configuration blob1,7970.004%
opaque trailer blobs (2 x 256 B)5120.001%
metadata and configuration strings161<0.001%
section container headers (4 x 8 B)32<0.001%
total40,536,086100%

[V] These rows sum to 40,536,086 exactly, with no remainder. Version 6 printed this table without the 16-byte gap row and therefore fell 16 bytes short while claiming to be exact; see section 19.2.

3. Names, sizes, and how to read the figures

Four different things in this format are all loosely called “a section” or “a header” in casual description, and they have four different sizes. Version 6 used the words inconsistently: its level-0 map called the first band header at 4,560 bytes while its section 3 called the reserved head the header at 4,096 bytes, and its band labelled config blob, 1,947 B sat above a section describing a configuration blob of 1,797 bytes. Neither number was wrong; they were different things wearing the same name. This section fixes the vocabulary, and the rest of the manual holds to it.

termwhat it iswhere it startssize for section 2
chain recordone framed key/value unit of the property chain: len, type, keylen, key, vallen, value, len0x0BDE98F22,252,857 B
sectionthe value of a chain record whose value begins with 0x7FCA00FF0x0BDE9B822,252,812 B
DNNI recorda 20-byte header plus its payload, inside a section. Also called a whole record0x1013F68 (record 117)66,604 B
payloadthe bytes after a DNNI record header0x1013F7C66,584 B
LEVEL 0 - THE CHAIN RECORD THAT CARRIES SECTION 2 chain record 22,252,857 B, section value 22,252,812 B - a 45-byte difference framing len, type, keylen, key, vallen value = section 2 22,252,812 B len2 0x0BDE98F 0x0BDE9B8 0x21176C4 0x21176C8 +0x0000000 +0x0000029 +0x1538D35 +0x1538D39 LEVEL 1 - THE SECTION VALUE not to scale container header marker + root count records 0 to 116 4,412,840 B record 117 whole record 66,604 B records 118 to 569 17,773,360 B 0x0BDE9B8 0x0BDE9C0 0x1013F68 0x1024394 0x21176C4 +0x0000000 +0x0000008 +0x04355B0 +0x04459DC +0x1538D0C LEVEL 2 - ONE DNNI RECORD not to scale 20-byte header marker, tag, type, children, size payload 66,584 B 0x1013F68 0x1013F7C 0x1024394 +0x0000000 +0x0000014 +0x001042C

Figure 2 The four nested things this manual has to name, on one page. A chain record is the framed key/value unit. A section is the value inside one such record. A DNNI record is a 20-byte header plus a payload, inside a section. A payload is what follows a record header. Sizes in this manual always say which of the four they are; a figure that says whole record includes the 20-byte header, and one that says payload does not.

3.1 The rules the figures follow

4. The container header

The first 0x1000 bytes are reserved. They hold a fixed header, a hash index over the property chain, and zero padding out to the boundary. Sixteen further bytes sit between that boundary and the first chain record. The 0x1000 figure is inherited from the parametric format, where the same region held a table of 1021 global synthesis floats.

LEVEL 0 - THE WHOLE FILE, 40,536,086 B, 22 CHAIN RECORDS the four sections are to scale; the three metadata bands are widened to fit their labels head 4,560 B section 0 - f0 8,111,519 B config 1,947 B section 1 - timing 4,328,069 B section 2 - acoustic 22,252,857 B section 3 - vocoder 5,836,582 B trailer 552 B 0x0000000 0x00011D0 0x07BD76F 0x07BDF0A 0x0BDE98F 0x21176C8 0x26A85EE 0x26A8816 LEVEL 1 - THE RESERVED HEAD AND THE GAP, 0x0000 - 0x1010 not to scale fixed fields 22 B hash index 22 x 12 B = 264 B zero padding 3,810 B gap 16 B 0x0000 0x0016 0x011E 0x1000 0x1010 +0x0000 +0x0016 +0x011E +0x1000 +0x1010 LEVEL 2 - THE FIXED FIELDS magic 0x0000F580 10 constant [U] total_size u64 = file size data_offset 0x1000 0x1000 u16 [U] 0x0000 0x0004 0x0008 0x0010 0x0014 0x0016 +0x0000 +0x0004 +0x0008 +0x0010 +0x0014 +0x0016

Figure 3 The header. The reserved head is 0x1000 bytes, but the level-0 band above it is 4,560 bytes because that band is a whole number of chain records: the head, the 16-byte gap, and the eleven metadata records that follow. All fields are little-endian.

+0x000magicUint4B0x0000F580 in both banks [V]
+0x004unknownUint4B10. Not a section count: 4 sections, 22 chain records [U]
+0x008total_sizeUint8Bequals the file size exactly [V]
+0x010data_offsetUint4B0x1000 [V]
+0x014unknownUint2B0x1000, equal to data_offset [U]
+0x016index[22]_NOFS_IDXone entry per chain record [V]
+0x11Epadding[3810]UCharall zero in both banks [V]
+0x1000gap[16] UChartwelve zero bytes then u32 = 0x1000 [V]
+0x000key_hashUint4BFNV-1a 32 of the chain key, signed char [V]
+0x004offsetUint8Bstart of the chain record, not of its value [V]

[V] The chain starts at 0x1010, not at data_offset. The sixteen bytes at 0x1000 are 000000000000000000000000 then 00100000 — twelve zeros followed by a u32 = 0x1000. That trailing word is exactly what a backward walk reads when it steps off the front of the first record, so it acts as a sentinel terminating reverse traversal at the header. A parser that starts at data_offset and expects a record there will fail.

[V] The index is neither in chain order nor sorted by hash, and its position is not a function of the hash modulo the entry count. Resolve a key by searching the index for its hash; do not rely on position.

4.1 The index hash

[V] The hash is 32-bit FNV-1a with the standard offset basis 0x811C9DC5 and prime 0x01000193, computed over sign-extended char. Feeding bytes as unsigned reproduces only 18 of 22 entries per bank — every key whose bytes are all below 0x80. The 4 misses are the three 29-byte binary section keys and the 1-byte key 0x91, all of which contain high bytes. Sign-extending each byte to a 32-bit value before the XOR resolves all of them: 22 of 22 here and 44 of 44 across both banks.

uint32_t h = 0x811C9DC5;
for (const char *p = key; p != key + len; ++p)
    h = (h ^ (uint32_t)(int32_t)*p) * 0x01000193;

The signature of the bug is diagnostic: a mismatching entry always agrees with unsigned FNV-1a in its lowest byte and nowhere else, because sign extension only ever disturbs bits 8 and above.

[S] The original documentation gives the header as 12 bytes with a u32 total_size and no index at all. [V] That is wrong for neural banks on both counts: the size field is 64 bits, and a 22-entry index sits at +0x016. The u32 at +0x004 does read 10, and the original reports the same constant across a much larger set of voices, which corroborates that the value is fixed rather than a property of these two files.

5. The property chain

Everything above the record layer — names, languages, the configuration blob, the four sections themselves and the two opaque trailer blobs — is a single flat chain of length-framed key/value records. It begins at 0x1010 and tiles to the last byte of the file with no gap and no slack, which is the strongest available check that the container is fully understood.

LEVEL 0 - THE WHOLE FILE, 40,536,086 B, 22 CHAIN RECORDS the four sections are to scale; the three metadata bands are widened to fit their labels head 4,560 B section 0 - f0 8,111,519 B config 1,947 B section 1 - timing 4,328,069 B section 2 - acoustic 22,252,857 B section 3 - vocoder 5,836,582 B trailer 552 B 0x0000000 0x00011D0 0x07BD76F 0x07BDF0A 0x0BDE98F 0x21176C8 0x26A85EE 0x26A8816 LEVEL 1 - THE CHAIN, 0x1010 TO EOF, 22 RECORDS not to scale .name 28 B .version 27 B .vendor 41 B 8 more metadata records f0model-dds section 0 4 records key 0x91 config blob sections 1-3 3 records 7f7f7f7f 256 B 7e7f7f7f 256 B 0x0001010 0x000102C 0x0001047 0x0001070 0x00011D0 0x07BD76F 0x07BD7F4 0x07BDF0A 0x26A85EE 0x26A8702 0x26A8816 LEVEL 2 - ONE CHAIN RECORD, HERE .version = "101" len u32 = 27 type u16 keylen u16 = 8 key bytes ".version" vallen u32 = 3 value "101" len u32 = 27 0x102C 0x1030 0x1032 0x1034 0x103C 0x1040 0x1043 0x1047 +0x000 +0x004 +0x006 +0x008 +0x010 +0x014 +0x017 +0x01B

Figure 4 The property chain. The length word is repeated at both ends and counts both copies of itself, so the chain can be walked in either direction. Level 1 is drawn as chain records, which is what the level-0 bands are made of.

+0x000lenUint4Btotal record length, including both copies [V]
+0x004typeUint2B1 for all 44 records observed [V]
+0x006keylenUint2B1, 4..29 bytes; may be binary [V]
+0x008key[keylen] UCharASCII name, or a binary section key [V]
the three fields below are at key-dependent offsets
+0x008+kvallenUint4Bvalue length in bytes [V]
+0x00C+kvalue[vallen] UCharstring, blob, or a whole DNNI section [V]
+len-4len2Uint4Bthe length again, for reverse traversal [V]

5.1 The full chain, GUMI AI v101

offsetkeyrecordvaluecontent
0x0001010.name287GUMI AI
0x000102C.version273101
0x0001047.vendor4118INTERNET Co., Ltd.
0x0001070.multi6543english mandarin japanese spanish cantonese
0x00010B1.feature_rap_languages6325english mandarin japanese
0x00010F0.musical_preference394sing
0x0001117.timbre_styles5323Ballade Cute Soft Vivid
0x000114C.hdvm254true
0x0001165.feature_f0_vibmod340
0x0001187.feature_f0_orn310
0x00011A6.feature_f0_enhancement4231.0
0x00011D0f0model-dds8,111,5198,111,492SECTION
0x07BD76Ff0model-configs4615_ _ _ _ _ _ _ _
0x07BD79D.language338japanese
0x07BD7BE.phoneset316romaji
0x07BD7DD.type232mu
0x07BD7F40x911,8141,7971797 binary bytes
0x07BDF0Ab3d8126b21183f76…4,328,0694,328,024SECTION
0x0BDE98Fb3d8126b21183f76…22,252,85722,252,812SECTION
0x21176C8b3d8126b21183f76…5,836,5825,836,537SECTION
0x26A85EE7f7f7f7f…276256256 binary bytes
0x26A87027e7f7f7f…276256256 binary bytes

The record column is the whole chain record including framing; the value column is what the key maps to. For the four section records the difference is the 45-byte framing of a 29-byte key, and 27 bytes for f0model-dds.

[V] The three binary section keys share an 8-byte prefix (b3d8126b21183f76) and a 5-byte suffix (06258849bd) with 16 unique bytes between, in both banks. [U] Those middles are not MD5 hashes of the sections they name. [V] They are, however, looked up by name: the configuration blob stores each of them as the value of a readable key (model_duration, model_timbre_pred, model_vocoder), which is how the engine finds a section without scanning. See section 15.

[S] The original documentation describes this same key/value encoding for the parametric format and reads the leading and trailing length words as a surrounding “block” with a reserved field. [V] The block is the record: the second word is the same length repeated at the end of the previous record.

6. Sections

Four chain records carry a DNNI section as their value. Each section begins with an 8-byte container header — a marker and a root count — and then a stream of records that runs to the last byte of the section with nothing left over.

LEVEL 0 - THE WHOLE FILE, 40,536,086 B, 22 CHAIN RECORDS the four sections are to scale; the three metadata bands are widened to fit their labels head 4,560 B section 0 - f0 8,111,519 B config 1,947 B section 1 - timing 4,328,069 B section 2 - acoustic 22,252,857 B section 3 - vocoder 5,836,582 B trailer 552 B 0x0000000 0x00011D0 0x07BD76F 0x07BDF0A 0x0BDE98F 0x21176C8 0x26A85EE 0x26A8816 LEVEL 1 - SECTION 2, 570 RECORDS not to scale; box sizes are whole records, 20-byte header included container 8 B records 0-3 4 records, 112 B rec 4 _psv, 1,139 B records 5-24 20 records, 42,325 B rec 25 prim, 152 B records 26-116 91 records, 4,369,112 B rec 117 prim, 66,604 B records 118-561 444 records, 17,772,480 B rec 562-569 style subtree, 880 B 0x0BDE9B8 0x0BDE9C0 0x0BDEA30 0x0BDEEA3 0x0BE93F8 0x0BE9490 0x1013F68 0x1024394 0x2117354 0x21176C4 +0x0000000 +0x0000008 +0x0000078 +0x00004EB +0x000AA40 +0x000AAD8 +0x04355B0 +0x04459DC +0x153899C +0x1538D0C LEVEL 2 - THE SECTION CONTAINER HEADER marker 0x7FCA00FF root count u32 = 1 0x0BDE9B8 0x0BDE9BC 0x0BDE9C0 +0x000 +0x004 +0x008

Figure 5 A section. The container header is eight bytes, not twenty: the marker is not followed by a name, and the first record starts immediately at +0x008. Every omitted run of records is drawn as a dashed box that says how many records and how many bytes it stands for, so the level still tiles.

#rolesection startssectionchain recordrecordsquantisedint16 parameters
0f0 / pitch model0x00011E78,111,4928,111,5194561463,821,664
1duration & timing model0x07BDF334,328,0244,328,06981212,126,080
2acoustic / timbre model0x0BDE9B822,252,81222,252,85757020110,912,640
3vocoder0x21176F15,836,5375,836,582231692,742,016
total40,528,86540,529,027133843719,602,400

[V] All four roles are now direct readings rather than inferences: the configuration blob names sections 1, 2 and 3 by their binary keys as model_duration, model_timbre_pred and model_vocoder, and section 0's chain key is the ASCII string f0model-dds. Version 6 marked sections 0 and 3 as inferred.

[V] The u32 after the container marker is not a record count. It reads 2, 2, 1, 2 for sections 0–3 in both banks, against actual record counts of 456, 81, 570, 231. Section 8 shows what it really is: the number of roots in the record tree.

[V] Section lengths are not constant across banks. Teto's section 2 is 22,252,813 bytes, one byte longer, because record 562 stores the style names and Teto's are one character longer in total. It is not safe to assume that two banks of the same generation have identical section lengths.

7. The DNNI record

Records are introduced by a marker that is a quiet NaN when read as a little-endian float32, so it can never collide with a legitimate weight value.

LEVEL 0 - THE WHOLE FILE, 40,536,086 B, 22 CHAIN RECORDS the four sections are to scale; the three metadata bands are widened to fit their labels head 4,560 B section 0 - f0 8,111,519 B config 1,947 B section 1 - timing 4,328,069 B section 2 - acoustic 22,252,857 B section 3 - vocoder 5,836,582 B trailer 552 B 0x0000000 0x00011D0 0x07BD76F 0x07BDF0A 0x0BDE98F 0x21176C8 0x26A85EE 0x26A8816 LEVEL 1 - SECTION 2, 570 RECORDS not to scale; box sizes are whole records, 20-byte header included container 8 B records 0-3 4 records, 112 B rec 4 _psv, 1,139 B records 5-24 20 records, 42,325 B rec 25 prim, 152 B records 26-116 91 records, 4,369,112 B rec 117 prim, 66,604 B records 118-561 444 records, 17,772,480 B rec 562-569 style subtree, 880 B 0x0BDE9B8 0x0BDE9C0 0x0BDEA30 0x0BDEEA3 0x0BE93F8 0x0BE9490 0x1013F68 0x1024394 0x2117354 0x21176C4 +0x0000000 +0x0000008 +0x0000078 +0x00004EB +0x000AA40 +0x000AAD8 +0x04355B0 +0x04459DC +0x153899C +0x1538D0C LEVEL 2 - THE 20-BYTE HEADER OF RECORD 117 header fields to scale; the payload box is truncated marker 0x7FCA40FF tag 4 B, "prim" type_code u32 = 52 children u32 = 0 size u32 = 66,584 payload 66,584 B 0x1013F68 0x1013F6C 0x1013F70 0x1013F74 0x1013F78 0x1013F7C 0x1024394 +0x000 +0x004 +0x008 +0x00C +0x010 +0x014 +0x1042C

Figure 6 The DNNI record header. Walk the stream with offset + 20 + size; the size field is authoritative and no scanning for markers is needed.

+0x000markerUint4B0x7FCA00FF hdr / 0x7FCA40FF data / 0x7FCA41FF meta [V]
+0x004tag[4] UChartype tag; ASCII only in section 2 [V]
+0x008type_codeUint4Blayer type in section 2, arbitrary elsewhere [V]
+0x00CchildrenUint4Bnumber of records nested under this one [V]
+0x010sizeUint4Bpayload bytes that follow [V]
+0x014payload[size] UCharnext record is at +0x014 + size
markerrolecount in GUMI AI v101
0x7FCA00FFsection container header, then a u32 root count4 (one per section)
0x7FCA40FFdata record866
0x7FCA41FFmetadata / configuration record472

[V] 1338 records plus 4 container headers, identically in both banks. The per-section split is 456 / 81 / 570 / 231 and every section walks to its exact end with zero bytes left over.

7.1 One header, six payload shapes

The 20 bytes above are common to all 1338 records. What follows them is not. Six payload shapes account for every record in section 2, and they differ in size by four orders of magnitude — from nothing at all to 66 KB. This is why a parser must dispatch on tag and type_code before it reads a single payload byte.

STRUCTURAL NODE - size 0, e.g. cmpg, modm, cmpu, _rld 20-byte header children = 5 no payload size = 0 LAYER DESCRIPTOR - modl, 20-byte payload 20-byte header children = 4 kernel 3 stride 1 padding 3 dilation 3 groups 1 STRING TABLE - _stb, _psv 20-byte header count u32 len u32 string ASCII ... len u32 string ASCII PER-VOICE ARRAY - prim, 132 or 516-byte payload 20-byte header count u32 = 32 32 x float32 128 B QUANTISED TENSOR - prim, up to 66,584-byte payload 20-byte header v3, v4, 0, n 16 B n x float32 scales 1,024 B rows, cols 8 B rows x cols x int16 65,536 B

Figure 7 The payload shapes, drawn to a common header width but not to a common byte scale — the last row is 3,300 times the length of the first.

7.2 The word at +0x0C is a child count

[V] The field is easily mistaken for padding. It is zero on 369 of 1338 records and nonzero on the rest. On modl records it is exactly the number of prim records that immediately follow: 136 of 136 in GUMI and 136 of 136 in Teto, with no exceptions.

tagvalue at +0x0Cmeaning
prim0 on all 355a leaf; weight tensors have no children
modl2 ×102, 4 ×33, 1 ×1tensors belonging to this layer: weight+bias, gated conv, embedding
moda0 on all 8an activation marker, a leaf
cmpu3 on all 3structural group; children are layers
modm2, 3 ×3, 4module boundary
cmpg4 ×6, 5 ×2, 9 ×3, 15 ×3structural group, one level higher [U]
_stb2the style subtree: a vector group and a scalar record

[S] The original documentation had this right and called it meta[1], “the number of prim tensors following this modl”. Its meta[] index counts words from the tag, so meta[0] is type_code, meta[1] is the child count, meta[2] is size, and meta[3] onward are payload words.

7.3 Type tags are only meaningful in section 2

sectionASCII tags, GUMIASCII tags, Tetotags differing between banks
013 of 45614 of 456415 of 456
12 of 815 of 8174 of 81
2570 of 570570 of 5700 of 570
34 of 2319 of 231212 of 231

[V] Section 2 carries stable, meaningful ASCII tags and they are byte-identical between the two banks. Sections 0, 1 and 3 carry essentially none, and their tags differ in almost every record. The same is true of type_code and the child count: outside section 2 all three words are scrambled while the marker and size stay valid. A parser can therefore walk any section but can only interpret section 2. The ASCII-tag counts differ between the banks because the scrambled bytes happen to be printable at different rates; version 6 printed the GUMI column alone as if it held for both.

[S] The original's parsing algorithm scans the whole file for marker bytes and accepts a marker if the next four bytes are printable ASCII. [V] That heuristic admits false positives: the binary tags ,SZ0, i8Bj, V;W0 and ]FT3 all pass it in GUMI's sections 0 and 3 while being ordinary scrambled tags. Sequential walking with the size field has no such failure mode.

8. The record stream is a tree

[V] The child counts are not decoration. Reading the root count from the container header and then consuming each record's children recursively walks section 2 as a prefix-encoded tree and lands exactly on the last record: 570 of 570 consumed from a single root, in both banks. Sections 0, 1 and 3 do not walk, for the same reason their tags do not resolve — the arity word is scrambled there too.

i = 0
for _ in range(root_count):
    i = consume(i)
assert i == len(records)

def consume(i):
    n = records[i].children; i += 1
    for _ in range(n): i = consume(i)
    return i
_rld  #0    2 children - the section root
 _rld #1    8 children
  _ftm #2   6 children, payload (108, 1, 71, 2, 66, 0, 0.005f, 0)
  cmpg #3   5 children
   _psv #4  japanese-romaji-phones, 42 symbols
   _psv #5..#8   mandarin, english, cantonese, spanish
   _psv #9  multiphone, 107 symbols
  _dct #10  2 children: two prim tables of 16,392 B
  cmpu #13,#17,#21   3 children each: weight, weight, bias
  prim #25  the base identity vector, 132 B payload
  _vqc #26  4 children - the encoder stack
  ...
  _stb #562 2 children - the style subtree
   cmpg #563     5 children
    prim #564    default vector, 132 B payload
    prim #565..#568   one vector per style, 132 B each
    prim #569    the four per-style scalars, 20 B payload

Section 2 as the arity words encode it. The style block is not a convention inferred from adjacency: it is a subtree with _stb at its root.

[I] This also explains the container header's u32. It is the number of roots, and section 2's value of 1 is the reason a single walk from record 0 consumes everything. [U] Whether the value 2 in the other three sections is meaningful cannot be tested while their arity words are scrambled.

9. Quantised tensors

96.7% of the file is one payload shape: a per-row scale array in float32 followed by an int16 matrix.

LEVEL 0 - THE WHOLE FILE, 40,536,086 B, 22 CHAIN RECORDS the four sections are to scale; the three metadata bands are widened to fit their labels head 4,560 B section 0 - f0 8,111,519 B config 1,947 B section 1 - timing 4,328,069 B section 2 - acoustic 22,252,857 B section 3 - vocoder 5,836,582 B trailer 552 B 0x0000000 0x00011D0 0x07BD76F 0x07BDF0A 0x0BDE98F 0x21176C8 0x26A85EE 0x26A8816 LEVEL 1 - SECTION 2, 570 RECORDS not to scale; box sizes are whole records, 20-byte header included container 8 B records 0-3 4 records, 112 B rec 4 _psv, 1,139 B records 5-24 20 records, 42,325 B rec 25 prim, 152 B records 26-116 91 records, 4,369,112 B rec 117 prim, 66,604 B records 118-561 444 records, 17,772,480 B rec 562-569 style subtree, 880 B 0x0BDE9B8 0x0BDE9C0 0x0BDEA30 0x0BDEEA3 0x0BE93F8 0x0BE9490 0x1013F68 0x1024394 0x2117354 0x21176C4 +0x0000000 +0x0000008 +0x0000078 +0x00004EB +0x000AA40 +0x000AAD8 +0x04355B0 +0x04459DC +0x153899C +0x1538D0C LEVEL 2 - RECORD 117, HEADER THEN PAYLOAD not to scale header 20 B payload 66,584 B 0x1013F68 0x1013F7C 0x1024394 +0x00000 +0x00014 +0x1042C LEVEL 3 - THE PAYLOAD not to scale v3 16 v4 11 0 n 256 scales 256 x float32 = 1,024 B rows 256 cols 128 int16 matrix 256 x 128 x 2 = 65,536 B 0x1013F7C 0x1013F80 0x1013F84 0x1013F88 0x1013F8C 0x101438C 0x1014390 0x1014394 0x1024394 +0x00000 +0x00004 +0x00008 +0x0000C +0x00010 +0x00410 +0x00414 +0x00418 +0x10418 LEVEL 4 - THE SCALE ARRAY, ONE FLOAT32 PER OUTPUT ROW not to scale scale[0] f32 scale[1] f32 scale[2] f32 ... scale[255] f32 0x1013F8C 0x1013F90 0x1013F94 0x1013F98 0x1014388 0x101438C +0x000 +0x004 +0x008 +0x00C +0x3FC +0x400

Figure 8 A quantised tensor, from the whole file down to a single scale. Row r of the matrix is divided by 32,767 and multiplied by scale[r].

+0x000v3Uint4B16 on all 437 tensors; a storage flag meaning 16-bit [V]
+0x004v4Uint4B6..15; not a function of the tensor, inert [V]
+0x008reservedUint4B0 on all 437 [V]
+0x00CnUint4Bscale count; always equals rows [V]
+0x010scale[n] Floatone per output row [V]
+0x010+4nrowsUint4B
+0x014+4ncolsUint4B
+0x018+4nq[rows*cols] Int2Brow-major [V]
payload size == 16 + n*4 + 8 + rows*cols*2   exactly, on 437 of 437 records in both banks

W[r, c] = q[r, c] * scale[r] / 32767.0

[V] Dequantisation is exactly invertible: rint(W / (scale/32767)) reproduces the original int16 array with a maximum absolute error of 0 over all 437 tensors in both banks.

9.1 The clip range is asymmetric, and how often it is reached

Version 6 was wrong here. It reported that 32.4% of rows reach full scale and dismissed a figure of about 55% quoted elsewhere. Both of those numbers came out of an abs() evaluated in int16, where abs(-32768) overflows back to -32768 and is then silently discarded by a maximum. Computing the same three statistics that way reproduces version 6's figures to two decimal places (32.37% / 59.71% / 95.84%), which is what identifies the cause. The correct figures are below, and the ~55% that version 6 rejected is close to right.

[V] The quantiser clips to the full signed int16 range, not to a symmetric ±32767: the value -32768 occurs 54,366 times in GUMI and 54,291 times in Teto. A row that hits the negative rail therefore dequantises to scale[r] × 32768/32767, marginally outside the nominal range. Full scale means |q| ≥ 32767.

statistic, over all 107,718 rowsGUMI AI v101Kasane Teto v104
rows reaching full scale, |q| ≥ 3276751.45%51.55%
rows reaching 30,00068.85%69.01%
rows reaching 16,38496.37%96.41%
the same three under version 6's int16 abs()32.37% / 59.71% / 95.84%32.43% / 59.85% / 95.89%

Per section, the full-scale row fraction is 49.88% (section 0), 41.90% (section 1), 55.22% (section 2), 45.20% (section 3). The encoder therefore clips rather than using an exact per-row maximum, and clipped elements are not recoverable.

9.2 What v4 is not

[V] v4 is not a function of the tensor. Diffing GUMI against Teto record by record turns up 368 quantised tensors whose per-row scales and whose entire int16 array are byte-identical; 45 of those carry a different v4 — usually by one, occasionally by two. Since the tensor is the same in both files, no function of the tensor can produce two answers. Direct searches over bit-lengths of the maximum, mean, median and upper percentiles of |q|, over scale exponents, dynamic range, and over rows, cols and their product all fail to predict it.

[S] The original documentation reads v4 as a kernel size, quoting values 3, 5, 7, 9, 10 and 20. [V] In these two banks it takes the values 6, 7, 8, 9, 10, 11, 12, 13, 15, and the kernel size is carried elsewhere, in the modl tuple described in section 14. Either way dequantisation does not need it.

9.3 The float32 misreading

[S] The original documentation states that a prim payload is a 20-byte header followed by raw IEEE-754 float32 weights. [V] It is not, and the arithmetic settles it. The original's own worked example is a data region of 66,576 bytes, giving a stated payload of 66,584. Payloads of exactly that size exist in both banks — there are 111 of them in GUMI, 60 in section 0 and 51 in section 2 — and every one decodes as 16 + 256×4 + 8 + 256×128×2 = 66,584: a 256 by 128 int16 matrix with 256 float32 scales. (Version 6 gave this count as 46; the real number is 111.)

Read as float32 the same region yields 16,644 elements, which the original factors as 73 × 76 × 3 and interprets as a convolution. That factorisation is arithmetically correct and structurally meaningless: 16,644 is 66,576/4, and 66,576 is the byte count of a quantised tensor plus its scale table, not a float32 element count. [V] The real channel counts are declared explicitly in the _ncw and _gnc records and are powers of two throughout; see section 14.

10. Float32 records

The rest of the payload shapes are small and plain. A one-word count followed by that many float32 values is the form every per-voice array takes.

+0x000countUint4Belement count [V]
+0x004v[count] FloatIEEE-754 single, little-endian [V]

[V] The encoding is predicted by tag and type_code together, not by type_code alone. A record tagged prim with type code 49 is a one-word float array in 151 cases; a record tagged modl with the same type code is a layer descriptor and not a tensor at all, in 129 cases. Dispatch on the pair.

[I] Within a modl the type_code of each child marks its role: 52 for weight matrices, 49 for the bias vector that follows them. A gated convolution therefore reads as three 52s and one 49, and an ordinary layer as one 52 and one 49.

11. Per-voice data — the singer

Everything up to this point is shared. Two banks of the same generation have byte-identical weight tensors in sections 0, 1 and 2 — all 19,602,400 of them. What differs is a handful of small float32 arrays, and this section locates every one of them.

arraysectionrecordrecord offsetpayload offsetfloatspayload
pitch0record 130x000359B0x00035AF32132 B
timing1record 800x0BDE7730x0BDE787128516 B
base identity2record 250x0BE93F80x0BE940C32132 B
default mode2record 5640x21173A40x21173B832132 B
style vectors2records 565–5680x211743C0x2117450128528 B
style scalars2record 5690x211769C0x21176B0420 B
style names2record 5620x21173540x211736840 B
total1500 B

The two offset columns are the record start and the payload start; they differ by the 20-byte record header. Version 6's table gave record offsets under a heading that did not say so while quoting payload offsets in the prose beside it.

LEVEL 0 - THE WHOLE FILE, 40,536,086 B four marks, drawn at minimum visible width 0x0000000 0x000359B 0x0003633 0x0BDE773 0x0BDE98B 0x0BE93F8 0x0BE9490 0x2117354 0x2117390 0x21173A4 0x211743C 0x2117660 0x211769C 0x21176C4 0x26A8816

Figure 9 The entire singer, marked on the whole file. Four red marks, 1,500 bytes between them, drawn at the minimum visible width because at true scale each would be thinner than a hairline. The other 40,534,586 bytes are shared with every bank of this generation.

11.1 Vocal identity is 396 bytes

Of those 1500 bytes, three arrays carry the identity of the voice: pitch, base and default. Ninety-six float32 values. [V*] Copying just these three payloads from one bank into another moves the perceived singer while leaving all 19,602,400 weights untouched.

Here they are in full. This is the whole of GUMI AI v101's vocal identity — every byte of it:

pitch — section 0, record 13, payload at 0x000035AF, 132 bytes: a u32 count of 32, then 32 × float32

000035af  20 00 00 00 9d 31 dd bd 30 d2 2d 3d 4a 19 5a bd
000035bf  d0 0d 0f 3e 31 1d 25 3e f2 89 21 be 97 63 1c 3e
000035cf  a2 70 55 bd 62 f9 83 bd ac 2e dd bc 77 24 24 3d
000035df  c2 66 00 3d f5 38 52 bc 06 99 d4 3d c8 11 9d bd
000035ef  aa cd a7 3e 22 88 03 3e b2 a9 95 bd 76 a4 11 3e
000035ff  bc 71 7f bd 94 49 bb 3e 8a fe 19 3e 58 01 1b bc
0000360f  d5 e7 86 3d 76 3e 62 3d d0 fb f8 3c e8 14 be bd
0000361f  54 8b 83 be 27 d7 d2 3d cb f8 b2 3d e4 98 2d be
0000362f  ab b6 93 bc

base identity — section 2, record 25, payload at 0x00BE940C

00be940c  20 00 00 00 bd 23 8c bd 05 8e 03 3d c4 7d 56 bd
00be941c  93 2d 49 3e 09 a6 2c 3e 81 af b2 3d 15 b1 4e 3d
00be942c  06 bd 1c be f6 7e 69 bc b0 62 3f 3e f6 a3 c2 3d
00be943c  7b f3 01 bc ac 96 6e ba 35 2b 9f bd 82 ba 45 bd
00be944c  65 ef ce 3d 85 21 11 3d 97 7a b9 bd c5 7a b2 3b
00be945c  5d af c8 3d 44 35 18 be 70 f7 b6 bd ef 15 31 be
00be946c  84 0d fd bc da 3e fb 3d 8b b2 b4 bd c8 61 c4 bc
00be947c  a2 36 0f 3e 29 d7 8c 3d 04 6d 0d be 4a 79 b5 bd
00be948c  cf 27 39 bd

default mode — section 2, record 564, payload at 0x021173B8

021173b8  20 00 00 00 a5 e1 4b 3d 6b 1c 59 bd 30 38 2f 3e
021173c8  4a ff 76 3d 7e 2a f5 3d 20 6b 9c be c3 17 e4 bd
021173d8  05 60 de 3c ed 22 d9 3d b8 a7 f0 bc b9 5e 81 3d
021173e8  fc 87 a6 bc 73 eb 1a be 05 2e a6 3d 08 22 49 3e
021173f8  68 80 9f 3d d9 a8 9d 3e 95 cf 2e bd ad 1a 2b bd
02117408  34 ff 8d 3d e0 36 33 bd a9 e5 18 3e e0 e0 06 bd
02117418  66 64 cb be 47 62 cc 3e dd 45 39 bc 18 80 36 3c
02117428  80 c5 16 be 04 6e 40 be 61 7f 90 3d d1 9e 64 3e
02117438  d9 01 53 be

[V] 396 bytes out of 40,536,086 is 0.00098% of the file. The first four bytes of each block are the count word 20 00 00 00 = 32; everything after is little-endian float32. [V] Between GUMI and Kasane Teto, 358 of these 396 bytes differ.

11.2 Pitch — section 0, record 13

[V] Section 0 is the f0 model. Five of its records carry a 132-byte payload — records 13, 429, 435, 445, 451 — and only record 13 differs between banks. It is the one an independent editor exports as the pitch vector.

LEVEL 0 - THE WHOLE FILE, 40,536,086 B, 22 CHAIN RECORDS the four sections are to scale; the three metadata bands are widened to fit their labels head 4,560 B section 0 - f0 8,111,519 B config 1,947 B section 1 - timing 4,328,069 B section 2 - acoustic 22,252,857 B section 3 - vocoder 5,836,582 B trailer 552 B 0x0000000 0x00011D0 0x07BD76F 0x07BDF0A 0x0BDE98F 0x21176C8 0x26A85EE 0x26A8816 LEVEL 1 - SECTION 0, 456 RECORDS not to scale; box sizes are whole records, 20-byte header included container 8 B rec 0 36 B ... rec 13 pitch, 152 B ... rec 429 152 B ... rec 435 152 B ... rec 445 152 B ... rec 451 152 B ... rec 455 28 B 0x00011E7 0x00011EF 0x0001213 0x000359B 0x0003633 0x07B3CBB 0x07B3D53 0x07B5793 0x07B582B 0x07BBB33 0x07BBBCB 0x07BD60B 0x07BD6A3 0x07BD74F 0x07BD76B +0x0000000 +0x0000008 +0x000002C +0x00023B4 +0x000244C +0x07B2AD4 +0x07B2B6C +0x07B45AC +0x07B4644 +0x07BA94C +0x07BA9E4 +0x07BC424 +0x07BC4BC +0x07BC568 +0x07BC584 LEVEL 2 - RECORD 13, THE PITCH VECTOR to scale; 152 B in total header 20 B count u32 = 32 32 x float32 128 B 0x000359B 0x00035AF 0x00035B3 0x0003633 +0x000 +0x014 +0x018 +0x098

Figure 10 Pitch. The other four 132-byte payloads in section 0 are byte-identical between the two banks examined, so they are shared backbone data, not per-voice.

11.3 Timing — section 1, record 80

[V] Section 1 is the duration and timing model, and the timing vector is its last record. It is the only per-voice array with 128 floats rather than 32.

LEVEL 0 - THE WHOLE FILE, 40,536,086 B, 22 CHAIN RECORDS the four sections are to scale; the three metadata bands are widened to fit their labels head 4,560 B section 0 - f0 8,111,519 B config 1,947 B section 1 - timing 4,328,069 B section 2 - acoustic 22,252,857 B section 3 - vocoder 5,836,582 B trailer 552 B 0x0000000 0x00011D0 0x07BD76F 0x07BDF0A 0x0BDE98F 0x21176C8 0x26A85EE 0x26A8816 LEVEL 1 - SECTION 1, 81 RECORDS not to scale; box sizes are whole records, 20-byte header included container 8 B rec 0 28 B ... rec 40 264236 B ... rec 79 540 B rec 80 timing, 536 B 0x07BDF33 0x07BDF3B 0x07BDF57 0x0A1264B 0x0A52E77 0x0BDE557 0x0BDE773 0x0BDE98B +0x0000000 +0x0000008 +0x0000024 +0x0254718 +0x0294F44 +0x0420624 +0x0420840 +0x0420A58 LEVEL 2 - RECORD 80, THE TIMING VECTOR to scale; 536 B in total header 20 B count u32 = 128 128 x float32 512 B 0x0BDE773 0x0BDE787 0x0BDE78B 0x0BDE98B +0x000 +0x014 +0x018 +0x218

Figure 11 Timing. [U] What this array controls has not been isolated: it has never been transplanted on its own. Experiment C in section 21 is designed to settle it.

11.4 Base identity and the default mode

[V] Two 32-float vectors live in section 2 itself: record 25, near the top of the tree, and record 564, the first child of the style group. Together with pitch they are the 396 bytes above.

LEVEL 0 - THE WHOLE FILE, 40,536,086 B, 22 CHAIN RECORDS the four sections are to scale; the three metadata bands are widened to fit their labels head 4,560 B section 0 - f0 8,111,519 B config 1,947 B section 1 - timing 4,328,069 B section 2 - acoustic 22,252,857 B section 3 - vocoder 5,836,582 B trailer 552 B 0x0000000 0x00011D0 0x07BD76F 0x07BDF0A 0x0BDE98F 0x21176C8 0x26A85EE 0x26A8816 LEVEL 1 - SECTION 2, 570 RECORDS not to scale; box sizes are whole records, 20-byte header included container 8 B records 0-3 4 records, 112 B rec 4 _psv, 1,139 B records 5-24 20 records, 42,325 B rec 25 prim, 152 B records 26-116 91 records, 4,369,112 B rec 117 prim, 66,604 B records 118-561 444 records, 17,772,480 B rec 562-569 style subtree, 880 B 0x0BDE9B8 0x0BDE9C0 0x0BDEA30 0x0BDEEA3 0x0BE93F8 0x0BE9490 0x1013F68 0x1024394 0x2117354 0x21176C4 +0x0000000 +0x0000008 +0x0000078 +0x00004EB +0x000AA40 +0x000AAD8 +0x04355B0 +0x04459DC +0x153899C +0x1538D0C LEVEL 2 - RECORD 25, THE BASE IDENTITY VECTOR to scale; 152 B in total header 20 B count u32 = 32 32 x float32 128 B 0x0BE93F8 0x0BE940C 0x0BE9410 0x0BE9490 +0x000 +0x014 +0x018 +0x098

Figure 12 Base identity, in the same three steps as every other record in this manual. Record 564 has the identical layout, 880 bytes from the end of the section.

11.5 Vocal modes, and the ordering rule

[V] Record 562, tagged _stb, is a style-name table. Its payload is a count followed by length-prefixed names:

[u32 count] then count × { [u32 len][len bytes of ASCII] }
bank.timbre_styles (display order)_stb (storage order)
GUMI AI v101Ballade Cute Soft VividSoft Ballade Vivid Cute
Kasane Teto v104Joyful Cute Power MellowCute Power Mellow Joyful

[V] The vectors and the scalars both follow _stb order. The obvious mistake here is to take the order from .timbre_styles and then treat the mismatch as a permutation to be solved. There is no permutation. Section 8 settles it structurally: _stb is the parent of the cmpg that holds the vectors and of the record that holds the scalars, so the name table and the data it names are one subtree. .timbre_styles is a separate display string that happens to be alphabetical for GUMI and is not for Teto.

recordGUMI style (_stb)scalar in record 569pairing from .timbre_styles
565Soft+0.06766955Ballade — wrong
566Ballade-0.02669089Cute — wrong
567Vivid+0.02701959Soft — wrong
568Cute-0.01311327Vivid — wrong

Verified both ways on both banks: the 128 data bytes of records 565–568 equal the editor's exported style vectors when matched in _stb order, and the four floats of record 569 equal the editor's scalars in the same order, to the last bit.

[V] Record 569 is a normal record with a one-word count header, not sixteen loose bytes at the tail of the section. It happens to be the last record, which is how it is easily mistaken for loose bytes.

12. Phoneme dictionaries

[V] The six _psv records near the top of section 2 are not conditioning vectors. They are the phoneme dictionaries, and they are plain text. Each is a named group of four parallel string tables.

LEVEL 0 - THE WHOLE FILE, 40,536,086 B, 22 CHAIN RECORDS the four sections are to scale; the three metadata bands are widened to fit their labels head 4,560 B section 0 - f0 8,111,519 B config 1,947 B section 1 - timing 4,328,069 B section 2 - acoustic 22,252,857 B section 3 - vocoder 5,836,582 B trailer 552 B 0x0000000 0x00011D0 0x07BD76F 0x07BDF0A 0x0BDE98F 0x21176C8 0x26A85EE 0x26A8816 LEVEL 1 - SECTION 2, 570 RECORDS not to scale; box sizes are whole records, 20-byte header included container 8 B records 0-3 4 records, 112 B rec 4 _psv, 1,139 B records 5-24 20 records, 42,325 B rec 25 prim, 152 B records 26-116 91 records, 4,369,112 B rec 117 prim, 66,604 B records 118-561 444 records, 17,772,480 B rec 562-569 style subtree, 880 B 0x0BDE9B8 0x0BDE9C0 0x0BDEA30 0x0BDEEA3 0x0BE93F8 0x0BE9490 0x1013F68 0x1024394 0x2117354 0x21176C4 +0x0000000 +0x0000008 +0x0000078 +0x00004EB +0x000AA40 +0x000AAD8 +0x04355B0 +0x04459DC +0x153899C +0x1538D0C LEVEL 2 - THE SIX _psv RECORDS, ALL CHILDREN OF ONE cmpg not to scale; whole records, 20-byte header included rec 3 cmpg rec 4 japanese, 1,139 B rec 5 mandarin, 1,535 B rec 6 english, 1,380 B rec 7 cantonese, 1,168 B rec 8 spanish, 848 B rec 9 multiphone, 2,810 B 0x0BDEA1C 0x0BDEA30 0x0BDEEA3 0x0BDF4A2 0x0BDFA06 0x0BDFE96 0x0BE01E6 0x0BE0CE0 LEVEL 3 - RECORD 4, japanese-romaji-phones not to scale name 26 B table 0 42 symbols table 1 42 classes table 2 42 canonical names table 3 13 class names 0x0BDEA44 0x0BDEA5E 0x0BDEB49 0x0BDECED 0x0BDEE15 0x0BDEEA3 +0x000 +0x01A +0x105 +0x2A9 +0x3D1 +0x45F

Figure 13 Phoneme dictionaries. The same string-table encoding is used by _stb: a length-prefixed string is the format's only variable-length primitive.

recordnamesymbolsfirst symbols
4japanese-romaji-phones42a i u e o N cl t d s sh j z ts k kw …
5mandarin-xsampa-phones59a A o @ e 7 U u i i\ i` y AU @U ia iA …
6english-arpabet-phones50aa ae ah ao aw ax ay b ch d dx dr dw dh eh er …
7cantonese-xsampa-phones46ts tsh f h s l m n N w j p ph t th k …
8spanish-xsampa-phones31a e i o u U I y ll b B d D g k p …
9multiphone107a A o @ e 7 U u i i\ i` y AU @U ia iA …
tablelengthcontent
0nthe symbols, in that language's own notation
1nthe phonetic class of each symbol, one of the 13 in table 3
2nthe canonical multiphone name for each symbol, with prefixes ARP_, ES_, ROM_, YUE_ marking symbols that exist only in one language
313the closed class vocabulary: vowel, diphthong, coda, stop, affricate, aspirate, fricative, nasal, liquid, semivowel, silence, breath, trill

[I] Table 2 is the cross-lingual mapping. It is what lets one acoustic model sing five languages: each language's symbols are rewritten into the shared 107-symbol inventory before they reach the network.

Version 6 put the spare slot at the wrong end. It stated that the multiphone record's table 2 has 108 entries rather than 107 and that “the additional one, at index 0, is the empty string”, inferring that index 0 is reserved and the real inventory starts at 1. [V] Tables 0 and 2 of the multiphone record agree entry for entry on all 107 entries — index 0 is the symbol a in both — and the extra 108th entry, the empty string, is appended at the end, at index 107. The inventory is 0–106 and the reserved slot is the last one.

[V] _ftm, near the top of the section, declares 108 in its first payload word; its payload is eight words, (108, 1, 71, 2, 66, 0) then 0x3BA3D70A — which read as float32 is 0.005, the same hop-in-seconds constant the configuration blob stores as voc_thop_output — then 0. (Version 6 printed only the first six words.)

[V] All six dictionary records are byte-identical between GUMI and Teto, as expected for shared backbone data. [V] This also grounds the .multi chain value. Both banks declare cantonese english japanese mandarin spanish, and there is exactly one dictionary per declared language plus the union. The dictionaries are stored in a different order from .multi; as with the styles, do not read order across the two.

13. Network architecture

Section 2 declares its own shape. Channel widths come from _ncw and _gnc records, layer geometry from the modl payload, and the tensors hang beneath them as children.

LEVEL 0 - THE WHOLE FILE, 40,536,086 B, 22 CHAIN RECORDS the four sections are to scale; the three metadata bands are widened to fit their labels head 4,560 B section 0 - f0 8,111,519 B config 1,947 B section 1 - timing 4,328,069 B section 2 - acoustic 22,252,857 B section 3 - vocoder 5,836,582 B trailer 552 B 0x0000000 0x00011D0 0x07BD76F 0x07BDF0A 0x0BDE98F 0x21176C8 0x26A85EE 0x26A8816 LEVEL 1 - SECTION 2, 570 RECORDS not to scale; box sizes are whole records, 20-byte header included container 8 B records 0-3 4 records, 112 B rec 4 _psv, 1,139 B records 5-24 20 records, 42,325 B rec 25 prim, 152 B records 26-116 91 records, 4,369,112 B rec 117 prim, 66,604 B records 118-561 444 records, 17,772,480 B rec 562-569 style subtree, 880 B 0x0BDE9B8 0x0BDE9C0 0x0BDEA30 0x0BDEEA3 0x0BE93F8 0x0BE9490 0x1013F68 0x1024394 0x2117354 0x21176C4 +0x0000000 +0x0000008 +0x0000078 +0x00004EB +0x000AA40 +0x000AAD8 +0x04355B0 +0x04459DC +0x153899C +0x1538D0C LEVEL 2 - THE LAYER GROUP THAT CONTAINS RECORD 117 records 115 to 120, one gated dilated convolution _gnc rec 115, (128, 128, 388) modl rec 116, 4 children prim rec 117, weight prim rec 118, weight prim rec 119, weight prim rec 120, bias 0x1013F20 0x1013F40 0x1013F68 0x1024394 0x10347C0 0x1044BEC 0x1045004 LEVEL 3 - THE modl PAYLOAD, FIVE WORDS kernel 3 stride 1 padding = dilation x (kernel-1)/2 dilation 1, 3, 9, 27 or 81 groups 1 0x1013F54 0x1013F58 0x1013F5C 0x1013F60 0x1013F64 0x1013F68 +0x000 +0x004 +0x008 +0x00C +0x010 +0x014

Figure 14 Architecture. The dilation cycle 1, 3, 9, 27, 81 across five parallel paths is the multi-receptive-field fusion block of the HiFi-GAN family.

type_codechildrenpayloadcountreading
492(1, 1, 0, 1, 1)96pointwise convolution, weight + bias
494(3, 1, 1, 1, 1)8gated dilated conv, d = 1
494(3, 1, 3, 3, 1)7gated dilated conv, d = 3
494(3, 1, 9, 9, 1)7gated dilated conv, d = 9
494(3, 1, 27, 27, 1)7gated dilated conv, d = 27
482(no payload)6linear projection, weight + bias
494(3, 1, 81, 81, 1)4gated dilated conv, d = 81
521(no payload)1embedding table

Padding does not always equal dilation. Version 6 stated that it does. It holds for the 33 kernel-3 convolutions, but the 96 pointwise layers have payload (1, 1, 0, 1, 1) — padding 0, dilation 1. [V] The invariant that actually holds on all 129 modl payloads is padding = dilation × (kernel - 1) / 2, which is what keeps the output length equal to the input length for any kernel.

[V] The modl tuple is five words. Reading only four and calling the second element groups is a natural error; it is stride, and groups is the fifth word.

13.1 Channel widths

[V] _ncw opens a block and _gnc opens each sub-block within it. Both carry a three-word payload.

+0x000in_channelsUint4B
+0x004out_channelsUint4B
+0x008extraUint4B0, 4, 64, 388 in these banks [U]
blockopens atin → outsub-blocksextra
Arec 46144 → 2564 (1 adapter + 3 at 256 → 256)0
Brec 1044 → 12815 (1 adapter + 14 at 128 → 128)388
Crec 341256 → 2569 (1 adapter + 9 at 256 → 256)4
Drec 489256 → 1284 (1 adapter + 3 at 128 → 128)64

Version 6 gave block B as 12 sub-blocks; there are 14 at 128 → 128 plus the 4 → 128 adapter. The four blocks account for all 32 _gnc records.

[I] The extra word matches vocoder constants from the configuration blob: 64 is voc_order_spec and 4 is voc_nchannel. 388 and 0 are not accounted for.

[V] Only 29 of the 33 gated convolutions have out = 2 × in. The 4 exceptions are input adapters, with shapes (128, 108), (512, 144), (256, 4), (256, 256). The 108 is the multiphone inventory plus one, from section 12.

[V] _did carries (4, 64) then 0x42800000, which as float32 is 64.0.

14. The configuration blob

[V] The chain record with the 1-byte key 0x91 holds 1,797 bytes in GUMI and 2,131 in Teto. The original documentation does not mention it, and version 6 of this manual described its structure incorrectly. It is a typed, counted property list, and its keys are readable once one XOR is undone.

LEVEL 0 - THE WHOLE FILE, 40,536,086 B, 22 CHAIN RECORDS the four sections are to scale; the three metadata bands are widened to fit their labels head 4,560 B section 0 - f0 8,111,519 B config 1,947 B section 1 - timing 4,328,069 B section 2 - acoustic 22,252,857 B section 3 - vocoder 5,836,582 B trailer 552 B 0x0000000 0x00011D0 0x07BD76F 0x07BDF0A 0x0BDE98F 0x21176C8 0x26A85EE 0x26A8816 LEVEL 1 - THE 0x91 CHAIN RECORD not to scale chain framing 13 B configuration blob 1,797 B len2 4 B 0x07BD7F4 0x07BD801 0x07BDF06 0x07BDF0A +0x000 +0x00D +0x712 +0x716 LEVEL 2 - THE BLOB not to scale magic 0xFEFF count u32 = 46 entry 0 language entries 1 to 45 1,757 B 0x07BD801 0x07BD805 0x07BD809 0x07BD829 0x07BDF06 +0x000 +0x004 +0x008 +0x028 +0x705 LEVEL 3 - ONE ENTRY, HERE language = "japanese" type u32 = 0, strings index u32 = 0 keylen u16 = 8 key 8 obfuscated bytes count u32 = 1 len u16 = 8 value "japanese" 0x07BD809 0x07BD80D 0x07BD811 0x07BD813 0x07BD81B 0x07BD81F 0x07BD821 0x07BD829 +0x000 +0x004 +0x008 +0x00A +0x012 +0x016 +0x018 +0x020

Figure 15 The configuration blob. Keys are ASCII names XORed with a fixed positional mask; values are typed and counted. Both banks parse to the exact byte under this reading.

+0x000magicUint4B0x0000FEFF [V]
+0x004countUint4Bnumber of entries [V]
then count entries, each:
+0x000typeUint4B0 = strings, 1 = float64, 2 = int32 [V]
+0x004indexUint4B0-based, sequential; redundant with position [V]
+0x008keylenUint2B8 to 31 bytes [V]
+0x00Akey[keylen] UCharobfuscated ASCII, see below [V]
+0x00A+kcountUint4Bnumber of values, may be 0 [V]
+0x00E+kvalues[count]type 0: [u16 len][bytes] each; type 1: 8 B each; type 2: 4 B each

[V] Under this reading both blobs parse to the exact byte: 46 entries filling 1,797 bytes and 52 filling 2,131, with nothing left over. [V] Entries 0–20 are byte-identical between the banks; the blobs first diverge at byte 810, inside the value of model_duration, which is that bank's own section key. (Version 6 stated that the first 1,190 bytes are identical; the count word at +0x004 already differs.)

14.1 The keys are ASCII under a fixed XOR mask

[V] The key bytes are not hashes. Each is the plain name of the property XORed with a fixed, position-indexed mask — byte i of the key is XORed with mask byte i. The mask was recovered from two keys whose plaintext is known independently (language and phoneset, whose values are the same strings the chain stores under .language and .phoneset), and then extended by requiring every other key in both banks to decode to [a-z0-9_]. The fit is exact: 31 mask positions, zero conflicts across all 98 keys in the two banks, and Teto's six keys that GUMI does not have decode cleanly under a mask solved without them.

mask[0..30] = e0 99 5e 3f 0c 55 6a 5b f8 d1 36 37 a4 0d c2 d3 10 09 0e 2f 3c c5 1a 4b 28 41 e6 27 d4 7d 72

name[i] = key[i] ^ mask[i]

The mask covers the longest key observed, 31 bytes. Whether it continues beyond that, and whether it is a constant table or generated, is not established here.

14.2 The blob, decoded

#typekeyvalues
0stringslanguage"japanese"
1stringsphoneset"romaji"
2float64voc_thop240, 48000
3float64voc_thop_output0.005
4float64voc_maxnhar120
5float64voc_maxnhar_e5
6float64voc_npsd256
7float64voc_fnyq24000
8float64voc_nchannel4
9float64voc_chanfreq3000, 6000, 10000
10float64voc_nspec1025
11float64voc_lipradius1.5
12float64voc_order_spec64
13float64voc_order_bap5
14float64voc_adjust_rd_below0
15float64voc_adjust_rd_above0
16float64voc_default_formant_scale1
17float64dur_offset_svwl0
18stringsext_phoneme"cl", "stop"
19stringsext_phoneme"br", "breath"
20stringsmodel_duration_arch"gen2a"
21stringsmodel_duration<29-byte section key>
22stringsmodel_timbre_arch"dds"
23stringsmodel_timbre_ae""
24stringsmodel_timbre_pred<29-byte section key>
25stringsmodel_timbre_ar""
26stringsmodel_vocoder_arch"nhv"
27stringsmodel_vocoder<29-byte section key>
28float64silence_to_cl_thrd0.1
29float64tm_base_temp1
30float64tm_extra_temp2
31float64vm_temperature_voicednone
32float64vm_temperature_unvoicednone
33float64vm_clipping_voicednone
34float64vm_clipping_unvoicednone
35float64vm_ringing_suppression_voicednone
36float64vm_ringing_suppression_unvoicednone
37float64vm_impulse_removal_sensitivity0.33
38stringsvm_voicing_decision"vuv"
39int32lcod_smoother_enabled [Teto only]1
40float64lcod_smoother_context [Teto only]5
41float64lcod_smoother_base_thrd [Teto only]2
42float64lcod_smoother_max_thrd [Teto only]3
43float64lcod_smoother_knee [Teto only]-4
44float64lcod_smoother_unvoiced_filter [Teto only]0, -0.5, 5, -0.5, 15, -0.5, 25, 0, 64, 0
45float64pv_peak_suppression_bw10
46float64pv_peak_suppression_thrd0.35
47float64pv_pre_gain_upper_bound3
48float64pv_pre_gain_lower_bound-6
49float64pv_window_size_factor1
50float64pv_eq_residual_voicednone
51float64pv_eq_harmonicnone

[V] The doubles are self-consistent as a DSP front-end configuration: a 240-sample hop at 48 kHz is 200 frames per second and 0.005 s exactly; 24000 is the Nyquist rate; 1025 is 2048/2 + 1 spectrum bins; and the four vocoder channels have the three band edges 3000, 6000 and 10000 Hz. These are the analysis parameters of the acoustic front end, which is why they are stored once, outside the sections, rather than per model.

[V] Three entries carry a section's 29-byte binary chain key as their value. That is how the engine resolves a section by role rather than by scanning, and it settles what sections 1, 2 and 3 are: model_duration, model_timbre_pred and model_vocoder, with architecture tags gen2a, dds and nhv. [I] nhv is the usual abbreviation for a neural homomorphic vocoder, which is consistent with the voc_* parameter set: harmonic count, noise-band count and band edges, spectral and band-aperiodicity orders.

[V] Teto adds six lcod_smoother_* entries that GUMI does not have, which is the whole of the 334-byte difference in blob size. The blob is therefore versioned by content, not by a version word.

15. Backbone generations and model digests

[V] The backbone digest must cover sections 0 to 2 only. GUMI v101 and Teto v104 have byte-identical int16 payloads in sections 0, 1 and 2, and different ones in section 3. Hashing all four sections therefore splits two banks that share a backbone.

SHA-256 over the int16 payloads of sections 0, 1 and 2, in record order:
a0ffb7ca19b0dc2cbc42137fa2c92abfd88111049f5abf4e7f0d12602cd54df0
    - identical for GUMI AI v101 and Kasane Teto v104

the same over all four sections:
96e514b46fcb3a1f66f269a8adee86356ef2fbbbab84c0b9f4ae140fb67df0fd   GUMI AI v101
555ca4478ac6cc6c3c75167520857a6c1c0d84b3b6f2c0ace542b027a746d24f   Kasane Teto v104

[V] Section 3 tracks the declared version rather than the singer, and an independent editor names its content hash sing_model: MD5 over section 3's value bytes reproduces the editor's digest exactly, on both banks, independently.

bankMD5 of section 3's value
GUMI AI v1013f649ae6cb04ee4f7e9a7ed72ee29928
Kasane Teto v104b0dbc93ad9636601a8a24425788a884e

[V] The other three digests are not content hashes of this file. base_model, timing_model and f0_model are identical across two banks whose named sections demonstrably differ — 23 payloads differ in section 2, 3 in section 1 and 29 in section 0, including the identity vectors themselves. A sweep of 128 canonicalisations per section over both banks found only sing_model. [I] They identify the shared pretrained backbone and are assigned outside the file.

Of the 29 differing payloads in section 0, all but record 13 are quantised tensors whose scales and int16 arrays are identical and whose only difference is the inert v4 word; the same holds in sections 1 and 2 apart from the per-voice records. That is why the int16-only digest agrees while a whole-payload digest would not.

16. The trailer blobs

[V] The file does contain opaque high-entropy data. Two chain records at the very end of the file hold 256 bytes each.

keysizeentropy, GUMIentropy, Tetobehaviour across the two banks
7f7f7f7f256 B7.18 bits/B7.11 bits/Bper-bank; 254 of 256 bytes differ
7e7f7f7f256 B7.11 bits/B7.11 bits/Bbyte-identical, across two different vendors

256 bytes is the RSA-2048 modulus size, and the entropy is what you would expect of either a signature or a ciphertext. Tested as RSA with exponents 3, 17 and 65537 in both byte orders, no PKCS#1 structure appeared. [U] What reads them, and whether anything does, is not established here. They are documented and left alone: this reference describes layout, and a licence gate is outside its scope.

17. Behaviour under modification

changeresultgrade
Zero all 10,912,640 int16 weights of section 2loads and renders; output becomes static[V*]
Transplant the three identity vectors between banksthe perceived singer changes; weights untouched[V*]
Transplant differing type tags outside section 2no audible or structural change; the tags carry no information the parser uses[V*]
Requantise a tensor from its own dequantised valuesbit-identical output, maximum error zero over all 437[V]
Rewrite a metadata string of a different lengthrequires fixing the chain length words at both ends and the index offsets of every later record[V]
Add or remove a configuration-blob entryrequires fixing the blob's count word, every later entry's index word, the chain length words and the index offsets[V]

Rows marked [V*] were established by rendering audio in earlier work on this format and have not been re-tested here.

18. Reconciliation with the original documentation

ATroubledSnake's document is the earliest description of this format that I have seen. Reading it against the bytes is worth doing: it is right about several things that are easy to miss, and wrong in one place in a way that propagates into an entire section of invented architecture.

18.1 Right, and adopted here

claimstatus
meta[1] is the number of prim tensors following a modlcorrect and important. It is a child count, and it is what makes the record stream a tree
_ncw and _gnc carry in-channels, out-channels and an extra wordcorrect; field order confirmed, values differ because the example voice is a different model
_stb is a style bank and holds a style countcorrect one level up; the count is payload word 0 and the record parents the data
Dilations run 1, 3, 9, 27, 81 in five parallel paths; this is an MRF blockthe cycle is confirmed exactly, with counts 8 / 7 / 7 / 7 / 4
Layer type codes 48 linear, 49 convolution, 52 embedding, 50 vectorconfirmed for section 2, where these are the only values type_code takes
_voc holds a sample rate of 48000corroborated from a different place — voc_thop in the configuration blob
The u32 at +0x004 of the header is 10true in both banks; the original reports it across many more voices
.svpk is a ZIP holding voice.nofs plus assetsnot testable here; adopted as context in section 1

18.2 Wrong

claimcorrection
prim payloads are raw float32 weightsthey are int16 quantised with a float32 scale per row. Section 9.3 shows the original's own 66,584-byte example decoding as 256 × 128 int16
Conv weights factor as 73 × 76 × 3, with a 52-channel skip pathan artefact of the above. 16,644 is 66,576/4, a byte count, not an element count
The header is 12 bytes and total_size is u32the header reserves 0x1000, the size is u64 at +0x008, and a 22-entry hash index sits at +0x016
Section boundaries are found by scanning for the next markerthe size field at +0x010 is authoritative; scanning also produces false positives
_psv holds per-phoneme conditioning vectorsit holds the phoneme dictionaries as plain text; see section 12
v4 is the kernel sizekernel size is the first word of the modl payload. v4 is inert and is not a function of the tensor
v3 and v4 are matrix dimensions for type 48v3 is 16 on all 437 tensors in both banks; dimensions are the rows and cols words after the scale array

The two documents were written against different voice generations, which explains some of the divergence but not all of it.

19. Corrections to version 6

Version 7 re-derived every number in this document from the two banks using a parser written from the prose of version 6 alone, deliberately not sharing code with the one that produced it. The great majority of version 6 was confirmed byte for byte. What follows is everything that was not, with the correction and how it was established. Nineteen items; the first eight are substantive, the rest are labelling.

19.1 Clipping statistics — was §8

Version 6 said: “32.4% of rows reach full scale, not the ~55% sometimes quoted”, with 59.7% reaching 30,000 and 95.8% reaching 16,384.

Correction: The three figures are 51.45%, 68.85% and 96.37%. Version 6's numbers are reproduced to two decimals by computing abs() on an int16 array, where abs(-32768) overflows and is dropped by the maximum; -32768 occurs 54,366 times. The ~55% version 6 rejected is close to right, and is 55.22% for section 2 alone. See §9.1.

19.2 The byte budget — was §2

Version 6 said: A ten-row table said to sum to the file size “exactly, with no remainder”.

Correction: It summed to 40,536,070, sixteen bytes short: the gap between data_offset and the start of the chain had no row. The table in §2.1 now has that row and is asserted to sum to the file size by the build.

19.3 Metadata band count — was Figure 1 caption

Version 6 said: “the four metadata bands… total 7,059 bytes between them”.

Correction: There are three: 4,560, 1,947, 552 bytes, which is where the 7,059 came from.

19.4 How the banks differ — was Figure 1 caption

Version 6 said: “Teto differs by 335 bytes of metadata and by one byte inside section 2”, which totals 336.

Correction: 335 is the whole difference. It is +4 on .name, −5 on .vendor, +1 on .timbre_styles, +334 on the configuration blob, and +1 inside section 2.

19.5 Configuration blob layout — was §13, Figure 15

Version 6 said: Entry = keylen u16, key of 8 or 15 binary bytes, type u32, value as a UTF-8 string or a float64.

Correction: Entry = type u32, index u32, keylen u16, key, value count u32, then that many values. Keys run 8 to 31 bytes. There is a third type, int32. Both blobs now parse to the exact byte. See §14.

19.6 Configuration blob keys — was §13

Version 6 said: Keys described as binary hashes, and only the string values were readable.

Correction: Keys are plain ASCII names XORed with a fixed 31-byte positional mask. The whole blob is decoded in §14.2, which also names sections 1–3 and their architectures.

19.7 Configuration blob, shared prefix — was §13

Version 6 said: “The doubles in the first 1,190 bytes are byte-identical between the two banks.”

Correction: They diverge at byte 810; the count word at +0x004 differs immediately. Entries 0–20 are identical, which is the claim that survives.

19.8 The multiphone spare slot — was §11

Version 6 said: Table 2 has 108 entries and “the additional one, at index 0, is the empty string”, so the inventory starts at 1.

Correction: Tables 0 and 2 agree on all 107 entries starting at index 0; the empty string is appended at index 107. The inventory is 0–106.

19.9 Count of 66,584-byte payloads — was §8.2

Version 6 said: “there are 46 of them in GUMI”.

Correction: There are 111: 60 in section 0 and 51 in section 2, all 256 × 128.

19.10 Padding and dilation — was §12

Version 6 said: “Padding always equals dilation.”

Correction: True for the 33 kernel-3 convolutions only. The 96 pointwise layers have padding 0 and dilation 1. The general invariant is padding = dilation × (kernel-1)/2.

19.11 _ftm payload length — was §7 figure

Version 6 said: Payload given as six words, (108, 1, 71, 2, 66, 0).

Correction: It is eight words; word 6 is 0x3BA3D70A, which as float32 is 0.005, and word 7 is 0.

19.12 Block B sub-block count — was §12.1

Version 6 said: Block B listed as 12 sub-blocks, all 128 → 128.

Correction: There are 14, plus the 4 → 128 adapter. The four blocks then account for all 32 _gnc records.

19.13 Table 2 prefixes — was §11

Version 6 said: Prefixes given as ARP_, ROM_ and ES_.

Correction: There is a fourth, YUE_, in the Cantonese and multiphone dictionaries.

19.14 ASCII tag counts — was §6.3

Version 6 said: One column of ASCII-tag counts presented as holding for both banks.

Correction: The counts are bank-specific outside section 2: [13, 2, 570, 4] for GUMI and [14, 5, 570, 9] for Teto. §7.3 now prints both.

19.15 Figure 8, level 3 — was figure label

Version 6 said: The payload's end labelled 0x1024194 while the relative offset beside it read +0x10418 and level 2 of the same figure read 0x1024394.

Correction: 0x1024394. Absolute and relative rows are now generated from one number.

19.16 Figure 13, level 2 — was figure label

Version 6 said: The last _psv record ending at 0x0BE0CD0.

Correction: 0x0BE0CE0, 16 bytes further on.

19.17 Section roles — was §5 table

Version 6 said: Sections 0 and 3 graded [I], inferred.

Correction: Both are now [V]: the configuration blob names sections 1–3 by their binary keys, and section 0's chain key is the ASCII f0model-dds.

19.18 Duplicate section number — was §10.2

Version 6 said: Two subsections numbered 10.2 — Pitch and Vocal modes.

Correction: Renumbered; vocal modes is now §11.5.

19.19 Figure vocabulary — was throughout

Version 6 said: header meaning both a 4,560-byte band and a 4,096-byte region; config blob meaning both a 1,947-byte band and a 1,797-byte value; offsets given as record starts in one place and payload starts in another; the section-2 map jumping from the container box to record 4 with records 0–3 absorbed silently.

Correction: §3 fixes the vocabulary and states the figure rules. The generator now refuses to emit a level whose boxes do not tile its span, so an elided run must be drawn and labelled.

20. Verification

Run these against any bank. Each is a one-line consequence of something above, and each failure localises to a specific claim. The companion script nofs_verify7.py performs all of them.

#checkexpected
1u64 at +0x008 equals the file sizeexact
2the chain from 0x1010 tiles to EOFno gap, no slack
3FNV-1a over signed char resolves every chain key in the index22 of 22
4every section walks to its exact end using offset + 20 + size0 bytes left
5payload size == 16 + n*4 + 8 + rows*cols*2 on quantised records437 of 437
6requantising a tensor from its dequantised valuesmaximum error 0
7walking section 2 by child counts from the root count570 of 570 records
8record 562 is _stb and names n stylesn vectors follow in that order
9SHA-256 over sections 0–2 int16 payloadsequal for banks of one generation
10MD5 over section 3's value bytesequals the editor's sing_model
11the configuration blob parses to its last byte under §140 bytes left, count word honoured
12every configuration key decodes to [a-z0-9_] under the §14.1 maskall keys, both banks
13multiphone tables 0 and 2 agree entry for entry107 of 107, extra slot last
14padding == dilation * (kernel-1) / 2 on every modl payload129 of 129
15the byte-budget rows sum to the file sizeexact, no remainder

21. Open questions

#questionstatus
1What is v4?narrowed. Proven not to be a function of the tensor; 45 identical tensors carry different values. Likely an encoder-side counter
2What does the timing vector do?open. Never transplanted alone. Experiment C
3What do the per-style scalars mean?open. Ordering is settled, meaning is not. Experiment D
4Are the three shared digests content hashes?closed, negative. They identify the backbone and are assigned outside the file
5Why do stored styles not match .timbre_styles?closed. _stb gives the order and parents the data; the metadata string is display only
6Do all banks have the same five 132-byte records in section 0?open. Both banks examined here do; a wider survey has reported banks that do not
7Are type tags meaningful?closed. Only in section 2, where they are also stable across banks
8What is in the configuration blob?closed. A typed property list of DSP front-end and model-routing parameters, fully decoded in §14
9What are the two 256-byte trailer blobs?open. One per-bank, one constant across vendors. Experiment F
10What is the header's constant 10?open. Constant here and in the original's much larger sample
11Why is the arity word scrambled outside section 2?open. Marker and size stay valid while tag, type and arity do not, which looks deliberate
12What is the extra word on _ncw?narrowed. 64 is voc_order_spec and 4 is voc_nchannel; 388 and 0 unexplained
13What does cmpg's child count mean?open. It is 15 where 30 layers follow and 9 where 18 do, so it counts something one level up
14Does the key mask continue past 31 bytes?new, open. No key in either bank is longer, so positions 31 and beyond are untested

22. Experiments that would settle the remaining questions

Each needs the shipping editor and a bank the tester already owns. All are non-destructive: work on a copy, keep .name and .vendor intact, and every one preserves file length and record count.

#experimentwhat it decides
ASwap two style vectors in place — exchange the 128 data bytes of records 565 and 566 — leaving 562 and 569 alone. Render the same phrase under each style name.Confirms behaviourally what section 8 shows structurally
BReorder the names inside record 562 without touching any vector, keeping the byte length identical by swapping two equal-length names.Separates ‘the file states its order’ from ‘the engine reads that statement’
CTransplant the timing vector alone — section 1 record 80, 512 data bytes.Open question 2. Listen for phoneme duration and articulation with timbre held constant
DZero the four floats of record 569, then set them equal, then exaggerate one.Establishes what the per-style scalars do — most likely an intensity or blend weight
ESet v4 in one quantised record to 200.Confirms from the engine side what the byte diff implies: that v4 is inert
FOverwrite one byte of the 7f7f7f7f trailer blob and load the bank.Tells us whether anything reads it
GChange the child count on one modl from 2 to 3 and load.Tests whether the engine walks the tree by arity or by tag order
HChange voc_default_formant_scale in the configuration blob from 1.0 to 1.2, fixing only the eight bytes of the double.New. The first test of whether the blob is read at all, at no risk to length or record count

Experiment F is a read-only probe of whether a field is checked, not an attempt to satisfy the check; a bank whose trailer has been corrupted is less usable, not more. If it turns out to be enforced, the right conclusion is that the ‘no integrity check’ sentence must be withdrawn rather than worked around.

23. Reference implementation

toolpurpose
nofs_verify7.pyIndependent parser and the section 20 checklist, including the configuration blob and the key mask. Written from the format description without importing the others
nofs_extract.pyParse a bank, dequantise every tensor, emit a PyTorch .pt plus a JSON record index
nofs_repack.pyEdit metadata with offset fixups, transplant tags, scale weights, graft records or a whole voice
nofs_survey.pyWalk an installation, extract every bank's metadata and vectors, group by backbone
nofs_arch.pyShape-faithful PyTorch skeleton of section 2

Known pitfalls in existing tools. nofs_survey.py reads the style order from .timbre_styles and will mislabel every style it reports; it should read record 562. Its identity_vectors helper indexes section 0 record 13 and section 2 records 25 and 564 by position within a filtered list rather than by record index, which is correct for these banks but silently wrong for any bank whose 132-byte record set differs — open question 6 is exactly that case. Any tool that groups banks by hashing int16 payloads should hash sections 0 to 2 only. Any parser that treats the word at +0x0C as padding should read it as a child count. Any code that measures clipping should take abs() in a width wider than int16, or compare against -32768 directly; see §9.1.

24. Scope, and credit

This is format documentation, produced by examining files on a machine where every bank was licensed and activated. It describes how the container is laid out and how the numbers inside it are encoded. It did not require, and does not describe, any circumvention of a protection measure. Nothing here enables redistribution of a voicebank, and the per-voice arrays that carry identity are useless without the 40 MB of shared backbone that the vendor licenses.

The XOR mask in §14.1 obfuscates parameter names, not content: the values it guards are sample rates and filter orders, and undoing it makes the format documentable rather than making anything accessible that was not already. The two 256-byte trailer blobs, which are the only plausible licence-related data in the file, are documented as observations and deliberately not pursued. Experiment F asks only whether a field is checked.

Credit. The first description of this format was written by ATroubledSnake, who reverse-engineered the DNNI marker layer, the section name glossary, the layer type codes, the channel-width records and the record arity word, and published it openly. Sections 7.2 and 13 exist because of that work. The listening tests marked [V*], the twenty-five-bank survey quoted for population statistics, and the identity-transplant result on which section 11 rests all come from earlier work on this format by others.

25. Tag glossary

The 4-byte tag at +0x004 of every record. Only section 2 uses ASCII tags; elsewhere the field is scrambled. Counts are for GUMI AI v101 and are identical in Teto.

tagnkindmeaning
prim355leafa weight tensor or a float32 array. type_code 52 marks a weight matrix, 49 a bias or plain vector, 50 a table, 48 once
modl136descriptorone network layer. Child count is the number of tensors that follow; payload is (kernel, stride, padding, dilation, groups)
_gnc32structuralsub-block channel widths, (in, out, extra)
cmpg14structuralcompute group, one level above modm
moda8leafactivation marker. type_code 48, 50 or 55
_psv6string tablesa phoneme dictionary; see section 12
modm5structuralmodule boundary, groups layers and activations
_ncw4structuralblock channel widths, (in, out, extra)
cmpu3structuralcompute unit, groups two weights and a bias
_rld2structuralthe two outermost nodes of the section 2 tree
_ftm1descriptorfeature transform; eight words, first is 108
_dct1structuralparents two 16,392-byte tables
_vqc1structuralparents the encoder stack
_did1descriptorpayload (4, 64, 64.0f)
_stb1string tablestyle names, and parent of the style subtree

[S] The original documentation lists further tags — _vwr, _voc, _ddf, _f0d, _ff0, _pdc, _dur, _pfc, _bc1, _res — that do not appear in either bank examined here.

26. Quick reference

Everything a parser needs, on one page. Offsets are from the start of the structure named.

_NOFS_HEADER — section 4

+0x000magicUint4B0x0000F580
+0x004unknownUint4B10
+0x008total_sizeUint8Bfile size
+0x010data_offsetUint4B0x1000
+0x014unknownUint2B0x1000
+0x016index[22]_IDXu32 FNV-1a signed, u64 chain-record offset
+0x1000gap[16] UChartwelve zeros, then u32 0x1000; the chain starts after it

_NOFS_CHAIN_RECORD — chain starts at 0x1010, tiles to EOF — section 5

+0x000lenUint4Bwhole record, both length words included
+0x004typeUint2B1
+0x006keylenUint2B
+0x008key[keylen] UChar
+0x008+kvallenUint4B
+0x00C+kvalue[vallen] UCharstring, blob, or a DNNI section
+len-4len2Uint4Bthe length again

_DNNI_RECORD — section 7

+0x000markerUint4B0x7FCA00FF hdr / 40FF data / 41FF meta
+0x004tag[4] UCharASCII in section 2 only
+0x008type_codeUint4Blayer type in section 2
+0x00CchildrenUint4Brecords nested under this one
+0x010sizeUint4Bpayload bytes
+0x014payload[size] UCharnext record is at +0x014 + size

_DNNI_QUANT_PAYLOAD — section 9

+0x000v3Uint4B16
+0x004v4Uint4Binert
+0x008reservedUint4B0
+0x00CnUint4B= rows
+0x010scale[n] Float
+0x010+4nrowsUint4B
+0x014+4ncolsUint4B
+0x018+4nq[rows*cols] Int2BW[r,c] = q[r,c] * scale[r] / 32767, q in -32768..32767

_NOFS_CONFIG_ENTRY — inside the 0x91 value — section 14

+0x000typeUint4B0 strings, 1 float64, 2 int32
+0x004indexUint4B0-based, sequential
+0x008keylenUint2B8..31
+0x00Akey[keylen] UCharname[i] = key[i] ^ mask[i]
+0x00A+kcountUint4Bnumber of values
+0x00E+kvalues[count]strings are [u16 len][bytes]
wantlook at
the singersection 0 rec 13, section 2 rec 25, section 2 rec 564 — 396 bytes of payload, §11.1
timingsection 1 rec 80, 128 floats
vocal modessection 2 rec 562 for the names, 565–568 for the vectors, 569 for the scalars, in that order
the phoneme setsection 2 rec 4 to 9
the backbone identitySHA-256 of the int16 payloads of sections 0 to 2
the architecturewalk section 2 by child counts from the root count
front-end parametersthe 0x91 chain record, decoded per §14