forked from Plombo/vcromclaim
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathbrrencode3.py
executable file
·167 lines (139 loc) · 4.2 KB
/
brrencode3.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#!/usr/bin/env python3
# Author: Bryan Cain
# Based on but heavily modified from BRRTools by Bregalad (written in Java)
# Date: January 16, 2011
# Description: Encodes 16-bit signed PCM data to SNES BRR format.
import wave
import struct
class BRREncoder(object):
def __init__(self, pcm):
self.pcm_owner = False
#self.brr_owner = False
if type(pcm) == type(''):
pcm = open(pcm, 'rb')
self.pcm_owner = True
#if type(brr) == type(''):
# brr = open(brr, 'wb')
# self.brr_owner = True
self.pcm = pcm
#self.brr = brr
self.p1 = 0
self.p2 = 0
# clamps value to a signed short
def sshort(self, n):
if n > 0x7FFF: return (n - 0x10000)
elif n < -0x8000: return n & 0x7FFF
else: return n
# short clamp_16(int n)
def clamp_16(self, n):
if n > 0x7FFF: return (0x7FFF - (n>>24))
else: return n
# void ADPCMBlockMash(short[] PCMData)
def ADPCMBlockMash(self, PCMData):
smin=0
kmin=0
dmin=2**31
for s in range(13, 0, -1):
for k in range(4):
d = self.ADPCMMash(s, k, PCMData, False)
if d < dmin:
kmin = k # Memorize the filter, shift values with smaller error
dmin = d
smin = s
if dmin == 0.0: break
if dmin == 0.0: break
self.BRRBuffer[0] = (smin<<4)|(kmin<<2)
self.ADPCMMash(smin, kmin, PCMData, True)
# double ADPCMMash(int shiftamount, int filter, short[] PCMData, boolean write)
def ADPCMMash(self, shiftamount, filter, PCMData, write):
d2=0.0
vlin=0
l1 = self.p1
l2 = self.p2
step = 1<<shiftamount
for i in range(16):
# Compute linear prediction for filters
if filter == 0:
pass
elif filter == 1:
vlin = l1 >> 1
vlin += (-l1) >> 5
elif filter == 2:
vlin = l1
vlin += (-(l1 +(l1>>1)))>>5
vlin -= l2 >> 1
vlin += l2 >> 5
else:
vlin = l1
vlin += (-(l1+(l1<<2) + (l1<<3)))>>7
vlin -= l2>>1
vlin += (l2+(l2>>1))>>4
d = (PCMData[i]>>1) - vlin # Difference between linear prediction and current sample
da = abs(d)
if da > 16384 and da < 32768:
d = d - 32768 * ( d >> 24 ) # Take advantage of wrapping
dp = d + (step << 2) + (step >> 2)
c = 0
if dp > 0:
if step > 1:
c = int(dp /(step>>1))
else:
c = dp<<1
if c > 15:
c = 15
c -= 8
dp = (c<<(shiftamount-1)) # quantized estimate of samp - vlin
# edge case, if caller even wants to use it */
if shiftamount > 12:
dp = ( dp >> 14 ) & ~0x7FF
c &= 0x0f # mask to 4 bits
l2 = l1 # shift history
l1 = self.sshort(self.clamp_16(vlin + dp)*2)
d = PCMData[i]-l1
d2 += float(d)*d # update square-error
if write: # if we want output, put it in proper place */
self.BRRBuffer[(i>>1)+1] |= c<<(4-((i&0x01)<<2))
if write:
self.p2 = l2
self.p1 = l1
return d2
# encodes the entire PCM file to BRR
def encode(self):
self.BRRBuffer = [0, 0, 0, 0, 0, 0, 0, 0, 0] # byte[9]
#wav = wave.open(wav, 'rb')
#if wav.getsampwidth() != 2: raise ValueError('must be 16 bits per sample')
pcm = self.pcm
#brr = self.brr
self.p1 = 0
self.p2 = 0
samples2 = pcm.read(32) # the PCM samples in VC PCM files are misaligned for some reason
while len(samples2) == 32:
samples2 = struct.unpack('>16h', samples2)
self.BRRBuffer = [0, 0, 0, 0, 0, 0, 0, 0, 0]
self.ADPCMBlockMash(samples2)
#brr.write(struct.pack('9B', *self.BRRBuffer))
#samples2 = wav.readframes(16)
samples2 = pcm.read(32)
#wav.close()
if self.pcm_owner: pcm.close()
#if self.brr_owner: brr.close()
# offset: PCM offset (measured in samples, NOT in bytes)
# returns: 9-byte BRR block
def encode_block(self, offset):
# read PCM - the PCM samples in VC PCM files are misaligned for some reason
self.pcm.seek(offset * 2)
samples2 = self.pcm.read(32)
if len(samples2) != 32:
raise ValueError('invalid PCM offset %d (file offset %d)' % (offset, offset*2))
samples2 = struct.unpack('>16h', samples2)
# encode to BRR
self.BRRBuffer = [0, 0, 0, 0, 0, 0, 0, 0, 0]
self.ADPCMBlockMash(samples2)
return struct.pack('9B', *self.BRRBuffer)
if __name__ == '__main__':
import sys
if len(sys.argv) != 3:
print('Usage: %s input.pcm' % sys.argv[0])
#enc = BRREncoder(sys.argv[1])
#enc.encode()
print('Wrote file %s' % sys.argv[2])