Skip to content

Compressed Delta (`cMdT`) Format

Binary container for regularly-sampled signed-integer multichannel signal data. A file is a fixed 28-byte header followed by an encoded samples block.

The samples block goes through two optional, independent transforms before storage:

  • Coding — per-sample delta encoding (1st or 2nd order) followed by zig-zag, mapping signed deltas onto small unsigned integers.
  • Compression — generic compression (Zstandard or zlib) over the coded bytes.

Both stages are selected per file via header fields.

All multi-byte fields are little-endian. The header is packed (no padding) and must be exactly 28 bytes:

Off Size Field C type Constraint
0 4 magic uint32 0x54644D63 (ASCII c M d T)
4 8 payload_size uint64 size of samples block in bytes
12 1 total_channels uint8 ≥ 1
13 4 total_samples uint32 ≥ 1 per channel; ≥ 2 when coding = 2
17 8 sample_rate float64 finite, > 0
25 1 bits_per_sample uint8 8, 16, 24, or 32
26 1 coding uint8 see Coding
27 1 compression uint8 see Compression

The samples block immediately follows the header and spans payload_size bytes.

Samples are stored channel-major (non-interleaved). After full decoding — reversing compression then reversing coding — the layout is:

c0[0] c0[1] ... c0[M-1] c1[0] c1[1] ... c1[M-1] ... c(N-1)[M-1]

where N = total_channels and M = total_samples.

Each sample is a signed integer of bits_per_sample bits, stored little-endian. 24-bit samples occupy three consecutive bytes; readers sign-extend from bit 23 when widening to 32 bits.

The fully-decoded payload size equals total_channels × total_samples × (bits_per_sample / 8) bytes.

The coding field selects per-sample preprocessing applied independently per channel before cross-channel concatenation.

Value Symbol Per-channel transform
0 TWOS_COMPLEMENT none — samples stored as raw signed ints
1 DELTA_ZIGZAG 1st-order delta, then zig-zag
2 DOUBLE_DELTA_ZIGZAG 2nd-order delta, then zig-zag

Storage is in bits_per_sample-wide slots in all three modes; the bit-width never changes.

Zig-zag maps signed integers onto unsigned integers placing small absolute values near zero. For an N-bit signed integer:

encode(n) = (n << 1) ^ (n >> (N - 1)) // N = 8/16/24/32, arithmetic right shift
decode(z) = (z >> 1) ^ -(z & 1) // z is unsigned, logical right shift

For a channel with raw samples x[0..M-1], the encoder produces deltas d[0..M-1]:

d[0] = x[0]
d[i] = x[i] - x[i-1] for i ≥ 1

Then writes y[i] = zigzag(d[i]) to the output slot for sample i. The first sample is the raw seed; subsequent samples are first-order differences.

Two raw seed samples, then second-order differences:

d[0] = x[0]
d[1] = x[1]
d[i] = (x[i] - x[i-1]) - (x[i-1] - x[i-2]) for i ≥ 2

Then writes y[i] = zigzag(d[i]) for each i. Note both d[0] and d[1] are raw samples — not first-order deltas — because the decoder needs two consecutive raw samples to seed the second-order recurrence. Consequently coding = 2 requires total_samples ≥ 2.

Sample arithmetic is in bits_per_sample-wide signed two’s-complement; overflow wraps modulo 2^bits_per_sample and the inverse transform on decode recovers the original x[i].

The compression field selects how the coded bytes are stored.

Value Symbol Storage
0 NO_COMPRESSION coded bytes stored verbatim
1 ZSTD coded bytes compressed with Zstandard
2 ZLIB coded bytes compressed with zlib (deflate)

For NO_COMPRESSION, payload_size equals the raw size total_channels × total_samples × (bits_per_sample / 8).

For ZSTD and ZLIB, payload_size is the compressor’s output size; the decompressor must yield exactly the raw size.

