Jacob Vosmaer's blog

The DWVW audio compression format

2025-07-19

I got curious about DWVW, the lossless audio compression format of the Typhoon operating system on my Yamaha TX16W. I wrote a small program that can convert between DWVW and AIFF audio files.

DWVW is a cool Typhoon feature

The TX16W stores audio data on 720KB floppy disks. Floppy disks are slow. You find out about that because you have to re-load all your sounds from floppy every time you turn on the TX16W. One of the improvements brought to the machine by the Typhoon operating system was lossless compression for audio data. It typically achieves compression ratios of around 50%.

The compression uses a custom codec called DWVW which seems to stand for "Delta With Variable Width". The files themselves are AIFF-C files which are a container format for audio data.

The DWVW codec was created by the Typhoon developers and Typhoon / Cyclone is the only machine that uses it to my knowledge. Why did they create their own format? I don't know.

Why write a converter?

There is not a lot of software that I know that can read these compressed files so I thought it would be fun to make my own program that implements the compression. Besides it being fun, it is also useful when I want to transfer audio files from my computer to the TX16W. If I compress the audio files before copying them onto (virtual) floppies then I need fewer floppies.

Only now while I'm writing this blog post did I figure out there is an open source DWVW implementation in libsndfile. But it was more fun anyway to write my own. I don't know much about compression and this was a nice way to learn more about it.

How DWVW works

Digital audio is a stream of numbers which we usually call "samples". The simplest way to represent that on the computer is as a big array of fixed width numbers. This lets you store an arbitrary sequence of numbers. But audio is not arbitrary, it is constrained by the physics of how speakers and microphones work. The main idea behind DWVW is that the difference between adjacent samples in an audio stream is often much smaller than the maximum range of the signal. This is because the diaphragm of a microphone or speaker can only travel so far in the small period between two samples.

So, the difference between adjacent audio samples (the "sample delta") is usually small. If you now use a variable width encoding for numbers, where smaller numbers use less space, then you can store audio data in less space than the naive fixed width approach.

Let's say we're working with 16-bit audio. That means the audio is a stream of 16-bit numbers (samples). The normal way to represent that is with 2 bytes per sample. The width is the same every time so we only store the bits of the numbers themselves. DWVW on the other hand stores the numbers in two parts: a width followed by a sample delta. Each sample delta potentially has a different width so DWVW has to write down the width for each sample delta. This uses more space than the normal encoding, not less, because an array of fixed width numbers does not have to repeat the width each time!

DWVW still ends up saving space because the sample deltas are smaller than the normal numbers. On top of that, the widths are also delta encoded. So instead of storing the width of each sample delta in the stream, DWVW stores how much the width of the current sample delta has changed from that of the last sample delta.

As an example let's look at this 1kHz sine wave.

A 1kHz sine wave in an audio editor

The first nine samples are:

Sample 287 4255 8549 12341 15845 18660 20931 22349 23079

As sample delta / width pairs, they become:

Sample delta 287 3968 4294 3792 3504 2815 2271 1418 730
Width 9 12 13 12 12 12 12 11 10

The width of 287 is 9 because it's greater than 255 (the largest 8-bit number) and less than 512 (the smallest 10-bit number).

If we take the deltas of the widths we get:

Sample delta 287 3968 4294 3792 3504 2815 2271 1418 730
Width delta 9 3 1 -1 0 0 0 -1 -1

Now let's encode these numbers with DWVW. DWVW consists of (width delta, sample delta) pairs. The width and sample start at 0. The width delta is allowed to range from -8 to 8 but our first value is 9. We encode this as -1 which is then taken modulo 16. That works out because -1 mod 16 = 9. The value -1 is split into its absoute value (1) and its sign (-). The binary encoding for the absolute value is 01: one zero for the value one, and a stop bit. Then we get a sign bit where 0 means positive and 1 means negative. So for us that is 01 1. Next comes the sample delta. The binary representation of 287 is 100011111. The first binary digit will always be 1 so DWVW does not store it. We now get: 01 1 00011111. And then finally we get a sign bit for the sample delta. The number 287 is positive so we get sign bit 0. The final result is 01 1 00011111 0 which is only 12 bits long, not 16.

Width deltaWidth delta signSample deltaSample delta sign
01 1 00011111 0

Next we get sample delta 3968 and width delta 3. The width delta is encoded as 0001 0 (three zeroes for absolute value 3, a stop bit one, and a zero sign bit because 3 is positive). And the sample delta is 11110000000 0. The final result is 0001 0 11110000000 0. This is 17 bits so the compression is not compressing here! But don't worry, on average it usually will compress.

Let's continue in a table.

