text: # ----------------------------------------------------------------------------
  # fos.lib.pyglet
  # Copyright (c) 2006-2008 Alex Holkner
  # All rights reserved.
  # 
  # Redistribution and use in source and binary forms, with or without
  # modification, are permitted provided that the following conditions 
  # are met:
  #
  #  * Redistributions of source code must retain the above copyright
  #    notice, this list of conditions and the following disclaimer.
  #  * Redistributions in binary form must reproduce the above copyright 
  #    notice, this list of conditions and the following disclaimer in
  #    the documentation and/or other materials provided with the
  #    distribution.
  #  * Neither the name of fos.lib.pyglet nor the names of its
  #    contributors may be used to endorse or promote products
  #    derived from this software without specific prior written
  #    permission.
  #
  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  # POSSIBILITY OF SUCH DAMAGE.
  # ----------------------------------------------------------------------------
  # $Id:$
  
  from fos.lib.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
  
          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
  