import random
import array
import math
import itertools
import pyaudio
import time
def const(c):
    return lambda t: c
def sine(f,a,fm=const(0)):
    return lambda t: a(t) * math.sin(t * f * 2.0 * math.pi + fm(t))
def lfo(b,f,a):
    return lambda t: b + (a * math.sin(t * f * 2.0 * math.pi)/2.0)
def sumgen(gens):
    while 1:
        sum = 0
        dels = []
        for i,g in enumerate(gens):
            try:
                sum += next(g)
            except StopIteration:
                dels.append(gens[i])
        for x in dels:
           gens.remove(x)
        if dels:
            print (len(dels),len(gens))
        if not gens:
            return
        yield sum
def adsrgen(gen,rate,g,a,d,sl,s,r):
    for i,v in enumerate(gen):
        t = float(i)/rate
        if t < a:
            yield t/a * g * v
        elif t < a + d:
            yield (1.0 - (t - a)/d * (1.0 - sl)) * g * v
        elif t < a + d + s:
            yield sl * g * v
        elif t < a + d + s + r:
            yield sl * (1.0 - (t - a - d - s)/r) * g * v
        else:
            raise StopIteration
def clamp(x,bits):
    if x >  2**bits/2-1: return  int( 2**bits/2-1)
    if x < -2**bits/2:   return  int(-2**bits/2)
    return x
def sample(sig,rate):
    i = 0
    while 1:
        yield sig(float(i)/rate)
        i += 1
def digitize(smp,bits):
    sc = 2**bits/2
    while 1:
        yield clamp(int(next(smp) * sc), bits)
def pcm(sig,rate,bits):
    return digitize(sample(sig,rate),bits)
def taken(g,n):
    i = 0
    while i < n:
        yield next(g)
        i += 1
sb=16
sr=44100
sn=44100
tempered=[pow(2,float(i)/12) for i in range(0,12)]
major=[tempered[i] for i in [0,2,4,5,7,9,11]]
minor=[tempered[i] for i in [0,2,3,5,7,8,10]]
minor=[f/2 for f in minor]+minor+[f*2 for f in minor]
arange=(0.2,0.05)
bpm=160
bps=bpm/60.0
spb=1.0/bps
def vibra(f,a,vf,va,tf,ta,maxdur):
    return adsrgen(sample(sine(f,lfo(a,tf,a*ta),lfo(0,vf,va)),sr),sr,1,0.001,0.005,0.1,0,maxdur)
gens=[taken(sample(const(0),sr),sr)]
smps=digitize(sumgen(gens),sb)
def snd_output(incoming, frames, time_info, status):
    data = array.array('h',taken(smps,frames))
    return (data.tostring(), pyaudio.paContinue)
p = pyaudio.PyAudio()
ps = p.open(format=pyaudio.paInt16,
            channels=1,
            rate=sr,
            output=True,
            stream_callback=snd_output)
random.seed(1)
i=1
while ps.is_active():
    print(i,len(gens),ps.get_cpu_load())
    time.sleep(spb)
    ntones=random.randrange(3)
    if (len(gens) + ntones > 4):
        continue
    tones=random.sample(minor,ntones)
    for f in tones:
        a = random.uniform(*arange)
        gens.append(vibra(440*f,a/f,3.3,0.9,3.2,1,5))
    i += 1
ps.stop_stream()
ps.close()
p.terminate()