# autotracker-bottomup - the quite a few times more ultimate audio experience
# by Ben "GreaseMonkey" Russell, 2011. Public domain.
#
#
#
# BUGS:
# - sometimes gets stuck in an infinite loop. attempted to alleviate it but it doesn't work.
# - i think it sometimes jumps further than an octave in some situations
# oh and:
# - has moments where it sounds bad. if you can fix this for good, let me know!

import sys, struct, random
import math

# tunables.
SMP_FREQ = 44100
SMP_16BIT = True

# IT format constants. leave these alone.
IT_FLAG_STEREO = 0x01
IT_FLAG_VOL0MIX = 0x02 # absolutely useless since 1.04.
IT_FLAG_INSTR = 0x04
IT_FLAG_LINEAR = 0x08
IT_FLAG_OLDEFF = 0x10 # don't enable this, it's not well documented.
IT_FLAG_COMPATGXX = 0x20 # don't enable this, it's not well documented.
IT_FLAG_PWHEEL = 0x40 # MIDI-related, don't use
IT_FLAG_USEMIDI = 0x80 # undocumented MIDI crap, don't use
IT_SPECIAL_MESSAGE = 0x01 # MIDI-related, don't use
IT_SPECIAL_UNK1 = 0x02 # undocumented MIDI crap, don't use
IT_SPECIAL_UNK2 = 0x04 # undocumented MIDI crap, don't use
IT_SPECIAL_HASMIDI = 0x08 # undocumented MIDI crap, don't use
IT_SAMPLE_EXISTS = 0x01
IT_SAMPLE_16BIT = 0x02
IT_SAMPLE_STEREO = 0x04 # don't use, it's a modplugism.
IT_SAMPLE_IT214 = 0x08 # not supported yet - don't use.
IT_SAMPLE_LOOP = 0x10
IT_SAMPLE_SUS = 0x20 # mikmod doesn't like this, so be wary.
IT_SAMPLE_LOOPBIDI = 0x40
IT_SAMPLE_SUSBIDI = 0x80
# IT_CONVERT_* refers to the sample conversion flags.
# this is a VERY internal feature and not widely implemented.
# please ensure you ONLY use IT_CONVERT_SIGNED for normal samples.
# EXCEPTION: IT_CONVERT_DELTA + IT_SAMPLE_IT214 = IT215 compression.
IT_CONVERT_SIGNED = 0x01
IT_CONVERT_BIGEND = 0x02
IT_CONVERT_DELTA = 0x04
IT_CONVERT_BYTEDELT = 0x08
IT_CONVERT_TXWAVE = 0x10
IT_CONVERT_STEREO = 0x20

# anyhow, here's some code. enjoy.
IT_BASEFLG_SAMPLE = (
	   IT_SAMPLE_EXISTS
	| (IT_SAMPLE_16BIT if SMP_16BIT else 0)
)

##########################
#                        #
#   IT MODULE HANDLING   #
#                        #
##########################

