Skip to content

Commit 455f441

Browse files
committed
Add dfpwm codec implementation
Should match 100% to the Java implementation
1 parent fb3b22b commit 455f441

3 files changed

Lines changed: 219 additions & 0 deletions

File tree

dfpwm/dfpwm.lua

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
local shell=require("shell")
2+
3+
local args, options=shell.parse(...)
4+
if options.h or options.help then
5+
print([=[Usage: dfpwm [infile [outfile]]
6+
7+
Options:
8+
-h --help This message
9+
-d --decode Decode from dfpwm
10+
-e --encode Encode to dfpwm (default)
11+
-o --old Use old DFPWM codec
12+
-n --new Use new DFPWM1a codec (default)
13+
--bsize=n Buffer size
14+
]=])
15+
return
16+
end
17+
18+
local function errprint(msg)
19+
io.stderr:write(msg.."\n")
20+
io.stderr:flush()
21+
end
22+
23+
local encode=true
24+
local new=true
25+
local bsize=8192
26+
27+
if options.d or options.decode then
28+
encode=false
29+
end
30+
if options.o or options.old then
31+
new=false
32+
end
33+
if options.bsize then
34+
bsize=tonumber(options.bsize, 10)
35+
if not bsize then
36+
errprint("Error: '"..tostring(options.bsize).."' is not a valid number")
37+
return
38+
end
39+
end
40+
41+
local dfpwm=require("dfpwm")
42+
local codec=dfpwm.new(new)
43+
44+
local file, err
45+
if #args >= 1 then
46+
file, err=io.open(args[1], "rb")
47+
if not file then
48+
errprint(err)
49+
return
50+
end
51+
else
52+
file=io.stdin
53+
end
54+
55+
local outfile
56+
if #args >= 2 then
57+
outfile, err=io.open(args[2], "wb")
58+
if not outfile then
59+
file:close()
60+
errprint(err)
61+
return
62+
end
63+
else
64+
outfile=io.stdout
65+
end
66+
67+
while true do
68+
local data=file:read(bsize)
69+
if not data then break end
70+
data=table.pack(data:byte(1,-1))
71+
72+
local odata
73+
if encode then
74+
for i=1, #data do
75+
data[i]=bit32.bxor(data[i], 0x80)
76+
end
77+
odata=codec:compress(data)
78+
else
79+
odata=codec:decompress(data)
80+
for i=1, #odata do
81+
odata[i]=bit32.bxor(odata[i], 0x80)
82+
end
83+
end
84+
85+
for i=1, #odata do
86+
odata[i]=string.char(odata[i])
87+
end
88+
outfile:write(table.concat(odata))
89+
outfile:flush()
90+
91+
os.sleep(0)
92+
end
93+
94+
if #args >= 1 then
95+
file:close()
96+
if #args >= 2 then
97+
outfile:close()
98+
end
99+
end

dfpwm/lib/dfpwm.lua

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
local function ctx_update(state, curbit)
2+
local target=(curbit and 127 or -128)
3+
local nlevel=state.level+math.floor((state.response*(target-state.level)+state.RESP_PREC_HALF)/state.RESP_PREC_POWER)
4+
if nlevel == state.level and state.level ~= target then
5+
nlevel=nlevel+(curbit and 1 or -1)
6+
end
7+
8+
local rtarget, rdelta
9+
if curbit == state.lastbit then
10+
rtarget=state.RESP_PREC_MAX
11+
rdelta=state.RESP_INC
12+
else
13+
rtarget=0
14+
rdelta=state.RESP_DEC
15+
end
16+
17+
local nresponse=state.response+(state.new and 0 or math.floor((rdelta*(rtarget-state.response))/256))
18+
if nresponse == state.response and state.response ~= rtarget then
19+
nresponse=nresponse+((curbit == state.lastbit) and 1 or -1)
20+
end
21+
22+
if state.RESP_PREC > 8 then
23+
if nresponse < state.RESP_PREC_8 then
24+
nresponse=state.RESP_PREC_8
25+
end
26+
end
27+
28+
state.response=nresponse
29+
state.lastbit=curbit
30+
state.level=nlevel
31+
end
32+
33+
local function decompress(state, src)
34+
local dest={}
35+
for i=1, #src do
36+
local d=src[i]
37+
for j=0, 7 do
38+
-- apply context
39+
local curbit=(bit32.band(d, bit32.lshift(1, j)) ~= 0)
40+
local lastbit=state.lastbit
41+
ctx_update(state, curbit)
42+
43+
-- apply noise shaping
44+
local blevel = bit32.band((curbit == lastbit) and state.level or math.floor((state.flastlevel+state.level+1)/2), 0xFF)
45+
if blevel >= 128 then
46+
blevel=blevel-256
47+
end
48+
state.flastlevel=state.level
49+
50+
-- apply low-pass filter
51+
state.lpflevel=state.lpflevel+math.floor((state.LPF_STRENGTH*(blevel-state.lpflevel)+0x80)/256)
52+
dest[#dest+1]=bit32.band(state.lpflevel, 0xFF)
53+
end
54+
end
55+
return dest
56+
end
57+
58+
local function compress(state, src)
59+
local dest={}
60+
for i=1, #src, 8 do
61+
local d=0
62+
for j=0, 7 do
63+
local inlevel=(src[i+j] or 0)
64+
if inlevel >= 128 then
65+
inlevel=inlevel-256
66+
end
67+
local curbit=(inlevel > state.level or (inlevel == state.level and state.level == 127))
68+
d=bit32.bor(d/2, curbit and 128 or 0)
69+
ctx_update(state, curbit)
70+
end
71+
dest[#dest+1]=d
72+
end
73+
return dest
74+
end
75+
76+
local dfpwm={}
77+
78+
function dfpwm.new(newdfpwm)
79+
local state={
80+
new=newdfpwm,
81+
response=0,
82+
level=0,
83+
lastbit=false,
84+
flastlevel=0,
85+
lpflevel=0,
86+
decompress=decompress,
87+
compress=compress
88+
}
89+
if newdfpwm then
90+
state.RESP_INC=1
91+
state.RESP_DEC=1
92+
state.RESP_PREC=10
93+
state.LPF_STRENGTH=140
94+
else
95+
state.RESP_INC=7
96+
state.RESP_DEC=20
97+
state.RESP_PREC=8
98+
state.LPF_STRENGTH=100
99+
end
100+
-- precompute and cache some stuff
101+
state.RESP_PREC_HALF=bit32.lshift(1, state.RESP_PREC-1)
102+
state.RESP_PREC_POWER=2^state.RESP_PREC
103+
state.RESP_PREC_MAX=bit32.lshift(1, state.RESP_PREC)-1
104+
state.RESP_PREC_8=bit32.lshift(2, state.RESP_PREC-8)
105+
return state
106+
end
107+
108+
return dfpwm

programs.cfg

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,4 +206,16 @@
206206
authors = "gamax92",
207207
repo = "tree/master/ooci"
208208
},
209+
["dfpwm"] = {
210+
files = {
211+
["master/dfpwm/lib/dfpwm.lua"] = "/lib",
212+
["master/dfpwm/dfpwm.lua"] = "/bin",
213+
},
214+
dependencies = {
215+
},
216+
name = "dfpwm codec",
217+
description = "Lua implementation of the DFPWM codec",
218+
authors = "gamax92, GreaseMonkey (for Java implementation)",
219+
repo = "tree/master/dfpwm"
220+
},
209221
}

0 commit comments

Comments
 (0)