SampleSample deltaWidthWidth deltaDWVW binary encoding
287 287 9 9 01 1 00011111 0
4255 3968 12 3 0001 0 11110000000 0
8549 4294 13 1 01 0 000011000110 0
12341 3792 12 -1 01 1 11011010000 0
15845 3504 12 0 1 10110110000 0
18660 2815 12 0 1 01011111111 0
20931 2271 12 0 1 00011011111 0
22349 1418 11 -1 11 1 0110001010 0
23079 730 10 -1 11 1 011011010 0

Note how DWVW omits the sign bit of the width delta when it is zero: all that is left is a stop bit. Also note that the samples themselves get larger and larger but the sample deltas do not. And the width deltas are pretty small.

This data did not compress all that well, we used 125 bits for 9 16-bit samples which would have otherwise cost us 9*16=144 bits of storage. A compression ratio of 87%. You'll have to take my word for it that typical compression rates are better in practice. (Although you could convince yourself by compressing audio with my dwvw utility. :) )

There are some special cases in the enoding that we have not encountered in the example. They are discussed in this description of the format.

Something that was not clear to me from the format documentation is how to encode sample deltas with a width of 16 bits. Observe that the encoded width is a number modulo 16 so the largest width we can encode is 15. What if the difference between two samples is a 16-bit number? DWVW solves this by also wrapping around the sample deltas. If the sample delta exceeds the largest postive or negative 15-bit value then DWVW first subtracts or adds 1<<16 (so 65536) to obtain a narrower number. When decoding, any values that exceed the signed 16-bit integer range wrap around again.

Development process

The development of my dwvw utility went in three phases:

  1. Decompress raw DWVW data
  2. Convert DWVW AIFF-C to plain AIFF
  3. Implement compression from AIFF to DWVW

I started off with a known good DWVW file from the TX16W. I used hexdump to see where the audio data started and I extracted it with dd. This gave me a file of raw DWVW data. I then wrote a program that converted that raw DWVW data into raw big-endian signed 16-bit PCM. I put a SoX play command in my compile/test setup so every time a new version of the code compiled I could hear if I was getting there. When you get audio transcoding wrong you usually hear static. :)

Once I thought I had decoding DWVW working (I later found out there was a lot I did not understand yet) I embedded the decoder in a larger program that handles AIFF files. This way I did not have to worry about the technicalities of the AIFF format right from the start.

The final phase was implementing compression. Because playing back a compressed DWVW file is cumbersome (I need to load into Cyclone or the TX16W first), I this time relied on checking if I could reproduce an original DWVW file bit-for-bit: I had an md5 command in my compile-test feedback loop.

A look at the encoder

Let's take a look at the encoder I wrote in C. We start with a macro bit to create values like 1<<15.

#define bit(shift) ((int64_t)1 << (shift))

Next we calculate the current sample delta, and we wrap it around if it is too big to be a DWVW delta.

sampledelta = sample - lastsample;
lastsample = sample;

if (sampledelta >= bit(outwordsize - 1))
  sampledelta -= bit(outwordsize);
else if (sampledelta < -bit(outwordsize - 1))
  sampledelta += bit(outwordsize);

Now we can calulate the delta width. This again wraps around. The variable outwordsize is the bit depth for the DWVW data we are generating (so 16 in the examples above).

sampledeltasign = sampledelta < 0;
sampledelta = sampledeltasign ? -sampledelta : sampledelta;

for (width = 0; (1 << width) <= sampledelta; width++)
  ;

widthdelta = width - lastwidth;
lastwidth = width;

if (widthdelta > outwordsize / 2)
  widthdelta -= outwordsize;
else if (widthdelta < -outwordsize / 2)
  widthdelta += outwordsize;

Now that we know the wrapped-around value of the width delta we can write it to memory with the putbit helper. Note how the stopbit is omitted if the width delta has the maximum length, and how the sign bit is omitted if the width delta is zero.

widthdeltasign = widthdelta < 0;
widthdelta = widthdeltasign ? -widthdelta : widthdelta;

for (i = 0; i < widthdelta; i++) /* Store widthdelta in unary */
  putbit(&bw, 0);
if (widthdelta < outwordsize / 2) /* Widthdelta stop bit */
  putbit(&bw, 1);
if (widthdelta)
  putbit(&bw, widthdeltasign);

With the width delta out of the way we can now write the sample data. Note how the for loop starts at 1, skipping the implied leading 1 of the sample delta. The sign bit is left out if the sample delta is 0.

for (i = 1; i < width; i++) /* Store sampledelta in binary */
  putbit(&bw, (sampledelta & bit(width - 1 - i)) > 0);

if (sampledelta)
  putbit(&bw, sampledeltasign);

/* Extra bit for otherwise unrepresentable value -(1 << (outwordsize - 1))
 */
if (sampledeltasign && sampledelta >= bit(outwordsize - 1) - 1)
  putbit(&bw, sampledelta == bit(outwordsize - 1));

You can read the rest on GitHub if you like!

Conclusion

I could go on for longer about this project but I think this is enough for now. Thanks for reading!

Tags: tx16w c yamaha

IndexContact