-
Notifications
You must be signed in to change notification settings - Fork 0
/
cmpreader.py
144 lines (123 loc) · 4 KB
/
cmpreader.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#!/usr/bin/python3
# ********************************************
# SWAT 3 CMP to WAV audio decompression tool
# https://github.com/induktio/swat3-tools
# ********************************************
#
# Usage: ./cmpreader.py [-v] [-t] filename.cmp
#
# Input filenames must have .cmp extension for the tool to work.
# Converted audio files are written to 'output' directory in WAV format.
#
# Author: Induktio <[email protected]>
#
import os
import sys
import wave
import struct
import argparse
from collections import deque
class BitReader:
def __init__(self, f, verify=False):
self.input = f
self.bits = 0
self.bcount = 0
self.readbts = 0
self.readcnt = 0
self.verify_file = None
if verify:
try:
log_file = f.name.replace('cmp', 'log')
self.verify_file = open(log_file)
print('Verifying log file: %s' % (log_file))
except:
print('Log file not found: %s' % (log_file))
self.verify_file = None
def readbit(self):
if self.bcount == 0:
a = self.input.read(4)
if (len(a) > 0):
self.bits = struct.unpack('<i', a)[0]
else:
raise RuntimeError
self.bcount = 32
rv = ( self.bits >> (32-self.bcount) ) & 1
self.bcount -= 1
return rv
def verify(self, n, v):
if self.verify_file:
cn, cv = self.verify_file.readline().split()
assert(int(cn) == n)
assert(int(cv) == v)
def readbits(self, n):
v = 0
if (self.readbts % 32) + n > 32 and n < 32:
n1 = 32 - self.readbts % 32
n2 = n - n1
i = 0
while i < n1:
v |= (self.readbit() << i)
i += 1
i = 0
v <<= n2
while i < n2:
v |= (self.readbit() << i)
i += 1
else:
i = 0
while i < n:
v |= (self.readbit() << i)
i += 1
if n < 32:
self.verify(n, v)
self.readcnt += 1
self.readbts += n
return v
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('inputfile', type=str, default=None, help='note: input file must have .cmp extension')
parser.add_argument('-t', '--test', action='store_true', help='verify extracted output using .log file')
parser.add_argument('-v', '--verbose', action='store_true', help='verbose output mode')
args = parser.parse_args()
inputfile = args.inputfile.replace('.cmp', '')
r = BitReader(open('%s.cmp' % (inputfile), 'rb'), verify=args.test)
r.readbits(64)
len_coded = r.readbits(32)
len_wav = r.readbits(32)
sampling_freq = r.readbits(32)
r.readbits(96)
cache = deque()
mov_sum = 0
cur_len = 0
border = 0
samples = 0
d1 = 0
d2 = 0
smp = 0
if not os.access('output', os.R_OK):
os.mkdir('output')
wavout = wave.open('output/%s.wav' % (inputfile.split('/')[-1]), 'wb')
wavout.setparams((1, 2, sampling_freq, int(len_wav/2), 'NONE', 'not compressed'))
wavsamples = []
while samples < len_wav/2:
border = r.readbits(1)
z = r.readbits(cur_len)
if border == 1 and z == 0:
cur_len = r.readbits(4)
else:
sign = 1
if border: sign = -1
d1 = z * sign
d2 += d1
smp += d2
samples += 1
cache.append(smp)
mov_sum += smp
if samples > 4096:
mov_sum -= cache.popleft()
drift = int(mov_sum / 4096.0 * min(samples, 1024) / 1024.0)
wavsamples.append(struct.pack('h', max(-32768, min(32767, (smp-drift)<<5))))
wavout.writeframes(b''.join(wavsamples))
wavout.close()
if args.verbose:
print('samples_cmp: %d samples_wav: %d sampling_freq: %d ' % (len_coded, len_wav, sampling_freq))