# NOTE: Currently only writes data.
class ITFile:
	def __init__(self):
		self.name = "autotracker-bu module"
		self.flags = IT_FLAG_STEREO
		self.highlight = 0x1004
		self.ordlist = []
		self.inslist = []
		self.smplist = []
		self.patlist = []
		self.chnpan = [32 for i in xrange(64)]
		self.chnvol = [64 for i in xrange(64)]
		self.version = 0x0217
		self.vercompat = 0x0200
		self.flags = (
			  IT_FLAG_STEREO
			| IT_FLAG_VOL0MIX # in the exceptionally rare case it may help...
			| IT_FLAG_LINEAR
		)
		self.special = (
			  IT_SPECIAL_MESSAGE
		)
		self.gvol = 128
		self.mvol = 48
		self.tempo = random.randint(90,160)
		self.speed = 3
		self.pitchwheel = 0
		self.pansep = 128
		self.message = (
			  "Generated with Autotracker-Bu\n"
			+ "2011 Ben \"GreaseMonkey\" Russell - Public domain\n"
		)
	
	def enqueue_ptr(self, call):
		self.ptrq.append((self.fp.tell(),call))
		self.write("00PS")
	
	def save(self, fname):
		self.fp = open(fname,"wb")
		
		self.message_fixed = self.message.replace("\n\r","\n").replace("\n","\r") + "\x00\x00"
		
		self.ptrq = []
		self.doheader(self)
		
		while self.ptrq:
			pos, f = self.ptrq.pop(0)
			t = self.fp.tell()
			self.fp.seek(pos)
			self.write(struct.pack("<I",t))
			self.fp.seek(t)
			f(self)
		
		self.fp.close()
	
	def w_msg(self, fp):
		fp.write(self.message_fixed)
	
	def doheader(self, fp):
		ordlist = self.ordlist[:]
		if (not ordlist) or ordlist[-1] != 0xFF:
			ordlist.append(0xFF)
		
		fp.write("IMPM")
		fp.write_padded(25, self.name)
		fp.write(struct.pack("<HHHHHHHHH"
			,self.highlight
			,len(ordlist),len(self.inslist),len(self.smplist),len(self.patlist)
			,self.version,self.vercompat,self.flags,self.special
		))
		fp.write(struct.pack("<BBBBBBH"
			,self.gvol,self.mvol,self.speed,self.tempo
			,self.pansep,self.pitchwheel,len(self.message_fixed)
		))
		fp.enqueue_ptr(self.w_msg)
		fp.write("AtBu")
		fp.write(''.join(chr(v) for v in self.chnpan))
		fp.write(''.join(chr(v) for v in self.chnvol))
		
		fp.write(''.join(chr(v) for v in ordlist))
		
		l = self.inslist + self.smplist + self.patlist
		for i in xrange(len(l)):
			self.enqueue_ptr(l[i].write)
		
		fp.write(struct.pack("<HI",0,0))
	
	def write(self, data):
		self.fp.write(data)
	
	def write_padded(self, length, s, addnull = True):
		if len(s) < length:
			s += "\x00"*(length-len(s))
		else:
			s = s[:length]
		
		assert len(s) == length, "STUPID! write_padded gave wrong length!"
		
		self.write(s)
		
		if addnull:
			self.write("\x00")
	
	def write_sample_array(self, data):
		# TODO: allow for a compressor / proper saturator
		if SMP_16BIT:
			for v in data:
				d = max(-0x8000,min(0x7FFF,int(v * 0x7FFF)))
				self.write(struct.pack("<h", d))
		else:
			for v in data:
				d = max(-0x80,min(0x7F,int(v * 0x7F)))
				self.write(struct.pack("<b",d))
	
	def smp_add(self, smp):
		self.smplist.append(smp)
		idx = len(self.smplist)
		return idx
	
	def pat_add(self, pat):
		idx = len(self.patlist)
		self.patlist.append(pat)
		return idx
	
	def ord_add(self, order):
		self.ordlist.append(order)

class Sample:
	name = "feed me with a sample :)"
	flags = 0
	boost = 1.0
	
	fname = "AUTOTRKR.BU"
	gvol = 64
	vol = 64
	defpan = 32 # NOTE: set top bit (0x80) to actually use default pan
	convert = IT_CONVERT_SIGNED
	lpbeg = 0
	lpend = 0
	freq = SMP_FREQ
	susbeg = 0
	susend = 0
	vibspeed = 0
	vibdepth = 0
	vibrate = 0
	vibtype = 0
	
	def __init__(self, name = None, gvol = None, *args, **kwargs):
		if name != None:
			self.name = name
		if gvol != None:
			self.gvol = gvol
		
		self.data = self.generate(*args, **kwargs)
		self.length = len(self.data)
		self.amplify()
	
	def write(self, fp):
		fp.write("IMPS")
		fp.write_padded(12, self.fname)
		fp.write(struct.pack("<BBB", self.gvol, self.flags, self.vol))
		fp.write_padded(25, self.name)
		fp.write(struct.pack("<BB", self.convert, self.defpan))
		fp.write(struct.pack("<IIIIII"
			,self.length, self.lpbeg, self.lpend, self.freq
			,self.susbeg, self.susend))
		fp.enqueue_ptr(self.write_data)
		fp.write(struct.pack("<BBBB", self.vibspeed, self.vibdepth, self.vibrate, self.vibtype))
	
	def write_data(self, fp):
		fp.write_sample_array(self.data)
	
	def amplify(self):
		l = -0.0000000001
		h = 0.0000000001
		for v in self.data:#[len(self.data)//32:]:
			if v < l:
				l = v
			if v > h:
				h = v
		
		amp = self.boost / max(-l,h)
		#print amp
		
		for i in xrange(len(self.data)):
			self.data[i] *= amp
	
	def generate(self, *args, **kwargs):
		return []

