# pybuzz library
# Written by Leonard :paniq: Ritter (paniq@gmx.net)

from buzz import *
from random import *

bpm = 0
tpb = 0
sps = 0
spt = 0
pit = 0
tps = 0

NOTE_C = 0

FALSE = 0
TRUE = 1

def MakeNote(octave,note):
	octave = octave + (note/12)
	note = note % 12
	return octave*16+note+1

def UnMakeNote(note):
    note -= 1
    return (note/16,note%16)

def UpdateMasterInfo():
	global bpm,tpb,sps,spt,pit,tps
	bpm,tpb,sps,spt,pit,tps = GetMasterInfo()

def OnMidiNote(channel,value,velocity):
	print "[MIDINote] channel: %02x value: %02x velocity: %02x" % (channel,value,velocity)

def OnMidiControlChange(ctrl,channel,value):
	print "[MIDICtrl] ctrl: %02x channel: %02x value: %02x" % (ctrl,channel,value)

SetEventTarget("OnMidiControlChange",OnMidiControlChange)
SetEventTarget("OnMidiNote",OnMidiNote)
UpdateMasterInfo()

def ClearAllPeerCtrls():
	for i in xrange(MAX_PEERCTRLS):
		ClearPeerCtrlTarget(i)
		SetPeerCtrlName(i,"")

def FindFreePeerCtrl():
	for i in xrange(MAX_PEERCTRLS):
		if not GetPeerCtrlName(i):
			return i
	return -1

def FindPeerCtrlByName(peerCtrlName):
	for i in xrange(MAX_PEERCTRLS):
		if GetPeerCtrlName(i) == peerCtrlName:
			return i
	return -1

def GetMachines():
	result = []
	for name in GetMachineNames():
		result.append(GetMachine(name))
	return result

def FilterPeerGroupsByMachineClassName(peerGroupList,machineClassName):
	peerCtrls = []
	for peer in peerGroupList:
		if peer[0].GetMachineInfo()[10] == machineClassName:
			peerCtrls.append(peer)
	return peerCtrls

class PeerCtrl:
	def __init__(self):
		self.peerCtrlIndex = FindFreePeerCtrl()
		self.SetName("#%03i" % self.peerCtrlIndex)
		
	def __del__(self):
		#self.ClearTarget()
		pass
	
	def ClearTarget(self):
		ClearPeerCtrlTarget(self.peerCtrlIndex)
		self.SetName("")
	
	def GetIndex(self):
		return self.peerCtrlIndex
	
	def SetTarget(self,machine,group,paramIndex):
		SetPeerCtrlTarget(self.peerCtrlIndex,machine,group,paramIndex)
	
	def GetTarget(self):
		return GetPeerCtrlTarget(self.peerCtrlIndex)
		
	def SendChange(self,trackIndex,value):
		SendPeerCtrlChangeNextTick(self.peerCtrlIndex,trackIndex,value)
		
	def GetValue(self,trackIndex):
		return GetPeerCtrlValue(self.peerCtrlIndex,trackIndex)

	def HasTarget(self):
		if GetPeerCtrlTarget(self.peerCtrlIndex):
			return 1
		else:
			return 0
	
	def GetMachineInfo(self):
		machine = self.GetMachine()
		if machine:
			return UnpackMachineInfo(GetMachineInfo(machine))
		return None
	
	def GetMachineName(self):
		machine = self.GetMachine()
		if machine:
			return GetMachineName(machine)
		return None
	
	def GetMachine(self):
		target = self.GetTarget()
		if target:
			return target[0]
		return None

	def GetGroup(self):
		target = self.GetTarget()
		if target:
			return target[1]
		return None
		
	def GetParameterIndex(self):
		target = self.GetTarget()
		if target:
			return target[2]
		return None

	def GetMachineParameterInfos(self):
		target = self.GetTarget()
		if target:
			info = self.GetMachineInfo()
			params = info[7]
			gpi = params[:info[5]]
			tpi = params[info[5]:]
			return (gpi,tpi)
		return None
	
	def GetParameterInfo(self):
		target = self.GetTarget()
		if target:
			info = self.GetMachineInfo()
			params = info[7]
			gpi = params[:info[5]]
			tpi = params[info[5]:]
			if self.GetGroup() == 1:
				return gpi[self.GetParameterIndex()]
			else:
				return tpi[self.GetParameterIndex()]
		return None
	
	def SetName(self,name):
		SetPeerCtrlName(self.peerCtrlIndex,name)
		
	def GetName(self):
		return GetPeerCtrlName(self.peerCtrlIndex)

