# $Id:$

from pyglet.media import Source, AudioFormat, AudioData

import ctypes
import os
import math

class ProceduralSource(Source):
    def __init__(self, duration, sample_rate=44800, sample_size=16):
        self._duration = float(duration)
        self.audio_format = AudioFormat(
            channels=1,
            sample_size=sample_size,
            sample_rate=sample_rate)

        self._offset = 0
        self._bytes_per_sample = sample_size >> 3
        self._bytes_per_second = self._bytes_per_sample * sample_rate
        self._max_offset = int(self._bytes_per_second * self._duration)
        
        if self._bytes_per_sample == 2:
            self._max_offset &= 0xfffffffe

    def _get_audio_data(self, bytes):
        bytes = min(bytes, self._max_offset - self._offset)
        if bytes <= 0:
            return None
        
        timestamp = float(self._offset) / self._bytes_per_second
        duration = float(bytes) / self._bytes_per_second
        data = self._generate_data(bytes, self._offset)
        self._offset += bytes
        is_eos = self._offset >= self._max_offset

        return AudioData(data,
                         bytes,
                         timestamp,
                         duration)

    def _generate_data(self, bytes, offset):
        '''Generate `bytes` bytes of data.

        Return data as ctypes array or string.
        '''
        raise NotImplementedError('abstract')

    def _seek(self, timestamp):
        self._offset = int(timestamp * self._bytes_per_second)

        # Bound within duration
        self._offset = min(max(self._offset, 0), self._max_offset)

        # Align to sample
        if self._bytes_per_sample == 2:
            self._offset &= 0xfffffffe

class Silence(ProceduralSource):
    def _generate_data(self, bytes, offset):
        if self._bytes_per_sample == 1:
            return '\127' * bytes
        else:
            return '\0' * bytes

class WhiteNoise(ProceduralSource):
    def _generate_data(self, bytes, offset):
        return os.urandom(bytes)

class Sine(ProceduralSource):
    def __init__(self, duration, frequency=440, **kwargs):
        super(Sine, self).__init__(duration, **kwargs)
        self.frequency = frequency
        
    def _generate_data(self, bytes, offset):
        if self._bytes_per_sample == 1:
            start = offset
            samples = bytes
            bias = 127
            amplitude = 127
            data = (ctypes.c_ubyte * samples)()
        else:
            start = offset >> 1
            samples = bytes >> 1
            bias = 0
            amplitude = 32767
            data = (ctypes.c_short * samples)()
        step = self.frequency * (math.pi * 2) / self.audio_format.sample_rate
        for i in range(samples):
            data[i] = int(math.sin(step * (i + start)) * amplitude + bias)
        return data

class Saw(ProceduralSource):
    def __init__(self, duration, frequency=440, **kwargs):
        super(Saw, self).__init__(duration, **kwargs)
        self.frequency = frequency
        
    def _generate_data(self, bytes, offset):
        # XXX TODO consider offset
        if self._bytes_per_sample == 1:
            samples = bytes
            value = 127
            max = 255
            min = 0
            data = (ctypes.c_ubyte * samples)()
        else:
            samples = bytes >> 1
            value = 0
            max = 32767
            min = -32768
            data = (ctypes.c_short * samples)()
        step = (max - min) * 2 * self.frequency / self.audio_format.sample_rate
        for i in range(samples):
            value += step
            if value > max:
                value = max - (value - max)
                step = -step
            if value < min:
                value = min - (value - min)
                step = -step
            data[i] = value
        return data

class Square(ProceduralSource):
    def __init__(self, duration, frequency=440, **kwargs):
        super(Square, self).__init__(duration, **kwargs)
        self.frequency = frequency
        
    def _generate_data(self, bytes, offset):
        # XXX TODO consider offset
        if self._bytes_per_sample == 1:
            samples = bytes
            value = 0
            amplitude = 255
            data = (ctypes.c_ubyte * samples)()
        else:
            samples = bytes >> 1
            value = -32768
            amplitude = 65535
            data = (ctypes.c_short * samples)()
        period = self.audio_format.sample_rate / self.frequency / 2
        count = 0
        for i in range(samples):
            count += 1
            if count == period:
                value = amplitude - value
                count = 0
            data[i] = value
        return data
