# PyEPL: hardware/sound/sound.pyx
#
# Copyright (C) 2003-2005 Michael J. Kahana
# Authors: Ian Schleifer, Per Sederberg, Aaron Geller, Josh Jacobs
# URL: http://memory.psych.upenn.edu/programming/pyepl
#
# Distributed under the terms of the GNU Lesser General Public License
# (LGPL). See the license.txt that came with this file.

"""
Module for sound functionality.
"""

import eplSound
    
cdef extern from "Python.h":
    int PyString_AsStringAndSize(object, char **, int *)
    object PyString_FromStringAndSize(char *, int)

cdef extern from "stdlib.h":
    void *malloc(int)
    void free(void *)

# some declarations
FORMAT_SIZE = 2
SCALE = 32767.0
SAMPLE_RATE = (44100)
PLAY_BUF_LEN = 60   # in seconds
REC_BUF_LEN = 60      # in seconds
NUM_CHANNELS = 2    # Assumes 2

# Added for easy external sound interface
import time
import threading

snd = None
numInitialized = 0

# python wrappers
def construct(recBufLen=REC_BUF_LEN, playBufLen=PLAY_BUF_LEN, sampleRate=SAMPLE_RATE):
    """
    """
    global REC_BUF_LEN
    global PLAY_BUF_LEN
    global SAMPLE_RATE
    global snd
    snd = eplSound.eplSound(recBufLen,playBufLen,sampleRate)

    # Update the globals for other functions
    REC_BUF_LEN = recBufLen
    PLAY_BUF_LEN = playBufLen
    SAMPLE_RATE = sampleRate
    
def eplSound_delete():
    """
    """
    global snd
    del snd

def append(s, int ow=0, float ampFactor=1.0):
    """
    Convert Python string to array, pass on to C++ append.
    """
    global snd
    return snd.append(s, len(s)/sizeof(short), ow, ampFactor)

def consume(long leng):
    """
    Get a C-array from C++ consume, convert to a Python string.
    """
    global snd
    s = PyString_FromStringAndSize(NULL, leng*sizeof(short))
    consumed = snd.consume(s,leng)
    #print consumed
    return s[0:consumed*sizeof(short)]
def clear():
    """
    """
    global snd
    return snd.clear()

def recstart():
    """
    """
    global snd
    return snd.recstart()

def recstop():
    """
    """
    global snd
    return snd.recstop()

def startstream():
    """
    """
    global snd
    return snd.startstream()

def stopstream():
    """
    """
    global snd
    return snd.stopstream()

def getSamplesPlayed():
    """
    """
    global snd
    return snd.getSamplesPlayed()

def resetSamplesPlayed():
    """
    """
    global snd
    snd.resetSamplesPlayed()

def getRecChans():
    """
    """
    global snd
    return snd.getRecChans()

def getPlayChans():
    """
    """
    global snd
    return snd.getPlayChans()

def getSampleRate():
    """
    """
    global snd
    return snd.getSampleRate()


# Easy interface to outside

def init(recBufLen=REC_BUF_LEN, playBufLen=PLAY_BUF_LEN, sampleRate=SAMPLE_RATE):
    """
    Construct, allocate, and start the sound stream.
    """
    global numInitialized
    if numInitialized == 0:
        numInitialized = numInitialized + 1
        construct(recBufLen, playBufLen, sampleRate)
        startstream()

# threadin stuph
__playThreadActive__ = 0
def __playThread__(s, int ow, float ampFactor=1.0):
    """
    Thread to append data periodically if play fifo is full.
    """
    
    global __playThreadActive__
    #print "In thread"
    # try and append
    ind = 0
    s = s[ind::]
    #appended = append(s, ow, ampFactor)
    appended = 0
    while appended < len(s)/FORMAT_SIZE and __playThreadActive__==1:
     
        # try and append again
        appended = append(s, ow, ampFactor)
        #print "Appended %d" % (appended)

        # reset start index
        ind = appended*FORMAT_SIZE
        s = s[ind::]
   
        # Wait for .25 the bufferlen
        time.sleep(PLAY_BUF_LEN*.25)


    #print "Thread Complete"
    __playThreadActive__ = 0

def playStart(s, int ow=0, float ampFactor=1.0):
    """
    Append data to the stream, optionally overwriting the fifo and/or
    applying a gain factor.

    This function resets the samples played.
    """

    cdef long appended
    global __playThreadActive__
    
    # reset the samples
    resetSamplesPlayed()

    # append the data
    appended = append(s, ow, ampFactor)

    # check to see if all was appended
    if appended < len(s)/FORMAT_SIZE:
        # must start thread to append
        #print "Not all appended"
        if __playThreadActive__ == 0:
            playThread = threading.Thread(name="eplSound play thread",
                                          target=__playThread__,
                                          args = (s[(appended*FORMAT_SIZE)::],ow,1)) # amp factor is already applied
            __playThreadActive__ = 1
            playThread.start()
            #print "Started thread"
            
    return appended


def playStop():
    """
    Stop playing immediately and return the number of samples played.
    """
    global __playThreadActive__
    
    # see if thread going
    if __playThreadActive__==1:
        # stop it
        __playThreadActive__ = 0
        
    # stop playing
    clear()

    # return the number of samples
    return getSamplesPlayed()




def deinit():
    """
    Stop and destruct the sound stream.
    """
    global numInitialized
    numInitialized = numInitialized - 1
    playStop()
    if numInitialized == 0:
        stopstream()
        eplSound_delete()
    