class Track:
	def __init__(self,index):
		self.note = MakeNote(4,NOTE_C)
		self.playPos = -1
		self.peerCtrl = index
		self.track = 0
		self.play = FALSE
		self.seedCount = 303
		self.sequence = [None]*16
		self.playPos = -1
		self.wait = 0
		self.speed = 1
		self.wait = 0
		self.loop = TRUE
		self.glide = FALSE
		self.index = index
		self.name = "Track #%02i" % index

	def SetPeerCtrl(self,ctrl):
		self.peerCtrl = ctrl

	def SetSeed(self,seedCount):
		self.seedCount = seedCount

	def SetNote(self,note):
		self.note = note

	def SetTrack(self,track):
		self.track = track

	def SetSequenceSize(self,size):
		if size == len(self.sequence):
			return
		seq = self.sequence
		self.sequence = [None]*size
		self.playPos = -1
		self.wait = 0
		self.Union(seq)
		#self.Randomize()

	def Add(self,line):
		if (line >= 0) and (line < len(self.sequence)):
			self.sequence[line] = 1

	def Del(self,line):
		if (line >= 0) and (line < len(self.sequence)):
			self.sequence[line] = None

	def SetLoop(self,loop):
		self.loop = loop
		
	def SetGlide(self,glide):
		self.glide = glide

	def SetSpeed(self,speed):
		if (speed >= 1) and (speed <= 128):
			self.speed = speed
			self.wait = 0

	def Cue(self):
		for i in xrange(len(self.sequence)):
			if self.sequence[i] and i:
				self.Roll(-i)
				return

	def Roll(self,amount):
		seqlen = len(self.sequence)
		newseq = [None]*seqlen
		for i in xrange(seqlen):
			ni = (i+amount) % seqlen
			newseq[ni] = self.sequence[i]
		self.sequence = newseq
	
	def Fill(self):
		self.sequence = [1]*len(self.sequence)

	def DoubleSize(self):
		seqlen = len(self.sequence)
		newseq = [None]*(seqlen*2)
		for i in xrange(seqlen):
			newseq[i*2] = self.sequence[i]
		self.sequence = newseq
	
	def HalfSize(self):
		seqlen = len(self.sequence)
		if seqlen <= 1:
			return
		newseq = [None]*(seqlen/2)
		for i in xrange(seqlen/2):
			newseq[i] = self.sequence[i*2]
		self.sequence = newseq
		if self.playPos >= (seqlen/2):
			self.playPos = -1
			self.wait = 0

	def Invert(self):
		for i in xrange(len(self.sequence)):
			if self.sequence[i]:
				self.sequence[i] = None
			else:
				self.sequence[i] = 1
		
	def PrintInfo(self):
		print "Property  | Value"
		print "----------+--------"
		print "Index #   | %i" % self.index
		print "PeerCtrl #| %i" % self.peerCtrl
		print "Track #   | %i" % self.track
		print "Length    | %i" % len(self.sequence)
		print "Speed     | %i" % self.speed
		print "Seed      | %i" % self.seedCount
		print "PlayPos   | %i" % self.playPos
		print "Loop?     | %i" % self.loop
		print "Glide?    | %i" % self.glide
	
	def Clear(self):
		self.sequence = [None]*len(self.sequence)

	def Save(self):
		return (2,self.note,self.sequence,self.peerCtrl,self.track,self.speed,self.seedCount,self.loop,self.glide,self.name)

	def Load(self,data):
		if (data[0] == 1):
			version,self.note,self.sequence,self.peerCtrl,self.track,self.speed,self.seedCount,self.loop,self.glide = data
		elif (data[0] == 2):
			version,self.note,self.sequence,self.peerCtrl,self.track,self.speed,self.seedCount,self.loop,self.glide,self.name = data
		
	def Randomize(self):
		seed(self.seedCount)
		for i in xrange(len(self.sequence)):
			if randint(0,1):
				self.sequence[i] = 1
			
	def Play(self):
		self.playPos = 0
		
	def Stop(self):
		self.playPos = -1

	def SetSequence(self,seq):
		self.sequence = seq

	def GetSequence(self):
		return self.sequence

	def Union(self,seq):
		seqlenb = len(seq)
		seqlena = len(self.sequence)
		if seqlenb < seqlena:
			seqlen = seqlenb
		else:
			seqlen = seqlena
		for i in xrange(seqlen):
			if seq[i]:
				self.sequence[i] = seq[i]
	
	def Subtract(self,seq):
		seqlenb = len(seq)
		seqlena = len(self.sequence)
		if seqlenb < seqlena:
			seqlen = seqlenb
		else:
			seqlen = seqlena
		for i in xrange(seqlen):
			if seq[i]:
				self.sequence[i] = None

	def Show(self):
		s = '['
		for i in xrange(len(self.sequence)):
			if self.sequence[i]:
				s += str(self.sequence[i])
			else:
				s += '.'
		s += ']'
		print s
	
	def Tick(self):
		note = GetParameter(self.index,0)
		byte = GetParameter(self.index,1)
		word = GetParameter(self.index,2)
		if (byte == 255):
			pass
		else:
			if (word == 65535):
				pass
			elif byte == 0x01: # set pattern length
				self.SetSequenceSize(word)
			elif byte == 0x02: # set seed and randomize
				self.seedCount = word
				self.Randomize()			
			elif byte == 0x03: # set note length
				self.speed = word
				self.wait = 0
			elif byte == 0x04: # set loop
				self.loop = word
			elif byte == 0x05: # set glide
				self.glide = word
			elif byte == 0x06: # set track
				self.track = word
		if (note == NOTE_NO):
			pass
		elif (note == NOTE_OFF):
			self.playPos = -1
		else:
			if (not self.glide) or (self.playPos == -1):
				self.playPos = 0
				self.wait = 0			
			self.note = note
			
		if self.playPos == -1:
			return
		note = self.sequence[self.playPos]
		if (self.wait == 0) and note and (self.peerCtrl != -1):
			SendPeerCtrlChange(self.peerCtrl,self.track,self.note)
		self.wait += 1
		if self.wait >= self.speed:
			self.wait = 0
			self.playPos += 1
		if self.playPos >= len(self.sequence):
			if self.loop:
				self.playPos = 0
			else:
				self.playPos = -1
			return