class Pattern:
	def __init__(self, rows):
		self.rows = rows
		assert rows >= 4, "too few rows" # note, this is just so modplug doesn't whinge. IT can handle 1-row patterns.
		assert rows <= 200, "too many rows" # on the other hand, IT chunders if you have more than 200 rows.
		self.data = [[[253,0,255,0,0] for j in xrange(64)] for i in xrange(rows)]
		
		# these are the defaults...
		# - note = 253 (0xFD)
		# - instrument = 0
		# - volume = 255 (0xFF)
		# - effect type = 0
		# - effect parameter = 0
	
	def write(self, fp):
		self.dopack()
		fp.write(struct.pack("<HH", len(self.packbuf), len(self.data)))
		fp.write("AtBu")
		fp.write(''.join(chr(v) for v in self.packbuf))
	
	def dopack(self):
		self.packbuf = []
		lc = [[253,0,255,0,0] for j in xrange(64)]
		lm = [0x00 for j in xrange(64)]
		for l in self.data:
			for i in xrange(64):
				c = l[i]
				m = 0x00
				if c[0] != 253:
					m |= 0x10 if c[0] == lc[i][0] else 0x01
				if c[1] != 0:
					m |= 0x20 if c[1] == lc[i][1] else 0x02
				if c[2] != 255:
					m |= 0x40 if c[2] == lc[i][2] else 0x04
				if c[3] != 0 or c[4] != 0:
					m |= 0x80 if c[3] == lc[i][3] and c[4] == lc[i][4] else 0x08
				
				v = i+1
				if m != lm[i]:
					v |= 0x80
				self.packbuf.append(v)
				
				if v & 0x80:
					self.packbuf.append(m)
					lm[i] = m
				
				if m & 0x01:
					self.packbuf.append(c[0])
					lc[i][0] = c[0]
				if m & 0x02:
					self.packbuf.append(c[1])
					lc[i][1] = c[1]
				if m & 0x04:
					self.packbuf.append(c[2])
					lc[i][2] = c[2]
				if m & 0x08:
					self.packbuf.append(c[3])
					self.packbuf.append(c[4])
					lc[i][3] = c[3]
					lc[i][4] = c[4]
			
			self.packbuf.append(0)

#########################
#                       #
#   BLEEPS 'N' BLOOPS   #
#                       #
#########################

# YES! We actually have an almost decent sample synth!
class Sample_KS(Sample):
	name = "Karplus-Strong synth"
	flags = IT_BASEFLG_SAMPLE | IT_SAMPLE_LOOP
	boost = 1.0
	def generate(self, freq, decay, filtn, length_sec, nfrqmul = 1.0, filt0 = 1.0, filtf = 1.0, filtdc = 0.01):
		# generate waveform
		delay = int(SMP_FREQ/freq)
		noise = [0 for i in xrange(delay)]
		
		nfrqctr = 1.0
		nfrqval = 0.0
		
		intlen = int(SMP_FREQ*length_sec)
		assert intlen >= delay, "KS sample length cannot be less than its period"
		
		# DC filter
		dq = 0.0
		
		# prefilter with filt0
		qn = 0.0
		q = 0.0
		dl = -0.001
		dh = 0.001
		
		nvolcur = 1.0
		nvoldec = 1.0 / (decay * SMP_FREQ)
		
		nlfsr = random.randint(1,0x7FFF)
		
		# generate up to "length" samples
		qf = 0.0 #noise[-1]
		l = []
		i = 0
		for j in xrange(intlen):
			#ov = noise[i]
			if nvolcur > 0.0:
				if nfrqctr >= 1.0:
					#nfrqval = random.random()*2.0-1.0
					nfrqval = (1.0 if (nlfsr & 1) else -1.0) * 1.0
					
					# skip a value to balance it a bit better
					if nlfsr == 1:
						nlfsr = 0x4000
					
					if nlfsr & 1:
						nlfsr = (nlfsr>>1) ^ 0x6000
					else:
						nlfsr >>= 1
					nfrqctr -= 1.0
				nfrqctr += nfrqmul
				qn = (nfrqval * nvolcur - qn) * filt0 + qn
				nvolcur -= nvoldec
				noise[i] += qn
			
			ov = q = noise[i] = (noise[i] - q) * filtn + q
			qf = (ov - qf) * filtf + qf
			dq += (qf - dq) * filtdc
			l.append(qf - dq)
			i = (i+1) % delay
		
		# set stuff
		self.lpend = intlen
		self.lpbeg = intlen - delay
		
		# return
		return l

