Skip to content

Commit

Permalink
Normalized --audio-lib option, and added support for python sounddevi…
Browse files Browse the repository at this point in the history
…ce lib, #115.
  • Loading branch information
vmdocua committed Dec 3, 2024
1 parent b548538 commit 88d216a
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 34 deletions.
21 changes: 7 additions & 14 deletions tools/reprostim-timesync-stimuli
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,6 @@ from psychopy import visual, core, event, clock
#######################################################
# Constants

# Enum for the audio libs
class AudioLib(str, Enum):
# PsychoPy SoundDevice audio lib
PSYCHOPY_SOUNDDEVICE = "psychopy_sounddevice"
# PsychoPy SoundPTB audio lib
PSYCHOPY_PTB = "psychopy_ptb"
# sounddevice audio lib
# http://python-sounddevice.readthedocs.io/
SOUNDDEVICE = "sounddevice"


# Enum for the mode of the script operation
class Mode(str, Enum):
Expand Down Expand Up @@ -312,10 +302,13 @@ def do_main(mode: Mode, logfn: str,
help='Specify QR code scale factor in range 0..1. '
'Use 1.0 to fit full height (default: 0.8).')
@click.option('-a', '--audio-lib',
type=click.Choice([a.value for a in AudioLib],
case_sensitive=False),
default=AudioLib.PSYCHOPY_SOUNDDEVICE,
help='Specify audio library to be used.')
type=click.Choice(['psychopy_sounddevice',
'psychopy_ptb',
'sounddevice'],
case_sensitive=True),
default='psychopy_sounddevice',
help='Specify audio library to be used '
'(default: psychopy_sounddevice).')
@click.option('-c', '--sound-codec',
type=click.Choice(['FSK', 'NFE'],
case_sensitive=True),
Expand Down
97 changes: 77 additions & 20 deletions tools/soundcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import numpy as np
import sounddevice as sd
from scipy.io.wavfile import write
from scipy.io.wavfile import write, read
from scipy.io import wavfile
from reedsolo import RSCodec

Expand All @@ -18,10 +18,30 @@
logger = logging.getLogger(__name__)
logger.setLevel(os.environ.get('REPROSTIM_LOG_LEVEL', 'INFO'))

# setup audio library
######################################
# Setup psychopy audio library

# Enum for the audio libs
class AudioLib(str, Enum):
# PsychoPy SoundDevice audio lib
PSYCHOPY_SOUNDDEVICE = "psychopy_sounddevice"
# PsychoPy SoundPTB audio lib
PSYCHOPY_PTB = "psychopy_ptb"
# sounddevice audio lib
# http://python-sounddevice.readthedocs.io/
SOUNDDEVICE = "sounddevice"

_audio_lib = os.environ.get('REPROSTIM_AUDIO_LIB', AudioLib.PSYCHOPY_SOUNDDEVICE)

from psychopy import prefs
prefs.hardware['audioLib'] = [os.environ.get('REPROSTIM_AUDIO_LIB',
'sounddevice')]
prefs.hardware['audioLib'] = ['sounddevice']
if _audio_lib == AudioLib.PSYCHOPY_SOUNDDEVICE:
logger.debug("Set psychopy audio library: sounddevice")
prefs.hardware['audioLib'] = ['sounddevice']
elif _audio_lib == AudioLib.PSYCHOPY_PTB:
logger.debug("Set psychopy audio library: ptb")
prefs.hardware['audioLib'] = ['ptb']

#logger.info("Using psychopy audio library: %s", prefs.hardware['audioLib'])
from psychopy import core, sound
from psychtoolbox import audio
Expand Down Expand Up @@ -81,6 +101,20 @@ def crc8(data: bytes, polynomial: int = 0x31, init_value: int = 0x00) -> int:
crc &= 0xFF # Keep CRC to 8 bits
return crc

######################################
# Constants

class SoundCodec(str, Enum):
# Frequency Shift Keying (FSK) where binary data is
# encoded as two different frequencies f0 and f1 with
# a fixed bit duration (baud rate or bit_rate).
FSK = "FSK"

# Numerical Frequency Encoding (NFE) numbers are mapped
# directly to specific frequencies
# can encode only some numeric hash.
NFE = "NFE"

######################################
# Classes

Expand Down Expand Up @@ -175,18 +209,6 @@ def set_uint64(self, i: int):
self.set_bytes(i.to_bytes(8, 'big'))


class SoundCodec(str, Enum):
# Frequency Shift Keying (FSK) where binary data is
# encoded as two different frequencies f0 and f1 with
# a fixed bit duration (baud rate or bit_rate).
FSK = "FSK"

# Numerical Frequency Encoding (NFE) numbers are mapped
# directly to specific frequencies
# can encode only some numeric hash.
NFE = "NFE"


# Class to provide general information about sound code
class SoundCodeInfo:
def __init__(self):
Expand Down Expand Up @@ -477,17 +499,19 @@ def list_audio_devices():



def play_sound(name: str,
def _play_sound_psychopy(name: str,
duration: float = None,
volume: float = 0.8,
sample_rate: int = 44100,
async_: bool = False):
logger.debug(f"play_sound(name={name}, duration={duration}, async_={async_})")
logger.debug(f"_play_sound_psychopy(name={name}, duration={duration}, async_={async_})")
snd = None
if duration:
snd = sound.Sound(name, secs=duration,
snd = sound.Sound(name, secs=duration, sampleRate=sample_rate,
stereo=True, volume=volume)
else:
snd = sound.Sound(name, stereo=True, volume=volume)
snd = sound.Sound(name, stereo=True, sampleRate=sample_rate,
volume=volume)
logger.debug(f"Play sound '{snd.sound}' with psychopy {prefs.hardware['audioLib']}")
snd.play()
logger.debug(f" sampleRate={snd.sampleRate}, duration={snd.duration}, volume={snd.volume}")
Expand All @@ -497,6 +521,39 @@ def play_sound(name: str,
logger.debug(f"Sound '{snd.sound}' has finished playing.")


def _play_sound_sd(name: str,
duration: float = None,
volume: float = 0.8,
sample_rate: int = 44100,
async_: bool = False):
logger.debug(f"_play_sound_sd(name={name}, duration={duration}, async_={async_})")
data = name

if os.path.exists(name):
rate, signal = read(name)
logger.debug(f"Read sound file: {name}, rate={rate}")
# Convert from int16 to float32
#signal = signal.astype(np.float32) / 32767
data = signal

sd.play(data, samplerate=sample_rate)


def play_sound(name: str,
duration: float = None,
volume: float = 0.8,
sample_rate: int = 44100,
async_: bool = False):
logger.debug(f"play_sound(name={name}, duration={duration}, async_={async_})")
if (_audio_lib == AudioLib.PSYCHOPY_SOUNDDEVICE or
_audio_lib == AudioLib.PSYCHOPY_PTB):
_play_sound_psychopy(name, duration, volume, sample_rate, async_)
elif _audio_lib == AudioLib.SOUNDDEVICE:
_play_sound_sd(name, duration, volume, sample_rate, async_)
else:
raise ValueError(f"Unsupported audio library: {_audio_lib}")


def save_soundcode(fname: str = None,
code_uint16: int = None,
code_uint32: int = None,
Expand Down

0 comments on commit 88d216a

Please sign in to comment.