def SetPeerNames(nameList):
	for i in xrange(len(nameList)):
		SetPeerCtrlName(i,nameList[i])

def DumpMachineData(name):
	for i in GetMachines():
		n = GetMachineName(i)
		if (n == name) or (not name):
			print GetMachinePosition(i)
			print n
			Dump(i,0,256)
			print

def ReposMachines():
	machines = GetMachines()
	machineCount = len(machines)
	nx = 2.0 / float(machineCount)
	x = -0.9
	y = 0.0
	for i in GetMachines():
		SetMachinePosition(i,x,y)
		x += nx

def AssignPeerCtrls():
	peerGroups = []
	for i in GetMachines():
		n = GetMachineName(i)
		info = UnpackMachineInfo(GetMachineInfo(i))
		if info[10] != "Quence PyBuzz":
			peers = []
			params = info[7]
			gpi = params[:info[5]]
			tpi = params[info[5]:]	
			index = 0
			for p in gpi:		
				peer = PeerCtrl()
				peer.SetTarget(i,1,index)
				index += 1
				peers.append(peer)
			index = 0
			for p in tpi:
				peer = PeerCtrl()
				peer.SetTarget(i,2,index)
				index += 1
				peers.append(peer)
			peerGroups.append(peers)
	return peerGroups


miType = 0
miVersion = 1
miFlags = 2
miMinTracks = 3
miMaxTracks = 4
miNumGlobalParameters = 5
miNumTrackParameters = 6
miParameterInfos = 7
miNumAttributes = 8
miAttributeInfos = 9
miName = 10
miShortName = 11
miAuthor = 12
miCommands = 13

piType = 0
piName = 1
piDescription = 2
piMinValue = 3
piMaxValue = 4
piNoValue = 5
piFlags = 6
piDefaultValue = 7
