• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

int-brain-lab / iblrig / 14196118657

01 Apr 2025 12:52PM UTC coverage: 47.634% (+0.8%) from 46.79%
14196118657

Pull #795

github

cfb5bd
web-flow
Merge 5ba5d5f25 into 58cf64236
Pull Request #795: fixes for habituation CW

11 of 12 new or added lines in 1 file covered. (91.67%)

1083 existing lines in 22 files now uncovered.

4288 of 9002 relevant lines covered (47.63%)

0.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

46.48
/iblrig/sound.py
1
import logging
2✔
2

3
import numpy as np
2✔
4

5
from pybpod_soundcard_module.module_api import DataType, SampleRate, SoundCardModule
2✔
6

7
log = logging.getLogger(__name__)
2✔
8

9

10
def make_sound(rate=44100, frequency=5000, duration=0.1, amplitude=1, fade=0.01, chans='L+TTL'):
2✔
11
    """
12
    Build sounds and save bin file for upload to soundcard or play via
13
    sounddevice lib.
14

15
    :param rate: sample rate of the soundcard use 96000 for Bpod,
16
                    defaults to 44100 for soundcard
17
    :type rate: int, optional
18
    :param frequency: (Hz) of the tone, if -1 will create uniform random white
19
                    noise, defaults to 10000
20
    :type frequency: int, optional
21
    :param duration: (s) of sound, defaults to 0.1
22
    :type duration: float, optional
23
    :param amplitude: E[0, 1] of the sound 1=max 0=min, defaults to 1
24
    :type amplitude: intor float, optional
25
    :param fade: (s) time of fading window rise and decay, defaults to 0.01
26
    :type fade: float, optional
27
    :param chans: ['mono', 'L', 'R', 'stereo', 'L+TTL', 'TTL+R'] number of
28
                   sound channels and type of output, defaults to 'L+TTL'
29
    :type chans: str, optional
30
    :return: streo sound from mono definitions
31
    :rtype: np.ndarray with shape (Nsamples, 2)
32
    """
33
    sample_rate = rate  # Sound card dependent,
2✔
34
    tone_duration = duration  # sec
2✔
35
    fade_duration = fade  # sec
2✔
36
    chans = chans if isinstance(chans, str) else chans[0]
2✔
37
    tvec = np.linspace(0, tone_duration, int(tone_duration * sample_rate))
2✔
38
    tone = amplitude * np.sin(2 * np.pi * frequency * tvec)  # tone vec
2✔
39

40
    len_fade = int(fade_duration * sample_rate)
2✔
41
    fade_io = np.hanning(len_fade * 2)
2✔
42
    fadein = fade_io[:len_fade]
2✔
43
    fadeout = fade_io[len_fade:]
2✔
44
    win = np.ones(len(tvec))
2✔
45
    win[:len_fade] = fadein
2✔
46
    win[-len_fade:] = fadeout
2✔
47

48
    tone = tone * win
2✔
49
    ttl = np.ones(len(tone)) * 0.99
2✔
50
    one_ms = round(sample_rate / 1000) * 10
2✔
51
    ttl[one_ms:] = 0
2✔
52
    null = np.zeros(len(tone))
2✔
53

54
    if frequency == -1:
2✔
55
        tone = amplitude * np.random.rand(tone.size)
2✔
56

57
    if chans == 'mono':
2✔
58
        sound = np.array(tone)
×
59
    elif chans == 'L':
2✔
60
        sound = np.array([tone, null]).T
×
61
    elif chans == 'R':
2✔
62
        sound = np.array([null, tone]).T
×
63
    elif chans == 'stereo':
2✔
64
        sound = np.array([tone, tone]).T
2✔
65
    elif chans == 'L+TTL':
×
66
        sound = np.array([tone, ttl]).T
×
67
    elif chans == 'TTL+R':
×
68
        sound = np.array([ttl, tone]).T
×
69

70
    return sound
2✔
71

72

73
def format_sound(sound, file_path=None, flat=False):
2✔
74
    """
75
    Format sound to send to sound card.
76

77
    Binary files to be sent to the sound card need to be a single contiguous
78
    vector of int32 s. 4 Bytes left speaker, 4 Bytes right speaker, ..., etc.
79

80

81
    :param sound: Stereo sound
82
    :type sound: 2d numpy.array os shape (n_samples, 2)
83
    :param file_path: full path of file. [default: None]
84
    :type file_path: str
85
    """
86
    bin_sound = (sound * ((2**31) - 1)).astype(np.int32)
×
87

88
    if bin_sound.flags.f_contiguous:
×
89
        bin_sound = np.ascontiguousarray(bin_sound)
×
90

UNCOV
91
    bin_save = bin_sound.reshape(1, np.multiply(*bin_sound.shape))
×
UNCOV
92
    bin_save = np.ascontiguousarray(bin_save)
×
93

UNCOV
94
    if file_path:
×
UNCOV
95
        with open(file_path, 'wb') as bf:
×
UNCOV
96
            bf.writelines(bin_save)
×
UNCOV
97
            bf.flush()
×
98

UNCOV
99
    return bin_sound.flatten() if flat else bin_sound
×
100

101

102
def configure_sound_card(card=None, sounds=None, indexes=None, sample_rate=96):
2✔
UNCOV
103
    if indexes is None:
×
UNCOV
104
        indexes = []
×
UNCOV
105
    if sounds is None:
×
106
        sounds = []
×
UNCOV
107
    if card is None:
×
108
        card = SoundCardModule()
×
109
        close_card = True
×
110

111
    if sample_rate in (192, 192000):
×
112
        sample_rate = SampleRate._192000HZ
×
UNCOV
113
    elif sample_rate in (96, 96000):
×
114
        sample_rate = SampleRate._96000HZ
×
115
    else:
116
        log.error(f'Sound sample rate {sample_rate} should be 96 or 192 (KHz)')
×
117
        raise (ValueError)
×
118

119
    if len(sounds) != len(indexes):
×
UNCOV
120
        log.error('Wrong number of sounds and indexes')
×
UNCOV
121
        raise (ValueError)
×
122

123
    sounds = [format_sound(s, flat=True) for s in sounds]
×
124
    for sound, index in zip(sounds, indexes, strict=False):
×
125
        card.send_sound(sound, index, sample_rate, DataType.INT32)
×
126

127
    if close_card:
×
128
        card.close()
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc