''' Use this example from an interactive python session, for example:

>>> from isine import change
>>> change(880)
'''

from threading import Thread
from Queue import Queue, Empty
from math import pi, sin
import struct
import alsaaudio

sampling_rate = 44100
format = alsaaudio.PCM_FORMAT_S16_LE
framesize = 2 # bytes per frame for the values above

def digitize(s):
    if s > 1.0 or s < -1.0:
        raise ValueError
        
    return struct.pack('h', int(s * 32767)) 

def generate(frequency):
    # generate a buffer with a sine wave of frequency
    size = int(sampling_rate / frequency)
    buffer = ''
    for i in range(size):
        buffer = buffer + digitize(sin(i/(2 * pi)))

    return buffer

class SinePlayer(Thread):
    
    def __init__(self, frequency = 440.0):
        Thread.__init__(self)
        self.setDaemon(True)
        self.device = alsaaudio.PCM()
        self.device.setchannels(1)
        self.device.setformat(format)
        self.device.setrate(sampling_rate)
        self.queue = Queue()
        self.change(frequency)

    def change(self, frequency):
        '''This is called outside of the player thread'''
        # we generate the buffer in the calling thread for less
        # latency when switching frequencies
        

        # More than 100 writes/s are pushing it - play multiple buffers
        # for higher frequencies

        factor = int(frequency/100.0)
        if factor == 0:
            factor = 1
        
        buf = generate(frequency) * factor
        print 'factor: %d, frames: %d' % (factor, len(buf) / framesize)

        self.queue.put( buf)
                        
    def run(self):
        buffer = None
        while True:
            try:
                buffer = self.queue.get(False)
                self.device.setperiodsize(len(buffer) / framesize)
                self.device.write(buffer)
            except Empty:
                if buffer:
                    self.device.write(buffer)
                

isine = SinePlayer()
isine.start()

def change(f):
    isine.change(f)
    
