#!/usr/bin/env python

import pygtk
pygtk.require ("2.0")
import gobject
gobject.threads_init()

import pygst
pygst.require('0.10')
import gst

from math import pi, sin

import sys
import traceback

BEEP_LENGTH=gst.SECOND/20
BEEP_FREQ=800

class PyAudioBeep(gst.Element):
    _sinkpadtemplate = gst.PadTemplate ("sink",
                                         gst.PAD_SINK,
                                         gst.PAD_ALWAYS,
                                         gst.caps_from_string ("audio/x-raw-int,endianness=1234,signed=(boolean)false,width=16,depth=16"))

    _srcpadtemplate = gst.PadTemplate ("src",
                                         gst.PAD_SRC,
                                         gst.PAD_ALWAYS,
                                         gst.caps_from_string ("audio/x-raw-int,endianness=1234,signed=(boolean)false,width=16,depth=16"))

    def __init__(self):
        gst.Element.__init__(self)

        self.sinkpad = gst.Pad(self._sinkpadtemplate, "sink")
        self.sinkpad.set_chain_function(self.chainfunc)
        self.sinkpad.set_event_function(self.eventfunc)
        self.sinkpad.set_getcaps_function(gst.Pad.proxy_getcaps)
        self.sinkpad.set_setcaps_function(gst.Pad.proxy_setcaps)
        self.add_pad (self.sinkpad)

        self.srcpad = gst.Pad(self._srcpadtemplate, "src")

        self.srcpad.set_event_function(self.srceventfunc)
        self.srcpad.set_query_function(self.srcqueryfunc)
        self.srcpad.set_getcaps_function(gst.Pad.proxy_getcaps)
        self.srcpad.set_setcaps_function(gst.Pad.proxy_setcaps)
        self.add_pad (self.srcpad)

    def chainfunc(self, pad, buffer):
        try:
            outbuf = buffer.copy_on_write ()
            self.draw_on (outbuf)
            return self.srcpad.push (outbuf)
        except:
            return GST_FLOW_ERROR

    def eventfunc(self, pad, event):
        return self.srcpad.push_event (event)
        
    def srcqueryfunc (self, pad, query):
        return self.sinkpad.query (query)
    def srceventfunc (self, pad, event):
        return self.sinkpad.push_event (event)

    def draw_on(self, buf):
        # Render 50ms beeps at the mark of each second
        caps = buf.get_caps()
        rate = caps[0]['rate']
        chans = caps[0]['channels']
        pos = buf.timestamp % gst.SECOND
        dur = (buf.size / (2 * chans)) * gst.SECOND / rate
        end = (buf.timestamp + dur) % gst.SECOND
           
        if pos <= BEEP_LENGTH or end < BEEP_LENGTH or dur >= gst.SECOND:
            # start_ts within a beep:
            cur_ts = buf.timestamp
            end_ts = cur_ts + dur

            if pos <= BEEP_LENGTH:
                start = buf.timestamp
                end = min (start + BEEP_LENGTH - pos, end_ts)
                try:
                    self.render_region (buf, start, end - start)
                except:
                    traceback.print_exc()

            # Set cur_ts to the end of the 'current' beep region
            cur_ts += BEEP_LENGTH - pos + gst.SECOND
       
            while cur_ts < end_ts + BEEP_LENGTH:
                # Remaining beeps within the buffer
                start = max (buf.timestamp, cur_ts - BEEP_LENGTH)
                end = min (cur_ts, end_ts)
                try:
                    self.render_region (buf, start, end - start)
                except:
                    traceback.print_exc()
                cur_ts += gst.SECOND

    def render_region(self,buf,start,len):
        caps = buf.get_caps()
        rate = caps[0]['rate']
        chans = caps[0]['channels']
        bps = 2 * chans

        # Render a BEEP_FREQ Hz sine wave, sampled at 'rate' Hz
        buf_start = buf.timestamp * rate / gst.SECOND
        cur_sample = start * rate / gst.SECOND
        end_sample = (start + len) * rate / gst.SECOND

        factor = BEEP_FREQ * 2* pi / rate
        offset = (cur_sample - buf_start) * bps

        while cur_sample < end_sample:
            val = int (16384 * sin (cur_sample * factor))

            for i in range(chans):
                in_val = (int (ord (buf[offset + 1])) << 8)
                in_val += ord (buf[offset + 0])
                in_val -= 32768

                out_val = int ((in_val) * 0.75) + val + 32768
                if out_val > 65535:
                   out_val = 65535
                if out_val < 0:
                   out_val = 0
                buf[offset + 0] = chr (out_val % 256)
                buf[offset + 1] = chr (out_val / 256)
                offset += 2
            cur_sample += 1

def on_new_decoded_pad (element, pad, last):
    global cs
    try:
       pad.link (cs.get_pad ('sink'))
    except:
       pass

gobject.type_register(PyAudioBeep)

if __name__ == "__main__":
    pipe = gst.Pipeline()

    if len(sys.argv) > 1:
        src = gst.element_factory_make ("filesrc")
        fname = sys.argv[1]
        src.set_property ("location", fname)
        db = gst.element_factory_make ("decodebin")
        cs = gst.element_factory_make ("audioconvert")
    else:
        src = gst.element_factory_make ("audiotestsrc")
        db = None
        cs = None
    
    c1 = PyAudioBeep()
    color = gst.element_factory_make ("audioconvert")
    scale = gst.element_factory_make ("audioresample")
    q1 = gst.element_factory_make ("queue")
    sink = gst.element_factory_make ("autoaudiosink")
    
    if db is not None:
       pipe.add (src, db, cs, c1, q1, color, scale, sink)
       gst.element_link_many (src, db)
       gst.element_link_many (cs, c1, q1, color, scale, sink)
       db.connect ('new-decoded-pad', on_new_decoded_pad)
    else:
       pipe.add (src, c1, q1, color, scale, sink)
       gst.element_link_many (src, c1, q1, color, scale, sink)
    
    def on_eos (bus, msg):
        mainloop.quit()
    
    bus = pipe.get_bus()
    bus.add_signal_watch()
    bus.connect('message::eos', on_eos)
    
    pipe.set_state (gst.STATE_PLAYING)
    
    mainloop = gobject.MainLoop()
    try:
        mainloop.run()
    except:
        pass

    pipe.set_state (gst.STATE_NULL)
    pipe.get_state (gst.CLOCK_TIME_NONE)


