File: Snd.m

package info (click to toggle)
psychtoolbox-3 3.0.18.12.dfsg1-1.1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 83,576 kB
  • sloc: ansic: 173,181; cpp: 20,885; objc: 5,148; sh: 2,752; python: 1,366; php: 384; makefile: 193; java: 113
file content (534 lines) | stat: -rw-r--r-- 21,481 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
function err = Snd(command,signal,rate,sampleSize)
% err = Snd(command,[signal],[rate],[sampleSize])
%
% Old Sound driver for Psychtoolbox. USE OF THIS DRIVER IS DEPRECATED FOR
% ALL BUT THE MOST TRIVIAL PURPOSES! Only useful for simple feedback tones,
% and indirectly by Eyelink's calibration routines. Does not mix well with
% simultaneous use of PsychPortAudio() or Screen() movie playback functions!
%
% Have a look at the help for PsychPortAudio ("help PsychPortAudio" and
% "help InitializePsychSound") for an introduction into the new sound
% driver, which is recommended for most purposes.
%
% Snd() is a rather dumb and primitive wrapper around the PsychPortAudio()
% driver. It uses PsychPortAudio's most basic functionality to achieve
% "sort of ok" sound playback. The driver is used in high-latency,
% low-timing precision mode, so Snd()'s audio playback timing will likely
% be very unreliable.
%
% Alternatively you can create an empty file named 'Snd_use_oldstyle.txt' in
% the PsychtoolboxConfigDir() folder, ie., [PsychtoolboxConfigDir 'Snd_use_oldstyle.txt']
% This will enable the old-style implementation of Snd(), which is equally
% shoddy and works as follows:
%
% While Snd used to use a special purpose low level driver on MacOS-9 which
% was well suited for cognitive science, Snd for all other operating
% systems (Windows, MacOS-X, Linux) just calls into Matlab's Sound()
% function which is of varying - but usually pretty poor - quality in most
% implementations of Matlab. There are many bugs, latency- and timing
% problems associated with the use of Snd.
%
% GNU/OCTAVE: If you don't use the PsychPortAudio based Snd() function, then
% you must install the optional octave "audio" package from Octave-Forge,
% as of Octave 3.0.5, otherwise the required sound() function won't be
% available and this function will fail!
%
% Audio device sharing for interop with PsychPortAudio:
% -----------------------------------------------------
%
% If you want to use PsychPortAudio and Snd() simultaneously, or one of the
% functions that indirectly use Snd(), e.g., Beeper() for simple beep tones,
% or Eyelink's auditory feedback during tracker setup and recalibration, which
% in turn uses Beeper() and thereby Snd(), then try this:
%
% 1. Open a suitable PsychPortAudio audio device, possibly also a slave audio
%    device and get a pahandle to it, e.g., pahandle = PsychPortAudio('Open',...);
%    or PsychPortAudio('OpenSlave', ...) for a slave device.
%
% 2. Now open Snd(), passing in this device handle for use as Snd() output device:
%    Snd('Open', pahandle);
%
%    If you want to repeatedly call Beeper(), or use auditory feedback from Eyelink,
%    which itself repeatedly calls Beeper(), then you should open the shared pahandle
%    via Snd('Open', pahandle, 1); - This will prevent Snd('Close') from having any
%    effect, so Beeper() won't close the Snd() driver after one beep, and Eyelink
%    will be able to emit multiple auditory feedback tones, not just a single one.
%
% 3. Proceed as usual, e.g., Snd('Play', ...) or Beeper(...), etc. Snd() will
%    use the pahandle audio device for playback, and pahandle can also be used
%    by PsychPortAudio calls directly for precisely controlled sound.
%
% 4. At the end of a session, you could forcefully detach Snd() from the pahandle
%    via a call to Snd('Close', 1).
%
% Supported functions:
% --------------------
%
% Snd('Play', signal [, rate][, sampleSize]) plays a sound.
%
% rate=Snd('DefaultRate') returns the default sampling rate in Hz, which
% currently is 22254.5454545454 Hz on all platforms for the old style sound
% implementation, and the default device sampling rate if PsychPortAudio is
% used. This default may change in the future, so please either specify a
% rate, or use this function to get the default rate. (This default is
% suboptimal on any system except long dead MacOS-9, but kept for backwards
% compatibility!).
%
% The optional 'sampleSize' argument used with Snd('Play') is only retained
% for backwards compatibility and has no meaning, unless you opt in to use
% the old-style implementation on Matlab with some operating systems. - It
% is checked for correctness, but other than that it is ignored. Allowable
% values are either 8 or 16.
%
% oldverbosity = Snd('Verbosity' [, verbosity]);
% - Query current level of verbosity, optionally set a new 'verbosity' level.
%
% Snd('Open') opens the channel, which stays open until you call
% Snd('Close'). Snd('Play',...) automatically opens the channel if it isn't
% already open. You can use Snd('Open', pahandle); to share an existing
% PsychPortAudio audio device handle 'pahandle' with Snd() for optimal
% interoperation. A Snd('Close') of such a shared 'pahandle' would not close the
% handle, but it would close Snd()'s further use of it. If you call
% Snd('Open', pahandle, 1); then a Snd('Close') will not have any effect, ie. the
% pahandle not only stays open, but also continues to be shared and open for use
% by Snd(). See instructions above.
%
% Snd('Close') immediately stops all sound and closes the channel, unless you
% specified a shared pahandle with PsychPortAudio via Snd('Open', pahandle, 1);
% earlier. Calling Snd('Close', 1) will always really close the channel.
%
% Snd('Wait') waits until the sound is done playing.
%
% isPlaying=Snd('IsPlaying') returns true if any sound is playing, and
% false (0) otherwise.
%
% Snd('Quiet') stops the sound currently playing, but leaves the channel open.
%
% "signal" must be a numeric array of samples.
%
% Your "signal" data should lie between -1 and 1 (smaller to play more
% softly). If the "signal" array has one row then it's played monaurally,
% through both speakers. If it has two rows then it's played in stereo.
% (Snd has no provision for changing which speaker(s), or the volume, used
% by a named snd resource, so use READSND to get the snd into an array,
% and supply the appropriately modified array to Snd.)
%
% "rate" is the rate (in Hz) at which the samples in "signal" should be
% played. We suggest you always specify the "rate" parameter. If not
% specified, the sample "rate", on all platforms, defaults to OS9's
% standard hardware sample rate of 22254.5454545454 Hz. That value is
% returned by Snd('DefaultRate'). Other values can be specified.
%
% OSX & WIN: "samplesize". Snd accepts the sampleSize argument and passes
% it to the Matlab SOUND command.  SOUND (and therefore Snd also) obeys the
% specified sampleSize value, either 8 or 16, only if it is supported by
% your computer hardware.
%
% Snd('Play',sin(0:10000)); % play 22 KHz/(2*pi)=3.5 kHz tone
% Snd('Play',[sin(1:20000) zeros(1,10000);zeros(1,10000) sin(1:20000)]); % stereo
% Snd('Wait');              % wait until end of all sounds currently in channel
% Snd('Quiet');             % stop the sound and flush the queue
%
% For most of the commands, the returned value is zero when successful, and
% a nonzero error number when Snd fails.
%
% Snd('Play', signal) takes some time to open the channel, if it isn't
% already open, and allocate a snd structure for your sound. This overhead
% of the call to Snd, if you call it in the middle of a movie, may be
% perceptible as a pause in the movie, which would be bad. However, the
% actual playing of the sound, asynchronously, is a background process that
% usually has very little overhead. So, even if you want a sound to begin
% after the movie starts, you should create a soundtrack for your entire
% movie duration (possibly including long silences), and call Snd to set
% the sound going before you start your movie. (Thanks to Liz Ching for
% raising the issue.)
%
% NOTE: We suggest you always specify the "rate" parameter. If not
% specified, the sample rate, on all platforms, defaults to OS9's
% standard hardware sample rate of 22254.5454545454 Hz. That value is returned
% by Snd('DefaultRate').
%
% See also PsychPortAudio, Beeper, AUDIOPLAYER, PLAY, MakeBeep, READSND, and WRITESND.

% 6/6/96    dgp Wrote SndPlay.
% 6/1/97    dgp Polished help text.
% 12/10/97  dhb Updated help.
% 2/4/98    dgp Wrote Snd, based on major update of VideoToolbox SndPlay1.c.
% 3/8/00    emw Added PC notes and code.
% 7/23/00   dgp Added notes about controlling volume of named snd, and updated
%               broken link to ResEdit.
% 4/13/02   dgp Warn that the two platforms have different default sampling rate,
%               and suggest that everyone routinely specify sampling rate to
%               make their programs platform-independent.
% 4/13/02   dgp Enhanced both OS9 and WIN versions so that 'DefaultRate'
%               returns the default sampling rate in Hz.
% 4/13/02   dgp Changed WIN code, so that sampling rate is now same on both platforms.
% 4/15/02   awi fixed WIN code.
% 5/30/02   awi Added sampleSize argument and documented.
%               SndTest would crash Matlab but the problem mysteriously vanished while editing
%               the Snd.m and SndTest.m files.  I've been unable to reproduce the error.
% 3/10/05   dgp Make it clear that the Snd mex is only available for OS9.
%               Mention AUDIOPLAYER, as suggested by Pascal Mamassian.
% 5/20/08    mk Explain that Snd() is deprecated --> Point to PsychPortAudio!
% 1/12/09    mk Make sure that 'signal' is a 2-row matrix in stereo, not 2
%               column.
% 6/01/09    mk Add compatibility with Octave-3.
% 9/03/12    mk Add new implementation via PsychPortAudio(), which is used
%               by default unless user opts out. Cleanup online help by
%               removal of obsolete and outdated information.
% 7/20/15    mk Make commands case insensitive, white-space cleanup, try to
%               make Snd('Open'); Snd('Close'); sequences work to avoid problems
%               like in PTB forum message #19284 on Linux or Windows.
% 1/19/16    mk Make more robust against failing InitializePsychSound. Fallback to sound().
% 7/11/19    mk Allow sharing pahandle with external code / piggyback onto existing pahandle
%               via Snd('Open', pahandle);
% 12/9/19    mk Add 'Verbosity' subcommand to be able to silence Snd() and PsychPortAudio() output.
% 08/10/21   mk Try to make Snd - pahandle sharing more reliable via new Snd('Open', pahandle, 1).
% 10/29/21   mk Some more help updates.

