181 lines
6.3 KiB
Python
181 lines
6.3 KiB
Python
# ----------------------------------------------------------------------------
|
|
# 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
|