A conforming reader must reject inputs that violate any of the following:

  • Header reads less than 28 bytes.
  • magic != 0x54644D63.
  • bits_per_sample is not 8, 16, 24, or 32.
  • coding > 2.
  • compression > 2.
  • total_channels == 0.
  • total_samples == 0.
  • total_samples < 2 while coding is DOUBLE_DELTA_ZIGZAG.
  • sample_rate is NaN, ±Inf, or ≤ 0.
  • For NO_COMPRESSION: fewer bytes than the raw size remain after the header.
  • For ZSTD / ZLIB: fewer than payload_size bytes remain after the header.
  • For ZSTD: payload does not start with a valid Zstandard frame header.
  • For ZLIB: payload does not start with a valid zlib stream header.
  • For ZSTD / ZLIB: the decompressed samples block size differs from the raw size.

The validation order is at the reader’s discretion, but cheap checks (header size, magic, enum ranges) should run before stream-position-dependent checks (payload size, frame headers).

Single-call encoder for any valid combination of coding, compression, and bits_per_sample. Allocates the result on the heap; caller frees with free. Returns NULL on invalid arguments, compression failure, or allocation failure.

Compile: cc -std=c17 -O3 cmdt_encode.c -lzstd -lz -o cmdt_encode

#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <zstd.h>
#include <zlib.h>
#define CMDT_MAGIC 0x54644D63u
#define CMDT_CODING_TWOS 0
#define CMDT_CODING_DELTA 1
#define CMDT_CODING_DOUBLE 2
#define CMDT_COMPRESS_NONE 0
#define CMDT_COMPRESS_ZSTD 1
#define CMDT_COMPRESS_ZLIB 2
#pragma pack(push, 1)
typedef struct {
uint32_t magic;
uint64_t payload_size;
uint8_t total_channels;
uint32_t total_samples;
double sample_rate;
uint8_t bits_per_sample;
uint8_t coding;
uint8_t compression;
} CmdtHeader;
#pragma pack(pop)
// Unsigned sign-fill mask keeps every step fully defined; a sign-extended N-bit input yields an N-bit result
static inline uint32_t zigzag_encode(int32_t value) {
const uint32_t sign_mask = 0u - ((uint32_t)value >> 31);
return ((uint32_t)value << 1) ^ sign_mask;
}
// Reinterpret the low bits of value as a signed bits-wide integer (modular two's-complement)
static inline int32_t truncate_signed(uint32_t value, uint8_t bits) {
if (bits == 32) {
return (value & 0x80000000u) ? (int32_t)(value - 0x80000000u) + INT32_MIN : (int32_t)value;
}
const uint32_t sign_bit = 1u << (bits - 1);
const uint32_t low = value & ((1u << bits) - 1);
return (int32_t)(low ^ sign_bit) - (int32_t)sign_bit;
}
// Byte-wise little-endian access: no alignment, aliasing, or host byte-order assumptions
static int32_t read_sample(const uint8_t* src, uint32_t sample_index, uint8_t bits_per_sample) {
const size_t bytes_per_sample = bits_per_sample / 8u;
const uint8_t* sample_data = src + (size_t)sample_index * bytes_per_sample;
uint32_t raw = 0;
for (size_t byte_index = 0; byte_index < bytes_per_sample; ++byte_index) {
raw |= (uint32_t)sample_data[byte_index] << (8u * byte_index);
}
return truncate_signed(raw, bits_per_sample);
}
static void write_sample(uint8_t* dst, uint32_t sample_index, uint8_t bits_per_sample, uint32_t value) {
const size_t bytes_per_sample = bits_per_sample / 8u;
uint8_t* sample_data = dst + (size_t)sample_index * bytes_per_sample;
for (size_t byte_index = 0; byte_index < bytes_per_sample; ++byte_index) {
sample_data[byte_index] = (uint8_t)(value >> (8u * byte_index));
}
}
uint8_t* cmdt_encode(const void* input_samples,
uint8_t total_channels,
uint32_t total_samples,
double sample_rate,
uint8_t bits_per_sample,
uint8_t coding,
uint8_t compression,
size_t* output_size) {
if (!input_samples || !output_size || !total_channels || !total_samples) {
return NULL;
}
if (!isfinite(sample_rate) || sample_rate <= 0.0) {
return NULL;
}
if (bits_per_sample != 8 && bits_per_sample != 16 && bits_per_sample != 24 && bits_per_sample != 32) {
return NULL;
}
if (coding > CMDT_CODING_DOUBLE || compression > CMDT_COMPRESS_ZLIB) {
return NULL;
}
if (coding == CMDT_CODING_DOUBLE && total_samples < 2) {
return NULL;
}
const size_t bytes_per_sample = bits_per_sample / 8;
const size_t raw_size = (size_t)total_channels * total_samples * bytes_per_sample;
// Apply per-channel coding into a contiguous channel-major buffer
uint8_t* coded = (uint8_t*)malloc(raw_size);
if (!coded) {
return NULL;
}
for (uint8_t channel = 0; channel < total_channels; ++channel) {
const uint8_t* src = (const uint8_t*)input_samples + (size_t)channel * total_samples * bytes_per_sample;
uint8_t* dst = coded + (size_t)channel * total_samples * bytes_per_sample;
for (uint32_t sample_index = 0; sample_index < total_samples; ++sample_index) {
const int32_t x = read_sample(src, sample_index, bits_per_sample);
uint32_t delta = (uint32_t)x;
// Deltas stay in unsigned arithmetic, so wraparound is fully defined at every sample width
if (coding == CMDT_CODING_DELTA && sample_index >= 1) {
const int32_t x_prev = read_sample(src, sample_index - 1, bits_per_sample);
delta = (uint32_t)x - (uint32_t)x_prev;
} else if (coding == CMDT_CODING_DOUBLE && sample_index >= 2) {
const int32_t x_prev1 = read_sample(src, sample_index - 1, bits_per_sample);
const int32_t x_prev2 = read_sample(src, sample_index - 2, bits_per_sample);
delta = ((uint32_t)x - (uint32_t)x_prev1) - ((uint32_t)x_prev1 - (uint32_t)x_prev2);
}
// Wrap the delta to bits_per_sample width before zigzag
const int32_t d = truncate_signed(delta, bits_per_sample);
const uint32_t y = (coding == CMDT_CODING_TWOS) ? (uint32_t)d : zigzag_encode(d);
write_sample(dst, sample_index, bits_per_sample, y);
}
}
// Optionally compress the coded buffer
const uint8_t* final_data = coded;
size_t final_size = raw_size;
uint8_t* compressed_buffer = NULL;
if (compression == CMDT_COMPRESS_ZSTD) {
const size_t bound = ZSTD_compressBound(raw_size);
compressed_buffer = (uint8_t*)malloc(bound);
if (!compressed_buffer) {
free(coded);
return NULL;
}
const size_t result = ZSTD_compress(compressed_buffer, bound, coded, raw_size, 3);
if (ZSTD_isError(result)) {
free(compressed_buffer);
free(coded);
return NULL;
}
final_data = compressed_buffer;
final_size = result;
} else if (compression == CMDT_COMPRESS_ZLIB) {
uLong bound = compressBound((uLong)raw_size);
compressed_buffer = (uint8_t*)malloc(bound);
if (!compressed_buffer) {
free(coded);
return NULL;
}
if (compress(compressed_buffer, &bound, coded, (uLong)raw_size) != Z_OK) {
free(compressed_buffer);
free(coded);
return NULL;
}
final_data = compressed_buffer;
final_size = (size_t)bound;
}
// Build the final blob: header followed by samples block
const size_t blob_size = sizeof(CmdtHeader) + final_size;
uint8_t* blob = (uint8_t*)malloc(blob_size);
if (!blob) {
free(compressed_buffer);
free(coded);
return NULL;
}
CmdtHeader* header = (CmdtHeader*)blob;
header->magic = CMDT_MAGIC;
header->payload_size = final_size;
header->total_channels = total_channels;
header->total_samples = total_samples;
header->sample_rate = sample_rate;
header->bits_per_sample = bits_per_sample;
header->coding = coding;
header->compression = compression;
memcpy(blob + sizeof(CmdtHeader), final_data, final_size);
free(compressed_buffer);
free(coded);
*output_size = blob_size;
return blob;
}
  • The encoder stores the header in host byte order. On big-endian platforms the multi-byte header fields must be byte-swapped to little-endian before the bytes are written out. The samples block is assembled byte-wise, so it is little-endian on any host and needs no particular input alignment.
  • For brevity, this encoder reads each input sample twice when computing deltas. A production encoder would maintain prev1 / prev2 running variables to make the inner loop a single read per sample.