persistent ptb_snd_oldstyle;
persistent ptb_snd_injected;
persistent pahandle;
persistent verbose;

persistent endTime;
if isempty(endTime)
    endTime = 0;
    ptb_snd_injected = 0;
    verbose = 1;
end

% Default return value:
err = 0;

if nargin == 0
    error('Wrong number of arguments: see Snd.');
end

if strcmpi(command, 'Verbosity')
    if nargin ~= 2
        error('Snd: Called "Verbosity" without specifying verbosity level.');
    end

    err = verbose;
    verbose = signal;
    return;
end

% Snd('Open', pahandle [, noclose=0]) called to inject a pahandle of an already open
% PsychPortAudio device for sharing? If so, we piggyback onto pahandle
% for our audio playback:
if strcmpi(command,'Open') && nargin >= 2 && ~isempty(signal)
    % Close previous PsychPortAudio handle if it was not injected into us:
    if ~isempty(pahandle) && pahandle ~= signal && ~ptb_snd_injected
        PsychPortAudio('Close', pahandle);
    end

    % Assign 2nd argument as new pahandle:
    pahandle = signal;

    % Obviously we are not in old style mode here:
    ptb_snd_oldstyle = 0;

    % "noclose" flag provided as 1?
    if nargin >=3 && ~isempty(rate) && rate == 1
        % Mark this pahandle as injected and uncloseable by us. Neither the PPA
        % device, nor our association with it should be closed/removed, not even
        % if our caller calls Snd('Close'). This is useful for emitting repeated
        % beeps via Beeper(), as Beeper() would Snd('Close') after each beep, and
        % thereby remove the association:
        ptb_snd_injected = 2;

        if verbose
            fprintf('Snd(): Using PsychPortAudio via shared handle %i permanently.\n', pahandle);
        end
    else
        % Mark this pahandle as injected -- PPA device should not be closed by us
        % ever, but our Snd('Close') function can detach us from it, so it will
        % only be usable by PsychPortAudio directly again:
        ptb_snd_injected = 1;

        if verbose
            fprintf('Snd(): Using PsychPortAudio via shared handle %i, until you call Snd(''Close''); to unshare.\n', pahandle);
        end
    end

    endTime = 0;

    return;
end

