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 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551
|
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 trivially mix well with simultaneous
% use of PsychPortAudio(), see below for how to make it work with PsychPortAudio().
%
% 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.
%
% By default, Snd() uses Matlabs or Octaves audioplayer() function for sound
% playback. This allows good interoperation with Screen()'s GStreamer based movie
% playback functionality, as well as with other running audio client applications.
% This has been tested under Octave 5+ and Matlab R2022b and R2023b on Windows-10,
% macOS 13 and Ubuntu 20.04-LTS and later. The downside is that audioplayer() operates
% with high audio latency, bad audio timing precision, unreliable and imprecise
% audio timestamps, as well as very limited audio control and functionality. It
% is mostly good enough for simple sound playback of stereo sound, e.g., some
% audio feedback tones, not more.
%
% Snd() can also be used with PsychPortAudio, but as 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']
% to enforce the old-style implementation of Snd(), which uses audioplayer().
% The command Snd('Oldstyle') also requests use of this old-style audioplayer() path.
%
% Creating a file named 'Snd_use_newstyle.txt' similar to above will enforce use
% of PsychPortAudio().
%
%
% 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 subfunctions:
% -----------------------
%
% Snd('Play', signal [, rate][, sampleSize]) plays a sound.
%
% rate = Snd('DefaultRate') returns the default sampling rate in Hz, which
% currently is 44100 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.
%
% 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
% audioplayer() implementation. Otherwise 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 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().
%
% 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.
%
% "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 the most
% common hardware sample rate of 44100 Hz. That value is returned by
% Snd('DefaultRate'). Other values can be specified.
%
% "samplesize". Snd accepts the sampleSize argument and passes it to the
% audioplayer() command. audioplayer (and therefore also Snd) may obey
% 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.
%
% NOTE: We suggest you always specify the "rate" parameter. If not
% specified, the sample rate, on all platforms, defaults to the most
% common hardware sample rate of 44100 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.
% 06/30/23 mk Remove Octave special cases, update help text. Octave 5+ does have
% sound() builtin, and on par in functionality with Matlab. Both use
% audioplayer() objects internally. All said, sound() may be now good
% enough to do without the new PsychPortAudio based path.
% 10/04/23 mk Switch the old style path from use of sound() to use of audioplayer(),
% as sound() is using that anyway on Octave and Matlab, so direct use
% gives us more control. Note: Both Octave (since at least version 5)
% and Matlab use Portaudio internally for audioplayer afaict, with the
% default audio playback device. This translates to the ALSA default device
% on Linux, ie. usually a running Pulseaudio or Pipewire desktop sound server.
% This allows possibly for interop between Snd() and GStreamer and other apps.
% 10/07/23 mk Switch default opmode to use audioplayer() instead of PsychPortAudio, update docs.
persistent ptb_snd_oldstyle;
persistent ptb_snd_injected;
persistent pahandle;
persistent player;
persistent verbose;
if isempty(verbose)
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
if strcmpi(command, 'Oldstyle') && isempty(ptb_snd_oldstyle)
ptb_snd_oldstyle = 1;
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
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 explicitly wants old-style implementation via Matlab/Octave
% audioplayer() function:
ptb_snd_oldstyle = 1;
if verbose
fprintf('Snd(): Forced use of Matlab/Octave audioplayer() function for sound output.\n');
end
elseif exist([PsychtoolboxConfigDir 'Snd_use_newstyle.txt'], 'file')
% User explicitly wants new-style implementation via PsychPortAudio():
ptb_snd_oldstyle = 0;
if verbose
fprintf('Snd(): Forced use of PsychPortAudio() for sound output.\n');
end
else
% Default, if nothing else is specified, is to use "old style" audioplayer(),
% as the audioplayer() implementation on current Matlab and Octave seems to
% be good enough as tested on all operating systems and has a good interop story
% with other audio clients, GStreamer etc., and usually with PsychPortAudio in
% reqlatencyclass modes 0 and 1, at least on macOS and Windows, and on Linux if
% Pulseaudio enabled libportaudio 19.8 or later is used:
ptb_snd_oldstyle = 1;
end
if ~ptb_snd_oldstyle
% User wants new-style PsychPortAudio variant, so do preinit:
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 audioplayer() 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 audioplayer() 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
% Reasonable default:
rate = 44100;
else
% Let PPA decide itself:
rate = [];
end
end
if ptb_snd_oldstyle
% Old-Style implementation via audioplayer() function:
% Wait until any ongoing sound is done.
while ~isempty(player) && isplaying(player)
drawnow;
WaitSecs('YieldSecs', 0.001);
end
% Stop and delete potentially existing old player object:
if ~isempty(player)
stop(player);
clear player;
end
% On Octave on Linux, do this little trickery to abuse PsychPortAudio
% to suppress ALSA debug messages triggered by the audioplayer() object:
if IsLinux && IsOctave
PsychPortAudio('Verbosity', PsychPortAudio('Verbosity'));
end
% Create new player object and start non-blocking playback:
player = audioplayer(signal', rate, sampleSize);
play(player);
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
% Wait until any ongoing sound is done.
while ~isempty(player) && isplaying(player)
drawnow;
WaitSecs('YieldSecs', 0.001);
end
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 ~isempty(player) && isplaying(player)
drawnow;
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
elseif ~isempty(player)
stop(player);
if strcmpi(command,'Close')
clear player;
end
end
err=0;
elseif strcmpi(command,'DefaultRate')
if nargin>1
error('Wrong number of arguments: see Snd.');
end
if ptb_snd_oldstyle
if ~isempty(player)
err = get(player, 'SampleRate');
else
% Old style - reasonable hard-coded default:
err = 44100; % default sampling rate in Hz.
end
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')
% Nothing to do right now.
else
PsychPortAudio('Close');
pahandle = [];
error(['unknown command "' command '"']);
end
return;
|