Skip to content

Instantly share code, notes, and snippets.

@maddievision
Created May 3, 2013 05:18
Show Gist options
  • Save maddievision/5507332 to your computer and use it in GitHub Desktop.
Save maddievision/5507332 to your computer and use it in GitHub Desktop.
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