class Sample_Kicker(Sample):
	name = "Kicker"
	flags = IT_BASEFLG_SAMPLE
	boost = 1.8
	def generate(self):
		vol_noise = 0.8
		vol_sine = 1.2
		vol_noise_decay = 1.0 / (SMP_FREQ * 0.01)
		vol_sine_decay = 1.0 / (SMP_FREQ * 0.2)
		
		q_noise = 0.0
		
		kickmul = math.pi*2.0*150.0/SMP_FREQ
		offs_sine = 0.0
		offs_sine_speed = kickmul
		offs_sine_decay = 0.9995
		
		intlen = int(SMP_FREQ*0.25)
		l = []
		for j in xrange(intlen):
			sv = max(-0.7,min(0.7,math.sin(offs_sine)))
			offs_sine += offs_sine_speed
			offs_sine_speed *= offs_sine_decay
			
			nv = (random.random()*2.0-1.0)
			q_noise += (nv - q_noise) * 0.1
			nv = q_noise
			
			l.append(nv*vol_noise + sv*vol_sine)
			vol_noise -= vol_noise_decay
			if vol_noise < 0.0:
				vol_noise = 0.0
			vol_sine -= vol_sine_decay
			if vol_sine < 0.0:
				vol_sine = 0.0
			
		
		return l

class Sample_NoiseHit(Sample):
	name = "Noise hit generator"
	flags = IT_BASEFLG_SAMPLE
	boost = 1.0
	def generate(self, decay, filtl = 1.0, filth = 0.0):
		vol_noise = 1.0
		vol_noise_decay = 1.0 / (SMP_FREQ * decay)
		
		ql = 0.0
		qh = 0.0
		
		intlen = int(SMP_FREQ*decay)
		l = []
		for j in xrange(intlen):
			nv = (random.random()*2.0-1.0)
			ql += (nv - ql) * filtl
			qh += (nv - qh) * filth
			nv = ql - qh
			
			l.append(nv*vol_noise)
			vol_noise -= vol_noise_decay
			if vol_noise < 0.0:
				vol_noise = 0.0
		
		return l

class Sample_Hoover(Sample):
	name = "Hoover"
	flags = IT_BASEFLG_SAMPLE | IT_SAMPLE_LOOP
	boost = 1.0
	
	def generate(self, freq):
		oscfrq = [
			int(freq*(v + v*(random.random()*2.0-1.0)*0.002))/float(SMP_FREQ)
			for v in [0.25, 0.5, 1.0, 2.0]
		]
		
		oscvibspeed = [float(random.randint(1,5))*2.0*math.pi/SMP_FREQ for i in xrange(4)]
		oscvibdepth = [0.5,0.4,0.2,0.2]
		oscoffs = [random.random() for i in xrange(4)]
		oscviboffs = [random.random() for i in xrange(4)]
		oscvol = [1.0, 1.0, 1.0, 0.55]
		
		attack = 0.03
		atkvol = 0.0
		atkspd = 1.0/(attack*SMP_FREQ)
		
		intlen = int(SMP_FREQ*(attack+1.0))
		
		l = []
		for i in xrange(intlen):
			v = 0.0
			for j in xrange(4):
				ov = oscoffs[j]*2.0-1.0
				vib = math.sin(oscviboffs[j])*oscvibdepth[j]
				oscoffs[j] += oscfrq[j] * (2.0**(vib/12.0))
				if oscoffs[j] > 1.0:
					oscoffs[j] %= 1.0
				oscviboffs[j] += oscvibspeed[j]
				v += oscvol[j]*ov
			
			atkvol += atkspd
			if atkvol > 1.0:
				atkvol = 1.0
			l.append(v*atkvol)
		
		self.lpend = intlen
		self.lpbeg = int(intlen - SMP_FREQ*1.0 + 0.5)
		
		return l

##########################
#                        #
#   RANDOCHORD FACTORY   #
#                        #
##########################

class Key:
	def __init__(self):
		pass
	
	def get_base_note(self):
		return 60
	
	def has_note(self, n):
		return True

