Skip to content

Commit

Permalink
Merge pull request psychopy#6760 from TEParsons/dev-bf-mic-index
Browse files Browse the repository at this point in the history
ENH: Allow Sound to play on different speakers in the same experiment
  • Loading branch information
TEParsons authored Jul 31, 2024
2 parents 6fd04ca + e78cbb0 commit 66e617f
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 16 deletions.
2 changes: 1 addition & 1 deletion psychopy/experiment/components/sound/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def getSpeakerValues():
return vals

self.params['speakerIndex'] = Param(
speakerIndex, valType="code", inputType="choice", categ="Device",
speakerIndex, valType="str", inputType="choice", categ="Device",
allowedVals=getSpeakerValues,
allowedLabels=getSpeakerLabels,
hint=_translate(
Expand Down
5 changes: 1 addition & 4 deletions psychopy/hardware/speaker.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,11 @@ def __init__(self, index):
# find profile which matches index
for profile in profiles.values():
if index in (profile['index'], profile['name']):
self.index = profile['index']
self.index = int(profile['index'])
self.deviceName = profile['name']

if self.index is None:
logging.error("No speaker device found with index %d" % index)
else:
# set global device (best we can do for now)
setDevice(index)

def isSameDevice(self, other):
"""
Expand Down
34 changes: 23 additions & 11 deletions psychopy/sound/backend_ptb.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ class _StreamsDict(dict):
use the instance `streams` rather than creating a new instance of this
"""
def __init__(self, index):
# store device index
self.index = index

def getStream(self, sampleRate, channels, blockSize):
"""Gets a stream of exact match or returns a new one
Expand Down Expand Up @@ -186,11 +189,11 @@ def _getStream(self, sampleRate, channels, blockSize):

# create new stream
self[label] = _MasterStream(sampleRate, channels, blockSize,
device=defaultOutput)
device=self.index)
return label, self[label]


streams = _StreamsDict()
devices = {}


class _MasterStream(audio.Stream):
Expand Down Expand Up @@ -351,8 +354,8 @@ def isFinished(self):

def _getDefaultSampleRate(self):
"""Check what streams are open and use one of these"""
if len(streams):
return list(streams.values())[0].sampleRate
if len(devices.get(self.speaker.index, [])):
return list(devices[self.speaker.index].values())[0].sampleRate
else:
return 48000 # seems most widely supported

Expand Down Expand Up @@ -604,17 +607,26 @@ def stream(self):
"""Read-only property returns the stream on which the sound
will be played
"""
# if no stream yet, make one
if not self.streamLabel:
# if no streams for current device yet, make a StreamsDict for it
if self.speaker.index not in devices:
devices[self.speaker.index] = _StreamsDict(index=self.speaker.index)
# make stream
try:
label, s = streams.getStream(sampleRate=self.sampleRate,
channels=self.channels,
blockSize=self.blockSize)
label, s = devices[self.speaker.index].getStream(
sampleRate=self.sampleRate,
channels=self.channels,
blockSize=self.blockSize
)
except SoundFormatError as err:
# try to use something similar (e.g. mono->stereo)
# then check we have an appropriate stream open
altern = streams._getSimilar(sampleRate=self.sampleRate,
channels=-1,
blockSize=-1)
altern = devices[self.speaker.index]._getSimilar(
sampleRate=self.sampleRate,
channels=-1,
blockSize=-1
)
if altern is None:
raise SoundFormatError(err)
else: # safe to extract data
Expand All @@ -625,7 +637,7 @@ def stream(self):
self.blockSize = s.blockSize
self.streamLabel = label

return streams[self.streamLabel]
return devices[self.speaker.index][self.streamLabel]

def __del__(self):
if self.track:
Expand Down

0 comments on commit 66e617f

Please sign in to comment.