"""Load and play sounds
There are various APIs for this, none of which are perfect. By default PsychoPy will
look for and use, in this order: ['pygame','pyglet','pyaudio']
The API chosen will be stored as a string under
sound.audioAPI
and can be set using, e.g.
sound.setAudioAPI('pyglet')
pygame (must be version 1.8 or above):
pros: The most robust of the API options so far - it works consistently on all platforms
cons: needs an additional download, poor latencies
pyglet:
pros: comes with enthought python and is already the main API for drawing in PsychoPy
cons: complex model using event_dispatch, dodgy timing (just on win32?)
pyaudio:
pros: relatively low-level wrapper around portAudio
cons: needs another download, rather buggy..
"""
import numpy, threading, time
from os import path
from string import capitalize
from sys import platform,exit,stdout
from psychopy import event,core,log
if platform=='win32':
mediaLocation="C:\\Windows\Media"
else:
mediaLocation=""
preferredAPI = ['pygame','pyglet','pyaudio']
global audioAPI, Sound
Sound = None
audioAPI=None
try:
import pyglet
pyglet.options['audio'] = ('silent')
import pyglet.media.procedural
import pyglet.media#, pyglet.resource
import ctypes, math
havePyglet=True
except:
havePyglet=False
try:
import pygame
from pygame import mixer,sndarray
havePygame=True
except:
havePygame=False
try:
import pyaudio
pa = pyaudio.PyAudio()
havePyaudio=True
except:
havePyaudio=False
class _SoundBase:
"""Create a sound object, from one of many ways.
"""
def __init__(self,value="C",secs=0.5,octave=4, sampleRate=44100, bits=16):
"""
value: can be a number, string or an array.
If it's a number between 37 and 32767 then a tone will be generated at
that frequency in Hz.
-----------------------------
It could be a string for a note ('A','Bfl','B','C','Csh'...)
- you may want to specify which octave as well
-----------------------------
Or a string could represent a filename in the current
location, or mediaLocation, or a full path combo
-----------------------------
Or by giving an Nx2 numpy array of floats (-1:1) you
can specify the sound yourself as a waveform
secs: is only relevant if the value is a note name or
a frequency value
octave: is only relevant if the value is a note name.
Middle octave of a piano is 4. Most computers won't
output sounds in the bottom octave (1) and the top
octave (8) is generally painful
sampleRate(=44100): only used for sounds using pyglet. Pygame uses one rate for all sounds
sample rate for all sounds (once initialised)
bits(=16): Only 8- and 16-bits supported so far.
Only used for sounds using pyglet. Pygame uses the same
sample rate for all sounds (once initialised)
"""
#try to determine what the sound is
self._snd=None
if type(value) is str:
#try to open the file
OK = self._fromNoteName(value,secs,octave)
#or use as a note name
if not OK: self._fromFile(value)
elif type(value) in [float,int]:
#we've been asked for a particular Hz
self._fromFreq(value, secs)
elif type(value) in [list,numpy.ndarray]:
#create a sound from the input array/list
self._fromArray(value)
if self._snd is None:
raise RuntimeError, "I dont know how to make a "+value+" sound"
def play(self, fromStart=True):
"""Starts playing the sound on an available channel.
If no sound channels are available, it will not play and return None.
This runs off a separate thread i.e. your code won't wait for the
sound to finish before continuing. You need to use a
psychopy.core.wait() command if you want things to pause.
If you call play() whiles something is already playing the sounds will
be played over each other.
"""
pass #should be overridden
def stop(self):
"""Stops the sound immediately"""
pass #should be overridden
def getDuration(self):
pass #should be overridden
def getVolume(self):
"""Returns the current volume of the sound (0.0:1.0)"""
pass #should be overridden
def setVolume(self,newVol):
"""Sets the current volume of the sound (0.0:1.0)"""
pass #should be overridden
def _fromFile(self, fileName):
pass #should be overridden
def _fromNoteName(self, name, secs, octave):
#get a mixer.Sound object from an note name
A=440.0
thisNote=capitalize(name)
stepsFromA = {
'C' : -9,
'Csh' : -8,
'Dfl' : -8,
'D' : -7,
'Dsh' : -6,
'E' : -5,
'F' : -4,
'Fsh' : -3,
'G' : -2,
'Gsh' : -1,
'A': 0,
'Ash':+1,
'Bfl': +1,
'B': +2,
}
if thisNote not in stepsFromA.keys():
return False
thisOctave = octave-4
thisFreq = A * 2.0**(stepsFromA[thisNote]/12.0) * 2.0**thisOctave
self._fromFreq(thisFreq, secs)
def _fromFreq(self, thisFreq, secs):
nSamples = int(secs*self.sampleRate)
outArr = numpy.arange(0.0,1.0, 1.0/nSamples)
outArr *= 2*numpy.pi*thisFreq*secs
outArr = numpy.sin(outArr)
self._fromArray(outArr)
def _fromArray(self, thisArray):
pass #should be overridden
class SoundPygame(_SoundBase):
"""Create a sound object, from one of many ways.
"""
def __init__(self,value="C",secs=0.5,octave=4, sampleRate=44100, bits=16):
"""
value: can be a number, string or an array.
If it's a number between 37 and 32767 then a tone will be generated at
that frequency in Hz.
-----------------------------
It could be a string for a note ('A','Bfl','B','C','Csh'...)
- you may want to specify which octave as well
-----------------------------
Or a string could represent a filename in the current
location, or mediaLocation, or a full path combo
-----------------------------
Or by giving an Nx2 numpy array of floats (-1:1) you
can specify the sound yourself as a waveform
secs: is only relevant if the value is a note name or
a frequency value
octave: is only relevant if the value is a note name.
Middle octave of a piano is 4. Most computers won't
output sounds in the bottom octave (1) and the top
octave (8) is generally painful
sampleRate(=44100): only used for sounds using pyglet. Pygame uses one rate for all sounds
sample rate for all sounds (once initialised)
bits(=16): Only 8- and 16-bits supported so far.
Only used for sounds using pyglet. Pygame uses the same
sample rate for all sounds (once initialised)
"""
#check initialisation
if not mixer.get_init():
pygame.mixer.init(22050, -16, 2, 3072)
inits = mixer.get_init()
if inits is None:
init()
inits = mixer.get_init()
self.sampleRate, self.format, self.isStereo = inits
#try to determine what the sound is
self._snd=None
if type(value) is str:
#try to open the file
OK = self._fromNoteName(value,secs,octave)
#or use as a note name
if not OK: self._fromFile(value)
elif type(value) in [float,int]:
#we've been asked for a particular Hz
self._fromFreq(value, secs)
elif type(value) in [list,numpy.ndarray]:
#create a sound from the input array/list
self._fromArray(value)
if self._snd is None:
raise RuntimeError, "I dont know how to make a "+value+" sound"
def play(self, fromStart=True):
"""Starts playing the sound on an available channel.
If no sound channels are available, it will not play and return None.
This runs off a separate thread i.e. your code won't wait for the
sound to finish before continuing. You need to use a
psychopy.core.wait() command if you want things to pause.
If you call play() whiles something is already playing the sounds will
be played over each other.
"""
self._snd.play()
def stop(self):
"""Stops the sound immediately"""
self._snd.stop()
def fadeOut(self,mSecs):
"""fades out the sound (when playing) over mSecs.
Don't know why you would do this in psychophysics but it's easy
and fun to include as a possibility :)
"""
self._snd.fadeout(mSecs)
def getDuration(self):
"""Get's the duration of the current sound in secs"""
return self._snd.get_length()
def getVolume(self):
"""Returns the current volume of the sound (0.0:1.0)"""
return self._snd.get_volume()
def setVolume(self,newVol):
"""Sets the current volume of the sound (0.0:1.0)"""
self._snd.set_volume(newVol)
def _fromFile(self, fileName):
#try finding the file
self.fileName=None
for filePath in ['', mediaLocation]:
if path.isfile(path.join(filePath,fileName)):
self.fileName=path.join(filePath,fileName)
elif path.isfile(path.join(filePath,fileName+'.wav')):
self.fileName=path.join(filePath,fileName+'.wav')
if self.fileName is None:
return False
#load the file
self._snd = mixer.Sound(self.fileName)
return True
def _fromArray(self, thisArray):
global usePygame
#get a mixer.Sound object from an array of floats (-1:1)
#make stereo if mono
if self.isStereo and \
(len(thisArray.shape)==1 or thisArray.shape[1]<2):
tmp = numpy.ones((len(thisArray),2))
tmp[:,0] = thisArray
tmp[:,1] = thisArray
thisArray = tmp
#get the format right
if self.format == -16:
thisArray= (thisArray*2**15).astype(numpy.int16)
elif self.format == 16:
thisArray= ((thisArray+1)*2**15).astype(numpy.uint16)
elif self.format == -8:
thisArray= (thisArray*2**7).astype(numpy.Int8)
elif self.format == 8:
thisArray= ((thisArray+1)*2**7).astype(numpy.uint8)
self._snd = sndarray.make_sound(thisArray)
return True
class SoundPyglet(_SoundBase):
"""Create a sound object, from one of MANY ways.
"""
def __init__(self,value="C",secs=0.5,octave=4, sampleRate=44100, bits=16):
"""
value: can be a number, string or an array.
If it's a number between 37 and 32767 then a tone will be generated at
that frequency in Hz.
-----------------------------
It could be a string for a note ('A','Bfl','B','C','Csh'...)
- you may want to specify which octave as well
-----------------------------
Or a string could represent a filename in the current
location, or mediaLocation, or a full path combo
-----------------------------
Or by giving an Nx2 numpy array of floats (-1:1) you
can specify the sound yourself as a waveform
secs: is only relevant if the value is a note name or
a frequency value
octave: is only relevant if the value is a note name.
Middle octave of a piano is 4. Most computers won't
output sounds in the bottom octave (1) and the top
octave (8) is generally painful
sampleRate(=44100): only used for sounds using pyglet. Pygame uses one rate for all sounds
sample rate for all sounds (once initialised)
bits(=16): Only 8- and 16-bits supported so far.
Only used for sounds using pyglet. Pygame uses the same
sample rate for all sounds (once initialised)
"""
self.sampleRate=sampleRate
self.format = bits
self.isStereo = True
self.secs=secs
self._player=pyglet.media.ManagedSoundPlayer()
#self._player._eos_action='pause'
self._player._on_eos=self._onEOS
#try to determine what the sound is
self._snd=None
if type(value) is str:
#try to open the file
OK = self._fromNoteName(value,secs,octave)
#or use as a note name
if not OK: self._fromFile(value)
elif type(value) in [float,int]:
#we've been asked for a particular Hz
self._fromFreq(value, secs)
elif type(value) in [list,numpy.ndarray]:
#create a sound from the input array/list
self._fromArray(value)
if self._snd is None:
raise RuntimeError, "I dont know how to make a "+value+" sound"
def play(self, fromStart=True):
"""Starts playing the sound on an available channel.
If no sound channels are available, it will not play and return None.
This runs off a separate thread i.e. your code won't wait for the
sound to finish before continuing. You need to use a
psychopy.core.wait() command if you want things to pause.
If you call play() whiles something is already playing the sounds will
be played over each other.
"""
self._player.play()
pyglet.media.dispatch_events()
def _onEOS(self):
self._player._playing = False
self._player._timestamp = self._player._sources[0].duration
self._player.seek(0)
self._player.queue(self._snd)
self._player._fill_audio()
self._player.dispatch_event('on_eos')
return True
def stop(self):
"""Stops the sound immediately"""
self._snd._stop()
def getDuration(self):
s=self._snd
if s.duration is not None:
duration = s.duration
else:
duration = len(s._data)/float(s.audio_format.sample_rate)
#data are in byte packets so scale for sample_size (probably 2bytes)
duration = duration*8/s.audio_format.sample_size/s.audio_format.channels
return duration
def getVolume(self):
"""Returns the current volume of the sound (0.0:1.0)"""
return self._player.volume
def setVolume(self,newVol):
"""Sets the current volume of the sound (0.0:1.0)"""
self._player._set_volume(newVol)
def _fromFile(self, fileName):
#try finding the file
self.fileName=None
for filePath in ['', mediaLocation]:
if path.isfile(path.join(filePath,fileName)):
self.fileName=path.join(filePath,fileName)
elif path.isfile(path.join(filePath,fileName+'.wav')):
self.fileName=path.join(filePath,fileName+'.wav')
if self.fileName is None:
return False
self._snd = pyglet.media.load(self.fileName, streaming=False)
#for files we need to find the length of the file and
if self._snd.duration is not None: #will return none if not determined in the file
self.secs=self._snd.duration
#add to our player queue
self._player.queue(self._snd)
return True
def _fromArray(self, thisArray):
global _pygletArrSound
#get a mixer.Sound object from an array of floats (-1:1)
#make stereo if mono
if self.isStereo and \
(len(thisArray.shape)==1 or thisArray.shape[1]<2):
tmp = numpy.ones((len(thisArray),2))
tmp[:,0] = thisArray
tmp[:,1] = thisArray
thisArray = numpy.transpose(tmp)#pyglet wants the transpose
#use pyglet
self._snd = _pygletArrSound(data=thisArray, sample_rate=self.sampleRate, sample_size=-self.format)
self._player.queue(self._snd)
return True
class SoundPyaudio(_SoundBase):
"""Create a sound object, from one of MANY ways.
"""
def __init__(self,value="C",secs=0.5,octave=4,
sampleRate=44100, bits=16):
"""
value: can be a number, string or an array.
If it's a number between 37 and 32767 then a tone will be generated at
that frequency in Hz.
-----------------------------
It could be a string for a note ('A','Bfl','B','C','Csh'...)
- you may want to specify which octave as well
-----------------------------
Or a string could represent a filename in the current
location, or mediaLocation, or a full path combo
-----------------------------
Or by giving an Nx2 numpy array of floats (-1:1) you
can specify the sound yourself as a waveform
secs: is only relevant if the value is a note name or
a frequency value
octave: is only relevant if the value is a note name.
Middle octave of a piano is 4. NB On most computers you can't hear
sound_snds in the bottom octave (1) and the top
octave (8) is generally painful
sampleRate(=44100)
bits(=16): 8 or 16
"""
global mediaLocation
if not havePyglet or not havePyAudio:
raise ImportError, "pyglet and pyaudio are both needed for this type of sound"
self.offsetSamples = -1
self.bits = bits
self.sampleRate = sampleRate
self.channels=2
self.finished=False
self.chunkSize=2048# 1024 gives buffer underuns on OSX (even at 22kHz)
self.volume = 1.0
self.rawData = None
self._thread = PyAudioThread(self, pollingPeriod=0.01)
self._thread.start()
#try to determine what the sound is
self._snd=None
if type(value) is str:
#try to open the file
OK = self._fromNoteName(value,secs,octave)
#or use as a note name
if not OK: #hopefully a file
self._fromFile(value)
elif type(value) in [float,int]:
#we've been asked for a particular Hz
self._fromFreq(value, secs)
elif type(value) in [list,numpy.ndarray]:
#create a sound from the input array/list
self._fromArray(value)
if self.rawData is None:
raise RuntimeError, "I dont know how to make a "+value+" sound"
if self.bits==16: paFormat = pyaudio.paInt16
elif self.bits==8: paFormat = pyaudio.paInt8
else: raise TypeError, "Sounds must be 8, 16bit"
self._stream = pa.open(format = paFormat,
channels = self.channels,
rate = self.sampleRate,
input = False, output=True)
def play(self, startPos = 0):
"""Starts playing the sound.
startPos detremines where the sound begins (in secs)
"""
self.finished=False
self.setOffset(startPos)
self._fillBuffer()#to get the first sample on its way
def setOffset(self, secs):
#self._snd._seek(0)
self.offsetSamples = secs*self.sampleRate
def stop(self):
"""Stops the sound immediately"""
self.offsetSamples=-1
def getDuration(self):
s=self._snd
if s.duration is not None:
duration = s.duration
else:
duration = len(s._data)/float(s.audio_format.sample_rate)
#data are in byte packets so scale for sample_size (probably 2bytes)
duration = duration*8/s.audio_format.sample_size/s.audio_format.channels
return duration
def getVolume(self):
"""Returns the current volume of the sound (0.0:1.0)"""
return self.volume
def setVolume(self,newVol):
"""Sets the current volume of the sound (0.0:1.0)"""
self.volume = newVol
def _fromFile(self, fileName):
#try finding the file
self.fileName=None
for filePath in ['', mediaLocation]:
if path.isfile(path.join(filePath,fileName)):
self.fileName=path.join(filePath,fileName)
elif path.isfile(path.join(filePath,fileName+'.wav')):
self.fileName=path.join(filePath,fileName+'.wav')
if self.fileName is None:
return False
#load the file
self._snd = pyglet.media.load(self.fileName, streaming=False)
#convert to an array with int8 or int16
if self.bits==16:
sndArr = numpy.fromstring(self._snd._data,dtype=numpy.int16)
elif self.bits==8:
sndArr = numpy.fromstring(self._snd._data,dtype=numpy.uint8)
#pyaudio want signed ints so convert unit8 to int8
sndArr = (sndArr.astype(numpy.int16)-128).astype(int8)
sndArr.shape= [len(sndArr)/self.channels, self.channels]
#create the sound buffer from this array
self._fromArray(sndArr)
return True
def _fromNoteName(self, name, secs, octave):
#get a mixer.Sound object from an note name
A=440.0
thisNote=capitalize(name)
stepsFromA = {
'C' : -9,
'Csh' : -8,
'Dfl' : -8,
'D' : -7,
'Dsh' : -6,
'E' : -5,
'F' : -4,
'Fsh' : -3,
'G' : -2,
'Gsh' : -1,
'A': 0,
'Ash':+1,
'Bfl': +1,
'B': +2,
}
if thisNote not in stepsFromA.keys():
return False
thisOctave = octave-4
thisFreq = A * 2.0**(stepsFromA[thisNote]/12.0) * 2.0**thisOctave
self._fromFreq(thisFreq, secs)
def _fromFreq(self, thisFreq, secs):
#get a mixer.Sound object from a frequency
nSamples = int(secs*self.sampleRate)
outArr = numpy.arange(0.0,1.0, 1.0/nSamples)
outArr *= 2*numpy.pi*thisFreq*secs
outArr = numpy.sin(outArr)
if self.bits==16:
self._fromArray( (outArr*32767).astype(numpy.int16) )
if self.bits==8:
self._fromArray( (outArr*127.5-0.5).astype(numpy.int8) )#the minus 1 gives range -128:127
def _fromArray(self, thisArray):
"""Expects an array that is already of the correct format (int8 or int16).
Will create a second channel if only one is provided.
"""
#make stereo if mono
if self.channels==2 and \
(len(thisArray.shape)==1 or thisArray.shape[1]<2):
thisArray.shape = [len(thisArray),1]
thisArray = thisArray.repeat(2,1)#create the second channel
self.rawData = thisArray
return True
def _fillBuffer(self):
"""a function that can be called repeatedly to provide more data to the
stream"""
#NB chunkSize and getRemainingBytes() both refer to the number of bytes
#(not samples) IN EACH CHANNEL (not in total)
#ie. represent half the total number of bytes for a stereo source
if self.offsetSamples==-1 or self.finished:
print 'finishedPlaying', self.offsetSamples, self.finished
#sound is not playing yet, just return
return
#get the appropriate data from the array
if self.bits == 8:#ubyte
start = self.offsetSamples
end = self.offsetSamples+self.chunkSize#either the chunk or the last sample
elif self.bits==16: #signed int16
start = self.offsetSamples >> 1#half as many entries for same number of bytes
end = (self.offsetSamples+ self.chunkSize) >> 1
#check if we have that many samples
if end>self.rawData.shape[0]:
end = self.rawData.shape[0]
print 'end, shape', end, self.rawData.shape[0]
self.finished=True#flag that this must be the last sample
#update next offset position
self.offsetSamples+=self.chunkSize
thisChunk = (self.volume*self.rawData[start:end,:])
print start, end, self.rawData.shape, thisChunk.shape
data=thisChunk.tostring()
# play stream
if len(data)==0:
self.finished=True
return
#cwrite the data to the stream
self._stream.write(data)
def initPyaudio():
"""
define a thread for pyaudio event pumping (needed to fill buffers)
"""
class PyAudioThread(threading.Thread):
"""a thread class to allow PyAudio sounds to play asynchronously"""
def __init__(self, sound, pollingPeriod):
threading.Thread.__init__ ( self )
self.setDaemon(True)
self.sound = sound
self.pollingPeriod=pollingPeriod
self.running = -1
def run(self):
self.running=1
while self.running:
#do the data read
self.sound._fillBuffer()
time.sleep(self.pollingPeriod)#yields to other processes while sleeping
self.running=-1 #shows that it is fully stopped
def stop(self):
if self.running>0:
self.running=0 #make a request to stop on next entry
def setPollingPeriod(self, period):
self.pollingPeriod=period
def initPyglet():
"""
define a thread for pyglet event pumping (needed to fill buffers)
"""
evtDispatchLock = threading.Lock()
class _EventDispatchThread(threading.Thread):
"""a thread that will periodically call to dispatch events
"""
"""I've tried doing this in a way that the thread was started and stopped repeatedly
(so could be paused while a sound wasn't needed, but never made it work.
see sound.py in SVNr85 for the attempt."""
def __init__(self, pollingPeriod=0.005):
threading.Thread.__init__ ( self )
self.pollingPeriod=pollingPeriod
self.running = -1
core.runningThreads.append(self)
def run(self):
self.running=1
#print 'thread started'
while self.running:
#print self.pollingPeriod
pyglet.media.dispatch_events()
time.sleep(self.pollingPeriod)#yeilds to other processes while sleeping
#print 'thread stopped'
self.running=-1#shows that it is fully stopped
def stop(self):
if self.running>0:
self.running=0#make a request to stop on next entry
def setPollingPeriod(self, period):
#print 'polling period is now:%.3f' %period
self.pollingPeriod=period
global _eventThread, _pygletArrSound
if platform=='win32':
_eventThread = _EventDispatchThread(pollingPeriod=0.01)
else:
_eventThread = _EventDispatchThread(pollingPeriod=0.001)#Mac seem to be able to use a shorter refresh safely
def setEventPollingPeriod(period):
"""For pylget contexts this sets the frequency that events controlling sound start and stop
are processed in seconds.
A long period (e.g. 0.1s) will allow more time to be spent on drawing functions and computations,
whereas a very short time (e.g. 0.0001) will allow more precise starting/stopping of audio stimuli..
Events will always be polled on every screen refresh anyway, and repeatedly during
calls to event.waitKeys() so this command has few effects on anything other than for very
precise sounds.
"""
global _eventThread
_eventThread.setPollingPeriod(period)
def stopEventPolling():
"""Stop all polling of events in a pyglet context. Events will still be dispatched on every
flip of a visual.Window (every 10-15ms depending on frame rate).
The user can then dispatch events manually using
pyglet.event.dispatch_events()
"""
global _eventThread
_eventThread.stop()
def startEventPolling():
"""Restart automated event polling if it has been suspended.
This call does nothing if the polling had been
"""
global _eventThread
if _eventThread.stopping:
_eventThread.start()
class _pygletArrSound(pyglet.media.procedural.ProceduralSource):
"""
Create a pyglet.StaticSource from a numpy array.
"""
def __init__(self, data, sample_rate=22050, sample_size=16):
"""Array data should be float (-+1.0)
sample_size (16 or 8) determines the number of bits used for internal storage"""
duration = data.shape[1]/float(sample_rate) #determine duration from data
super(_pygletArrSound, self).__init__(duration,sample_rate, abs(sample_size))
self.sample_rate = sample_rate
self.sample_size=sample_size
if abs(sample_size)==8: #ubyte
self.allData = (data*127+127).astype(numpy.uint8)
elif abs(sample_size) == 16: #signed int16
self.allData = (data*32767).astype(numpy.int16)
def _generate_data(self, bytes, offset, volume=1.0):
#print 'bps', self._bytes_per_sample, bytes
if self.sample_size == 8:#ubyte
start = offset
samples = bytes
else: #signed int16
start = (offset >> 1)#half as many entries for same number of bytes
samples = (bytes >> 1)
return (self.allData[:,start:(start+samples)]).ctypes
def initPygame(rate=22050, bits=16, stereo=True, buffer=1024):
"""If you need a specific format for sounds you need to run this init
function. Run this *before creating your visual.Window*.
The format cannot be changed once initialised or once a Window has been created.
If a Sound object is created before this function is run it will be
executed with default format (signed 16bit stereo at 44KHz).
For more details see pygame help page for the mixer.
"""
if stereo==True: stereoChans=2
else: stereoChans=0
if bits==16: bits=-16 #for pygame bits are signed for 16bit, signified by the minus
mixer.init(rate, bits, stereoChans, buffer) #defaults: 22050Hz, 16bit, stereo,
setRate, setBits, setStereo = mixer.get_init()
if setRate!=rate:
log.warn('Requested sound sample rate was not poossible')
if setBits!=bits:
log.warn('Requested sound depth (bits) was not possible')
if setStereo!=2 and stereo==True:
log.warn('Requested stereo setting was not possible')
def setAudioAPI(api):
"""Change the API used for the presentation of sounds
usage:
setAudioAPI(api)
where:
api is one of 'pygame','pyglet', pyaudio'
"""
global audioAPI, Sound
exec('haveThis=have%s' %api.title())
if haveThis:
audioAPI=api
exec('init%s()' %(API.title()))
exec('thisSound= Sound%s' %(API.title()))
Sound= thisSound
return haveThis
#initialise it and keep track
for API in preferredAPI:
if setAudioAPI(API):
audioAPI=API
break#we found one so stop looking
if audioAPI is None:
log.error('No audio API found. Try installing pygame 1.8+')
|