class Key_GenericOctave(Key):
	MASK = [True]*12
	def __init__(self, basenote):
		self.basenote = basenote
	
	def get_base_note(self):
		return self.basenote
	
	def has_note(self, n):
		return self.MASK[(n-self.basenote)%12]

class Key_Major(Key_GenericOctave):
	MASK = [
		True,False,
		True,False,
		True,
		True,False,
		True,False,
		True,False,
		True,
	]

class Key_Minor(Key_GenericOctave):
	MASK = [
		True,False,
		True,
		True,False,
		True,False,
		True,
		True,False,
		True,False,
	]

class Key_Major_Pentatonic(Key_GenericOctave):
	MASK = [
		True,False,
		True,False,
		True,
		False,False,
		True,False,
		True,False,
		False,
	]

class Key_Minor_Pentatonic(Key_GenericOctave):
	MASK = [
		True,False,
		False,
		True,False,
		True,False,
		True,
		False,False,
		True,False,
	]

class Strategy:
	def __init__(self, *args, **kwargs):
		self.setup(*args,**kwargs)
		self.gens = []
		self.chused = 0
	
	def setup(self, *args, **kwargs):
		self.key = Key_GenericOctave(60)
	
	def gen_add(self, gen):
		self.gens.append((self.chused,gen))
		self.chused += gen.size()
	
	def get_key(self):
		return self.key

