Created
May 3, 2013 05:18
-
-
Save maddievision/5507332 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import struct | |
import json | |
import chunk | |
import wave | |
import hashlib | |
import binascii | |
import sys | |
import os | |
files = [x for x in os.listdir(".") if os.path.splitext(x)[1].lower() == '.spc'] | |
class QuickRAM(object): | |
def __init__(self,arr): | |
self.data = arr | |
def getbyte(self,offset): | |
return ord(self.data[offset]) | |
def getushort(self,offset): | |
return struct.unpack("<H",self.data[offset:offset+2])[0] | |
def __getitem__(self, key): | |
return self.data[key] | |
def __repr__(self,key): | |
return self.data | |
SHIFTS = [13,12,12,12,12,12,12,12,12,12,12, 12, 12, 16, 16, 16, | |
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 11, 11, 11] | |
class BRRSample(object): | |
def __init__(self,blocks,loopindex=-1,name=""): | |
self.loops = False | |
self.name = name | |
if loopindex > -1: | |
self.loops = True | |
self.loopindex = loopindex | |
else: | |
self.loopindex = -1 | |
self.blocks = blocks | |
m = hashlib.sha1() | |
for b in blocks: | |
m.update(b['raw'].data) | |
self.hash = binascii.hexlify(m.digest()[0:8]) | |
def decode(self): | |
samples = [] | |
p1 = 0 | |
p2 = 0 | |
dickle = False | |
i = 0 | |
lpi = -1 | |
while i < len(self.blocks): | |
b = self.blocks[i] | |
if i == self.loopindex and not dickle: | |
lpi = len(samples) | |
lp1 = p1 | |
lp2 = p2 | |
scale = b['scale'] | |
flt = b['filter'] << 2 | |
right_shift = SHIFTS[scale] | |
left_shift = SHIFTS[scale+16] | |
stripe = 0 | |
pos = 0 | |
while pos < 8: | |
nybble = 0 | |
if stripe == 0: | |
nybble = (b['raw'].getbyte(pos + 1) >> 4) & 0xF | |
stripe += 1 | |
elif stripe == 1: | |
nybble = (b['raw'].getbyte(pos + 1) & 0xF) | |
stripe = 0 | |
pos += 1 | |
s = nybble | |
if s > 7: s -= 16 | |
# print s | |
s <<= scale | |
# s = max(-32768,min(32767,s)) | |
# s = float(s) #float(max(-32768,min(32767,s))) | |
if flt >= 8: | |
s += p1 | |
s -= p2 | |
if flt == 8: | |
# s += p1 * 0.953125 - p2 * 0.46875 | |
s += p2 >> 4 | |
s += (p1 * -3) >> 6 | |
else: | |
# s += p1 * 0.8984375 - p2 * 0.40625 | |
s += (p1 * -13) >> 7 | |
s += (p2 * 3) >> 4 | |
elif flt: | |
# s += p1 * 0.46875 | |
s += p1 >> 1 | |
s += (-p1) >> 5 | |
#(nybble >> right_shift) << left_shift | |
# print "nyb %01X" % nybble | |
# print "s %d" % s | |
# s &= 0xFFFF | |
# if s > 32767: s -= 65536 | |
# if s < -32768: s += 65536 | |
s = max(-32768,min(32767,s)) | |
s *= 2 | |
s = max(-32768,min(32767,s)) | |
# s = int(s) | |
#print s | |
# s = s & 0xFFFF | |
samples.append(s) | |
p2 = p1 >> 1 | |
p1 = s | |
# if self.loops and i == len(self.blocks)-1 and p1 != lp1 and p2 != lp2: | |
# i = self.loopindex | |
# else: | |
# i += 1 | |
i += 1 | |
return { 'samples': samples, 'loopindex': -1 } | |
def __repr__(self): | |
info = [] | |
info.append(self.name) | |
info.append(self.hash[0:8]) | |
info.append("Blocks: %d" % len(self.blocks)) | |
if self.loops: | |
info.append("Loop: %d" % self.loopindex) | |
return "<BRRSample %s>" % ', '.join(info) | |
class BrainLordSPC(object): | |
def __init__(self,fn): | |
f = open(fn,"rb") | |
self.spcdata = f.read() | |
f.close() | |
self.filename = fn | |
fna = os.path.splitext(fn)[0] | |
self.spcram = QuickRAM(self.spcdata[0x100:0x10100]) | |
self.spcdsp = QuickRAM(self.spcdata[0x10100:0x10200]) | |
self.spcid6 = QuickRAM(self.spcdata[0x10200:]) | |
self.spcdir = self.spcdsp.getbyte(0x5D) << 8 | |
off,loop = self.getsampleinfo(0) | |
samplecount = (off - self.spcdir) // 4 | |
# since brain lord stores sample data directly after sample pointer table | |
# we can assume number of samples is sample 0 offset minute dir divide 4 | |
self.samples = [] | |
for i in xrange(samplecount): | |
smpoff,smploop = self.getsampleinfo(i) | |
# print "Sample %02X: %04X, %04X" % (i,smpoff,smploop) | |
off = smpoff | |
blocks = [] | |
doesloop = False | |
loopindex = -1 | |
visited = [] | |
while True: | |
if off in visited: | |
loopindex = visited.index(off) | |
break | |
visited.append(off) | |
header = self.spcram.getbyte(off) | |
rng = header >> 4 | |
flt = (header >> 2) & 3 | |
loop = (header >> 1) & 1 | |
end = header & 1 | |
blkraw = self.spcram[off:off+9] | |
off += 9 | |
blockdata = { | |
'scale': rng, | |
'filter': flt, | |
'loop': (loop ==1), | |
'end': (end ==1), | |
'raw': QuickRAM(blkraw) | |
} | |
blocks.append(blockdata) | |
if blockdata['loop']: | |
doesloop = True | |
if blockdata['end']: | |
if doesloop: | |
off = smploop | |
else: | |
break | |
smp = None | |
name = fna + "-%02X" % i | |
self.samples.append(BRRSample(blocks,loopindex,name)) | |
def getsampleinfo(self,num): | |
ss = (self.spcdir+num*4) | |
smpoff = self.spcram.getushort(ss) | |
smploop = self.spcram.getushort(ss+2) | |
return smpoff,smploop | |
allsamples = [] | |
allhashes = [] | |
for f in files: | |
spc = BrainLordSPC(f) | |
for s in spc.samples: | |
if s.hash not in allhashes: | |
allhashes.append(s.hash) | |
allsamples.append(s) | |
for s in allsamples: | |
print s | |
sampinfo = s.decode() | |
# print sampinfo | |
samp = sampinfo['samples'] | |
wfn = s.name + ".wav" | |
w = wave.open(wfn,'wb') | |
channels = 1 | |
sampwidth = 2 | |
srate = 16000 | |
nframes = len(samp) | |
comptype = "NONE" | |
compname = "not compressed" | |
w.setparams((channels,sampwidth,srate,nframes,comptype,compname)) | |
vals = ''.join([ struct.pack('<h',x) for x in samp ]) | |
w.writeframes(vals) | |
w.close() | |
print "%s WRITTEN" % wfn | |
print "%02d files scanned. %02d unique samples" % (len(f),len(allsamples)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment