User Tools

Site Tools


dfpwm

DFPWM (Dynamic Filter Pulse Width Modulation) is an audio codec created by Ben “GreaseMonkey” Russell in 2012, originally to be used as a voice codec for asiekierka's pixmess, a C remake of 64pixels. It is a 1-bit-per-sample codec which uses a dynamic-strength one-pole low-pass filter as a predictor. Due to the fact that a raw DPFWM decoding creates a high-pitched whine, it is often followed by some post-processing filters to make the stream more listenable.

DFPWM is recognisable, but also creates a fair bit of noise. It is suitable for brostep, however.

It depends on the implementation as to what all the parameters are. Testing has shown that 8 bits per sample works better than 16 bits. The codec is not frequency-dependent, and can work at any frequency. It is a mono codec, but stereo can be achieved by running two streams in parallel.

If necessary, you can get away with converting your streams to raw 1-bit, or you could possibly assume a static-strength low-pass filter.

The implementation in Computronics handles streams using Ri=7, Rd=20, 8 bits per sample, and LSB stored first.

Specification

DFPWM, at its simplest, works like this:

Decoding:
  smp(t) <- Predictor(bit(t))
Encoding:
  if smp(t) > LastPredictor OR (smp(t) = LastPredictor = 127):
    bit(t) <- Predictor(1)
  else:
    bit(t) <- Predictor(0)

where bit is either LOW or HIGH, and smp ∈ [LOW, HIGH]. (Apologies for using the ∈, it's just that if I try to do “less than or equal”, dokuwiki gives me an “is implied by” symbol instead. –GM)

State

Firstly we need to give some types: * Let q,s be signed integers. * Let b' be a single bit, either 0 or 1. * Let Ri,Rd be chosen constant signed integers. ((7,20) is reasonable for 8 bits per sample.)

Then we need to assign meanings: * Let q be the “charge”, initialised to 0. * Let s be the “strength”, initialised to 1. * Let Ri be the strength increase. * Let Rd be the strength decrease.

Ri and Rd are constant (and, until someone discovers a better set of values, will always be 7 and 20 respectively). q and s vary.

From here we can define the predictor.

Predictor

To simplify this, we will define this in terms of signed 8-bit samples.

Input comprehension

Let b' be the previous instance of b, initialised to 0 to simplify implementation.

Let t be the “target”, -128 if b is 0, and 127 if b is 1. (In other words, LOW and HIGH respectively.)

Charge adjustment

Let q' be an integer such that

q' ← q + (s*(t - q) + 128)/256

If q == q', and q != t, then:

If t < q: q' ← q' - 1
If t > q: q' ← q' + 1

This is done to ensure that the “charge” can always reach its target.

Then set q ← q'.

Strength adjustment

Let r,z be integers such that:

If b == b', then r = Ri, z = 255
If b != b', then r = Rd, z = 0

Let s' be an integer such that

s' ← s + (r*(z - s) + 128)/256

If s == s', and s != z, then:

If z < s: s' ← s' - 1
If z > s: s' ← s' + 1

Then set s ← s'.

Filtering

You can do anything here, within reason. These methods are by no means the best way to deal with the noise you get.

These notes are based on the C implementation.

Antijerk

If the current target and the previous target are different, output the average of the current and previous results from the predictor; otherwise, output the value directly.

Low-pass filter

outQ ← outQ + (expectedOutput - outQ) * 100/256, essentially.

dfpwm.txt · Last modified: 2015/06/25 11:36 (external edit)