class Strategy_Main(Strategy):
	def setup(self, basenote, keytype, patsize, blocksize, *args, **kwargs):
		self.basenote = basenote
		self.keytype = keytype
		self.patsize = patsize
		self.blocksize = blocksize
		self.key = keytype(basenote)
		self.pats = []
		self.rspeed = 2**random.randint(2,3)
		
		self.rhythm = [3]+[0]*(self.rspeed-1)+[1]+[0]*(self.rspeed-1)
		self.rhythm *= (self.patsize//len(self.rhythm))
		
		self.pat_idx = 0
		
		self.newkseq()
	
	def newkseq(self):
		self.kseq = random.choice({
			Key_Minor: [
				[(0,Key_Minor),(-4,Key_Major),(5,Key_Major),(-2,Key_Major)],
				[(0,Key_Minor),(-2,Key_Major),(-4,Key_Major),(-5,Key_Minor)],
			],
			Key_Major: [
				[(0,Key_Major),(-5,Key_Major),(-3,Key_Minor),(5,Key_Major)],
				[(0,Key_Major),(0,Key_Major),(-7,Key_Minor),(-5,Key_Major)],
			],
		}[self.keytype])
		
		self.kseq2 = random.choice({
			Key_Minor: [
				[(3,Key_Major),(0,Key_Minor),(-4,Key_Major),(-2,Key_Major)],
				[(-4,Key_Major),(-2,Key_Major),(0,Key_Minor),(-2,Key_Major)],
			],
			Key_Major: [
				[(2,Key_Minor),(0,Key_Major),(-3,Key_Minor),(0,Key_Major)],
				[(-3,Key_Minor),(-5,Key_Major),(-7,Key_Major),(-5,Key_Major)],
			],
		}[self.keytype])
	
	def get_pattern(self):
		pat = Pattern(self.patsize)
		
		kseq = self.kseq2[:] if self.pat_idx % 8 >= 4 else self.kseq[:]
		
		for i in xrange(0,self.patsize,self.blocksize):
			k,kt = kseq.pop(0)
			kchord = kt(self.basenote+k)
			for chn,gen in self.gens:
				gen.apply_notes(chn, pat, self, self.rhythm, i, self.blocksize, self.key, kchord)
			
			kseq.append(k)
		
		self.pats.append(pat)
		
		self.pat_idx += 1
		
		return pat
	
	def get_key(self):
		return self.key

class Generator:
	def __init__(self, *args, **kwargs):
		pass
	
	def size(self):
		return 1
	
	def apply_notes(self, chn, pat, strat, rhythm, bbeg, blen, kroot, kchord):
		pass

class Generator_Bass(Generator):
	def __init__(self, smp, *args, **kwargs):
		self.smp = smp
	
	def size(self):
		return 1
	
	def apply_notes(self, chn, pat, strat, rhythm, bbeg, blen, kroot, kchord):
		base = kchord.get_base_note()
		
		leadin = 0
		
		for row in xrange(bbeg, bbeg+blen, 1):
			if rhythm[row]&1:
				n = base-12 if random.random() < 0.5 else base
				pat.data[row][chn] = [n, self.smp, 255, 0, 0]
				
				if leadin != 0 and random.random() < 0.4:
					gran = 2
					count = 1
					
					#if random.random() < 0.2:
					#	gran = 1
					
					if leadin > gran*2 and random.random() < 0.4:
						count += 1
						if leadin > gran*3 and random.random() < 0.4:
							count += 1
					
					for j in xrange(count):
						pat.data[row-(j+1)*gran][chn] = [
							 base+12 if random.random() < 0.5 else base
							,self.smp
							,0xFF
							,ord('S')-ord('A')+1
							,0xC0 + random.randint(1,2)
						]
					
				if random.random() < 0.2:
					pat.data[row][chn][0] += 12
					if random.random() < 0.4:
						pat.data[row][chn][3] = ord('S')-ord('A')+1
						pat.data[row][chn][4] = 0xC0 + random.randint(1,2)
					else:
						pat.data[row+2][chn] = [254, self.smp, 255, 0, 0]
				
				leadin = 0
			else:
				leadin += 1

class Generator_AmbientMelody(Generator):
	MOTIF_PROSPECTS = [
		# 1-steps
		[1],
		[2],
		[3],
		
		# 2-steps
		[1,3],
		[2,3],
		[2,4],
		
		# niceties
		[5,7],
		[5,12],
		[7,12],
		[7],
		[5],
		[12],
		
		# 3-chords
		[3,7],
		[4,7],
		
		# 4-chords
		[3,7,10],
		[3,7,11],
		[4,7,10],
		[4,7,11],
		
		# turns and stuff
		[1,0],
		[2,0],
		[1,-1,0],
		[1,-2,0],
		[2,-1,0],
		[2,-2,0],
	]
	
	def __init__(self, smp, *args, **kwargs):
		self.smp = smp
		self.beatrow = 2**random.randint(2,3)
		self.lq = 60
		self.ln = -1
		self.mq = []
		self.nq = []
	
	def size(self):
		return 1
	
	def apply_notes(self, chn, pat, strat, rhythm, bbeg, blen, kroot, kchord):
		base = kchord.get_base_note()
		if bbeg == 0:
			self.lq = base
			self.ln = -1
			self.mq = []
			self.nq = []
		
		pat.data[bbeg][chn] = [self.lq, self.smp, 255, 0, 0]
		self.nq.append(bbeg)
		#self.ln = self.lq
		
		stabbing = False
		
		row = bbeg
		while row < bbeg+blen:
			if pat.data[row][chn][0] != 253:
				self.nq.append(row)
				
				row += self.beatrow
				continue
			
			q = 60
			
			if self.mq:
				if stabbing or random.random() < 0.9:
					n = self.mq.pop(0)
					self.ln = n
					pat.data[row][chn] = [n, self.smp, 255, 0, 0]
					self.nq.append(row)
					
					if not self.mq:
						self.lq = n
					
					if random.random() < 0.2 or stabbing:
						row += self.beatrow // 2
						stabbing = not stabbing
					else:
						row += self.beatrow
				else:
					row += self.beatrow
			elif row-bbeg >= 2*self.beatrow and random.random() < 0.3:
				backstep = random.randint(3,min(10,row//(self.beatrow//2)))*(self.beatrow//2)
				print ("back", row, backstep)
				
				for i in xrange(backstep):
					if row-bbeg >= blen:
						break
					pat.data[row][chn] = pat.data[row-backstep][chn][:]
					n = pat.data[row][chn][0]
					if n != 253:
						self.ln = self.lq = n
					row += 1
			else:
				if len(self.nq) > 5:
					self.nq = self.nq[-5:]
				
				while True:
					kk = False
					while True:
						rbi = random.choice(self.nq)
						rbn = pat.data[rbi][chn][0]
						
						if self.ln != -1 and abs(rbn-self.ln) > 12:
							continue
						
						break
					
					m = None
					print (rbn)
					for j in xrange(20):
						m = random.choice(self.MOTIF_PROSPECTS)
						
						down = random.random() < (8.0+(self.ln-base))/8.0 if self.ln != -1 else 0.5
						
						print (m,rbn,down,base)
						if down:
							m = [rbn-v for v in m]
						else:
							m = [rbn+v for v in m]
						
						
						if self.ln == m[0]:
							continue
						
						k = True
						for v in m:
							if not (kchord.has_note(v) and kroot.has_note(v)):
								k = False
								break
						
						if k:
							kk = True
							break
					
					if kk:
						break
				
				
				if rbn != self.ln:
					m = [rbn] + m
				
				print (m)
				self.mq += m
				
				# repeat at same row

class Generator_Drums(Generator):
	def __init__(self, s_kick, s_hhc, s_hho, s_snare, *args, **kwargs):
		self.s_kick = s_kick
		self.s_hhc = s_hhc
		self.s_hho = s_hho
		self.s_snare = s_snare
		self.beatrow = 2**random.randint(1,2)
	
	def size(self):
		return 3
	
	def apply_notes(self, chn, pat, strat, rhythm, bbeg, blen, kroot, kchord):
		for row in xrange(bbeg,bbeg+blen,self.beatrow):
			vol = 255
			smp = self.s_hhc
			if not (rhythm[row]&2):
				if (row&8):
					vol = 48
				if (row&4):
					vol = 32
				if (row&2):
					vol = 16
				if (row&1):
					vol = 8
				
				if random.random() < 0.2:
					smp = self.s_hho
			
			pat.data[row][chn] = [60, smp, vol, 0, 0]
		
		for row in xrange(bbeg,bbeg+blen,2):
			if random.random() < 0.1 and not rhythm[row]&1:
				pat.data[row][chn+1] = [60,self.s_kick,255,0,0]
		
		did_kick = False
		for row in xrange(bbeg,bbeg+blen,1):
			if rhythm[row]&1:
				if did_kick:
					pat.data[row][chn+2] = [60,self.s_snare,255,0,0]
				else:
					if random.random() < 0.1:
						pat.data[row+2][chn+1] = [60,self.s_kick,255,0,0]
					else:
						pat.data[row][chn+1] = [60,self.s_kick,255,0,0]
				
				did_kick = not did_kick


#################
#               #
#   BOOTSTRAP   #
#               #
#################

MIDDLE_C = 220.0 * (2.0 ** (3.0 / 12.0))

print ("Creating module")
itf = ITFile()

print ("Generating samples")
# these could do with some work, they're a bit crap ATM --GM
# note: commented a couple out as they use a fair whack of space and are unused.
SMP_GUITAR = itf.smp_add(Sample_KS(name = "KS Guitar", freq = MIDDLE_C/2, decay = 0.005, nfrqmul = 1.0, filt0 = 0.1, filtn = 0.6, filtf = 0.0004, length_sec = 1.0))
SMP_BASS = itf.smp_add(Sample_KS(name = "KS Bass", freq = MIDDLE_C/4, decay = 0.005, nfrqmul = 0.5, filt0 = 0.2, filtn = 0.2, filtf = 0.005, length_sec = 0.7))
#SMP_PIANO = itf.smp_add(Sample_KS(name = "KS Piano", freq = MIDDLE_C, decay = 0.07, nfrqmul = 0.02, filtdc = 0.1, filt0 = 0.09, filtn = 0.6, filtf = 0.4, length_sec = 1.0))
#SMP_HOOVER = itf.smp_add(Sample_Hoover(name = "Hoover", freq = MIDDLE_C))

SMP_KICK = itf.smp_add(Sample_Kicker(name = "Kick"))
SMP_HHC = itf.smp_add(Sample_NoiseHit(name = "NH Hihat Closed", gvol = 32, decay = 0.03, filtl = 0.99, filth = 0.20))
SMP_HHO = itf.smp_add(Sample_NoiseHit(name = "NH Hihat Open", gvol = 32, decay = 0.5, filtl = 0.99, filth = 0.20))
SMP_SNARE = itf.smp_add(Sample_NoiseHit(name = "NH Snare", decay = 0.12, filtl = 0.15, filth = 0.149))


print ("Generating patterns")
strat = Strategy_Main(random.randint(50,50+12-1)+12, Key_Minor if random.random() < 0.6 else Key_Major, 128, 32)
strat.gen_add(Generator_Drums(s_kick = SMP_KICK, s_snare = SMP_SNARE, s_hhc = SMP_HHC, s_hho = SMP_HHO))
strat.gen_add(Generator_AmbientMelody(smp = SMP_GUITAR))
strat.gen_add(Generator_Bass(smp = SMP_BASS))
for i in xrange(6):
	itf.ord_add(itf.pat_add(strat.get_pattern()))

print ("Saving")

# pick a random name
RN_NOUNS = [
	("cat","cats"),("kitten","kittens"),
	("dog","dogs"),("puppy","puppies"),
	("elf","elves"),("knight","knights"),
	("wizard","wizards"),("witch","witches"),("leprechaun","leprechauns"),
	("dwarf","dwarves"),("golem","golems"),("troll","trolls"),
	("city","cities"),("castle","castles"),("town","towns"),("village","villages"),
	("journey","journeys"),("flight","flights"),("place","places"),
	("bird","birds"),
	("ocean","oceans"),("sea","seas"),
	("boat","boats"),("ship","ships"),
	("whale","whales"),
	("brother","brothers"),("sister","sisters"),
	("viking","vikings"),("ghost","ghosts"),
	("garden","gardens"),("park","parks"),
	("forest","forests"),("ogre","ogres"),
	("sweet","sweets"),("candy","candies"),
	("hand","hands"),("foot","feet"),("arm","arms"),("leg","legs"),
	("body","bodies"),("head","heads"),("wing","wings"),
	("gorilla","gorillas"),("ninja","ninjas"),("bear","bears"),
	("vertex","vertices"),("matrix","matrices"),("simplex","simplices"),
	("shape","shapes"),
	("apple","apples"),("pear","pears"),("banana","bananas"),
	("orange","oranges"),
	("demoscene","demoscenes"),
	("sword","swords"),("shield","shields"),("gun","guns"),("cannon","cannons"),
	("report","reports"),("sign","signs"),("year","years"),("age","ages"),
	("blood","bloods"),("breed","breeds"),("monument","monuments"),
	("cheese","cheeses"),("horse","horses"),("sheep","sheep"),("fish","fish"),
	("dock","docks"),("tube","tubes"),("road","roads"),("path","paths"),
	("tunnel","tunnels"),("retort","retorts"),
	("toaster","toasters"),("goat","goats"),
	("tofu","tofus"),("vine","vines"),("branch","branches"),
	
]

RN_ADJECTIVES = [
	"tense","grand","pleasing","absurd","offensive","crazed",
	"magic","lovely","tired","lively","tasty","jealous",
	"red","orange","yellow","green","blue","purple","pink","brown",
	"white","black","cheap","blazed","biased","sweet",
	"invisible","hidden","secret","long","short","tall","broken",
	"random","fighting","hunting","eating","drinking","drunk",
	"weary","walking","running","flying","strong","weak",
	"woeful","tearful","rich","poor","awoken","sacred",
]

RN_VERBS = [
	# TODO
]

RN_PATTERNS = [
	"the (n[0])'s (n[0,1])",
	"(N[0])'s (n[0,1])",
	"(n[0,1]) of (N[0,1])",
	"on the (n[0])'s (n[0,1])",
	"(n[0,1]) of the (n[0,1])",
	"the (a) (n[0,1])",
	"(A) (n[0])",
	"(a) (n[1])",
	"(a) and (a)",
	"(N[0,1]) and (N[0,1])",
]

def randoname():
	pat = random.choice(RN_PATTERNS)
	while "(" in pat:
		ps, po, pp = pat.partition("(")
		p, pc, pn = pp.partition(")")
		
		assert pc == ")", "expected ')' in name pattern"
		
		p = random.choice(p.split("|"))
		if p.startswith("n") or p.startswith("N"):
			idx = random.choice(eval(p[1:]))
			w = random.choice(RN_NOUNS)[idx]
			if idx in [0] and p.startswith("N"):
				if w[0] in "aeiouAEIOU":
					p = "an " + w
				else:
					p = "a " + w
			else:
				p = w
		elif p.startswith("a") or p.startswith("A"):
			w = random.choice(RN_ADJECTIVES)
			if p.startswith("A"):
				if w[0] in "aeiouAEIOU":
					p = "an " + w
				else:
					p = "a " + w
			else:
				p = w
		else:
			raise Exception("invalid name pattern type")
		
		pat = ps + p + pn
	
	return pat

name = randoname()
itf.name = name
fname = "bu-%s.it" % name.replace(" ","-").replace("'","")
if len(sys.argv) > 1:
	fname = sys.argv[1]
itf.save(fname)

print ("Done")
print ("Saved as \"%s\"" % fname)