% Already defined if we shall use the new-style or old-style
% implementation?
if isempty(ptb_snd_oldstyle)
    % Nope, check if the special "old style" marker file exists:
    if exist([PsychtoolboxConfigDir 'Snd_use_oldstyle.txt'], 'file')
        % User explicitely wants old-style implementation via
        % Matlab/Octave sound() function:
        ptb_snd_oldstyle = 1;

        if verbose
            fprintf('Snd(): Using Matlab/Octave sound() function for sound output.\n');
        end
    else
        % User wants new-style PsychPortAudio variant:
        ptb_snd_oldstyle = 0;

        if verbose
            fprintf('Snd(): Initializing PsychPortAudio driver for sound output.\n');
        end

        % Basic sound preinit:
        try
            InitializePsychSound;
        catch
            fprintf('Snd(): ERROR!\n');
            ple;
            psychlasterror('reset');
            fprintf('Snd(): PsychPortAudio initialization failed - See error messages above. Trying to use old sound() fallback instead.\n');
            ptb_snd_oldstyle = 1;
        end
    end
end

% Don't use PsychPortAudio backend if already a PPA device open on Linux or
% Windows, as that device will often have exclusive access, so we would
% fail here:
if ~(strcmpi(command,'Open') || strcmpi(command,'Quiet') || strcmpi(command,'Close')) && ...
    isempty(pahandle) && ~ptb_snd_oldstyle && ~IsOSX

    if ~verbose
        oldv = PsychPortAudio('Verbosity', 0);
        odc = PsychPortAudio('GetOpenDeviceCount');
        PsychPortAudio('Verbosity', oldv);
    else
        odc = PsychPortAudio('GetOpenDeviceCount');
    end

    if odc > 0
        if verbose
            fprintf('Snd(): PsychPortAudio already in use. Using old sound() fallback instead...\n');
        end

        ptb_snd_oldstyle = 1;
    end
end

