# ---------------------------------------------------------------------------- # 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 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 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