#!/usr/bin/env python

"""
Simple script which plays a vsti synth through a vst effect,
randomizing parameters.
"""

import time
import nosuch.vstutil
import sys
import random
import math
from nosuch.vstutil import *
from math import sin,pi
from threading import Thread

# Utility functions

def vstinfo(v1,doparams=0):
	print "VST: ",v1.name()
	print "        product = ",v1.product_string()
	print "        Is VST2 = ",v1.is_vst2()
	print "        Is synth = ",v1.is_synth()
	print "        num_programs = ",v1.num_programs()
	print "        num_params = ",v1.num_params()
	print "        num_inputs = ",v1.num_inputs()
	print "        num_outputs = ",v1.num_outputs()
	print "        can_receive_midi = ",v1.can_receive_midi()

def vstrandparam(p):
	for i in range(p.num_params()):
		f = random.random()
		p.set_param(i,f)

def vstmutateparam(p,amount):
	for i in range(p.num_params()):
		f = p.get_param(i)
		f += amount * random.random() - amount/2
		if f < 0.0:
			f = 0.0
		elif f > 1.0:
			f = 1.0
		p.set_param(i,f)

class lfo:
	def __init__(self,tm0,cycle_leng):
		self.hz = 1.0/cycle_leng
		self.onzero_f = None
		self.onchange_f = None
		self.tm0 = tm0
		self.tm = tm0
		self.v = self.value()

	def on_zerocrossing(self,f):
		self.onzero_f = f

	def on_change(self,f,arg=None):
		self.onchange_f = f
		self.onchange_arg = arg

	def value(self):
		dt = self.tm - self.tm0
		return sin(pi*dt*self.hz)

	def advance_to(self,tm):
		self.tm = tm
		nv = self.value()
		if self.onzero_f != None and (( self.v < 0.0 and nv > 0.0 ) or ( self.v > 0.0 and nv < 0.0 )):
			self.onzero_f.bang()
		self.v = nv
		if self.onchange_f != None:
			self.onchange_f.bang(self.v,self.onchange_arg)

class lfothread(Thread):

	def __init__(self):
		self.keepgoing = 1
		self.lfos = []
		Thread.__init__(self)

	def add(self,lfo):
		self.lfos.append(lfo)

	def run(self):
		dt = 0.01
		while self.keepgoing:
			tm = time.time()
			for lfo in self.lfos:
				lfo.advance_to(tm)
			time.sleep(dt)

	def stop(self):
		self.keepgoing = 0

# Main script

if len(sys.argv) < 4:
	print "Usage: demo1 {vstdir} {synthdll} {effectdll} [{note-pitch} {note-seconds} {totaltime}]"
	print ""
	print "Set midipitch to 0 to play random notes"
	sys.exit(1)

if len(sys.argv) > 4:
	notepitch = int(sys.argv[4])
else:
	notepitch = 64

if len(sys.argv) > 5:
	notelength = float(sys.argv[5])
else:
	notelength = 0.3

if len(sys.argv) > 6:
	totaldur = float(sys.argv[6])
else:
	totaldur = 60.0

vstdir = sys.argv[1]
synthdll = sys.argv[2]
effectdll = sys.argv[3]

# Instantiate the VST plugins
effect = VstPlugin(dll=vstdir+"\\"+effectdll)
vstinfo(effect,doparams=0)
synth = VstPlugin(dll=vstdir+"\\"+synthdll)
vstinfo(synth,doparams=0)

# Start the audio device
audio = PaAudioDevice()
audio.open()
audio.start()

# Connect the output of the synth to the input of the effect
effect.setinput(synth)

# Connect the output of the effect to the audio device
audio.attach(effect)

if notepitch < 1:
	print "Now sending random notes and randomizing parameters every ",notelength," seconds for ",totaldur," seconds"
else:
	print "Now sending note ",notepitch," and randomizing parameters every ",notelength," seconds for ",totaldur," seconds"

# Play notes and randomize parameters

class randomizer:
	def __init__(self,vst):
		self.vst = vst

	def bang(self):
		# print "Randomizing vst=",self.vst
		vstrandparam(self.vst)

class mutator:
	def __init__(self,vst,amount):
		self.vst = vst
		self.amount = amount

	def bang(self,val,arg):
		# print "Mutating vst=",self.vst," val=",val
		vstmutateparam(self.vst,self.amount)


lfos = lfothread()
lfos.start()

tm0 = time.time()

lfo1 = lfo(tm0,3)
lfo1.on_zerocrossing(randomizer(synth))
lfos.add(lfo1)

lfo2 = lfo(tm0,5)
lfo2.on_zerocrossing(randomizer(effect))
lfos.add(lfo2)

lfo3 = lfo(tm0,5)
lfo3.on_change(mutator(effect,0.05))
lfos.add(lfo3)

endtime = totaldur + time.time()
while time.time() < endtime:
	if notepitch < 1:
		pt = int(random.random() * 128) % 128
	else:
		pt = notepitch

	# Send a MIDI note-on and note-off to the VST synth
	print "sending noteon tm=",time.time()
	synth.send_midi(1,pt,8000,80)

	try:
		time.sleep(notelength)
	except:
		print "Exception in time.sleep()"
		break

	synth.send_midi(1,pt,8000,0)

lfos.stop()

# Stop the audio device
audio.stop()
audio.close()

sys.exit(0)