if strcmpi(command,'Play')
    if nargin > 4
        error('Wrong number of arguments: see Snd.');
    end

    if nargin == 4
        if isempty(sampleSize)
            sampleSize = 16;
        elseif ~((sampleSize == 8) || (sampleSize == 16))
            error('sampleSize must be either 8 or 16.');
        end
    else
        sampleSize = 16;
    end

    if nargin < 3
        rate = [];
    end

    if nargin < 2
        error('Wrong number of arguments: see Snd.');
    end

    if size(signal,1) > size(signal,2)
        error('signal must be a 2 rows by n column matrix for stereo sounds.');
    end

    if isempty(rate)
        if ptb_snd_oldstyle
            % Old MacOS-9 style default:
            rate = 22254.5454545454;
        else
            % Let PPA decide itself:
            rate = [];
        end
    end

    if ptb_snd_oldstyle
        % Old-Style implementation via sound() function:

        WaitSecs(endTime-GetSecs); % Wait until any ongoing sound is done.

        % Octave special-case:
        if IsOctave
            if exist('sound') %#ok<EXIST>
                sound(signal',rate);
            else
                % Unavailable: Try to load the package, assuming its
                % installed but not auto-loaded:
                try
                    pkg('load','audio');
                catch %#ok<CTCH>
                end

                % Retry...
                if exist('sound') %#ok<EXIST>
                    sound(signal',rate);
                else
                    warning('Required Octave command sound() is not available. Install and "pkg load audio" the "audio" package from Octave-Forge!'); %#ok<WNTAG>
                end
            end
        else
            sound(signal',rate,sampleSize);
        end

        % Estimate 'endTime' for playback:
        endTime=GetSecs+length(signal)/rate;
    else
        % New-Style via PsychPortAudio:
        if ~isempty(pahandle)
            % PPA playback device already open.

            % Wait blocking until end of its playback:
            PsychPortAudio('Stop', pahandle, 1, 1);

            % Correct sampling rate set?
            props = PsychPortAudio('GetStatus', pahandle);
            if ~isempty(rate) && (abs(props.SampleRate - rate) > 1.0)
                % Device sampleRate not within tolerance of 1 Hz to
                % requested samplingrate.
                if ~ptb_snd_injected
                    % Close it, so it can get reopened with proper rate:
                    PsychPortAudio('Close', pahandle);
                    pahandle = [];
                else
                    % Should change samplerate, but can not, as this is a shared
                    % pahandle:
                    if verbose
                        fprintf('Snd(): Shared PsychPortAudio handle. Can not change sample rate from current %f Hz to %f Hz as requested!\n', ...
                                props.SampleRate, rate);
                    end
                end
            end
        end

        if isempty(pahandle)
            % Be silent during driver/device init:
            oldverbosity = PsychPortAudio('Verbosity', 2);

            % Open our own 'pahandle' sound device: Auto-Selected output
            % device [], playback only (1),
            % high-latency/low-timing-precision mode (0), at given
            % samplerate (rate), in stereo (2):
            pahandle = PsychPortAudio('Open', [], 1, 0, rate, 2);

            % Restore standard level of verbosity:
            PsychPortAudio('Verbosity', oldverbosity);

            if ~IsOSX && verbose
                fprintf('Snd(): PsychPortAudio will be blocked for use by your own code until you call Snd(''Close'');\n');
                fprintf('Snd(): If you want to use PsychPortAudio and Snd in the same session, make sure to open your\n');
                fprintf('Snd(): stimulation sound device via calls to, e.g., pahandle = PsychPortAudio(''Open'', ...);\n');
                fprintf('Snd(): *before* the first call to Snd() or any function that might use Snd(), e.g., Beeper()\n');
                fprintf('Snd(): or the Eyelink functions with auditory feedback. Call Snd(''Open'', pahandle) next, to\n');
                fprintf('Snd(): share that pahandle audio device with Snd(), Beeper() et al. for optimal collaboration.\n');
                fprintf('Snd(): Consider using PsychPortAudio(''OpenSlave'', ...); on a master device and pass that slave\n');
                fprintf('Snd(): handle to Snd(''Open'', ...) if you want to allow Snd() to operate fully independently.\n');
                fprintf('Snd(): You may want to read ''help Snd'' about other points to consider wrt. pahandle sharing.\n\n');
            end
        end

        % Make signal stereo if it isn't already:
        if size(signal, 1) < 2
            sndbuff = [signal ; signal];
        else
            sndbuff = signal;
        end

        % Play it:
        PsychPortAudio('FillBuffer', pahandle, sndbuff);
        PsychPortAudio('Start', pahandle, 1, 0, 1);
    end

elseif strcmpi(command,'Wait')
    if nargin>1
        error('Wrong number of arguments: see Snd.');
    end

    if ~isempty(pahandle)
        % Wait blocking until end of playback:
        PsychPortAudio('Stop', pahandle, 1, 1);
    else
        WaitSecs(endTime-GetSecs); % Wait until any ongoing sound is done.
    end
    err=0;

elseif strcmpi(command,'IsPlaying')
    if nargin>1
        error('Wrong number of arguments: see Snd.');
    end

    if ~isempty(pahandle)
        props = PsychPortAudio('GetStatus', pahandle);
        err = props.Active;
    else
        if endTime>GetSecs
            err=1;
        else
            err=0;
        end
    end

elseif strcmpi(command,'Quiet') || strcmpi(command,'Close')
    if nargin > 2
        error('Wrong number of arguments: see Snd.');
    end

    if ~isempty(pahandle)
        % Stop playback asap, wait for stop:
        PsychPortAudio('Stop', pahandle, 2, 1);

        % Close command?
        if strcmpi(command,'Close') && ((ptb_snd_injected ~= 2) || (nargin == 2 && signal == 1))
            if ~ptb_snd_injected
                % Close it:
                PsychPortAudio('Close', pahandle);
            end

            pahandle = [];
            ptb_snd_injected = 0;
        end
    else
        if ~IsOctave
            clear playsnd; % Stop any ongoing sound.
        end
    end
    endTime=0;
    err=0;

elseif strcmpi(command,'DefaultRate')
    if nargin>1
        error('Wrong number of arguments: see Snd.');
    end

    if ptb_snd_oldstyle
        % Old style - old hard-coded default:
        err = 22254.5454545454; % default sampling rate in Hz.
    else
        % Audio device open?
        if isempty(pahandle)
            % No: Fake it - Use the most common sampling rate:
            err = 44100;
        else
            % Yes: Query its current sampling rate:
            s = PsychPortAudio('GetStatus', pahandle);
            err = s.SampleRate;
        end
    end

elseif strcmpi(command,'Open')
    endTime=0;
else
    PsychPortAudio('Close');
    pahandle = [];
    error(['unknown command "' command '"']);
end

return;