PageRenderTime 58ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/PsychHardware/DatapixxToolbox/DatapixxBasic/DatapixxAudioKey.m

http://psychtoolbox-3.googlecode.com/
MATLAB | 519 lines | 227 code | 75 blank | 217 comment | 46 complexity | 3eed6ae55789f14f42e1164b256a3975 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, LGPL-2.0
  1. function varargout = DatapixxAudioKey(cmd, varargin)
  2. % DatapixxAudioKey - A prototype implementation of a DataPixx
  3. % audio-key/voice-key.
  4. %
  5. % This allows to record audio from the Datapixx, optionally synchronized to
  6. % visual stimulus onset, optionally with simple but precise timestamping of
  7. % onset of audio signals.
  8. %
  9. % This is an experimental proof-of-concept prototype! Its name, behaviour
  10. % and interface may change without warning in future beta-releases!
  11. %
  12. %
  13. % Subfunctions and their meaning:
  14. % -------------------------------
  15. %
  16. % DatapixxAudioKey('Open' [, sampleRate][, lrMode][, inputJack][, inputGain]);
  17. % - Open audio system for recording. This will stop any running audio
  18. % acquisition operations. Audio sampling will be performed at the given
  19. % optional 'sampleRate' between 8000 Hz and 96Khz with the given number of
  20. % audio input channels 'lrMode' (0 = Mono: Average of left and right
  21. % channel, 1 = Only left channel, 2 = Only right channel, 3 = Stereo).
  22. % Optionally select 'inputJack' as audio input (1=Microphone, 2=Line-In)
  23. % and 'inputGain' as input amplifier gain. If no 'inputJack' is specified
  24. % and no input is preselected, 'inputJack' will default to microphone
  25. % input.
  26. %
  27. %
  28. % DatapixxAudioKey('Close');
  29. % - Close audio subsystem after stopping any running acquisition operations.
  30. %
  31. % [startTimeBox, startTimeGetSecs] = DatapixxAudioKey('CaptureNow' [, maxDurationSecs=30][, startOffsetSecs=0][,bufferBaseAddress=20e6] [, numBufferFrames=maxScheduleFrames]);
  32. % - Start audio capture immediately (ie., with minimum possible delay on
  33. % your system), return a 'startTimeBox' timestamp in Datapixx clock time of
  34. % when capture actually started or will start, taking the 'startOffsetSecs'
  35. % into account. You can also use the 2nd optional return argument
  36. % 'startTimeGetSecs' to get the same timestamp mapped to Psychtoolbox
  37. % GetSecs() time. For a more precise mapping to GetSecs time, you can
  38. % convert the 'startTimeBox' timestamp into Psychtoolbox GetSecs() time
  39. % with the proper mapping functions of PsychDataPixx (see "help
  40. % PsychDataPixx").
  41. %
  42. % The optional 'startOffsetSecs' allows to delay the start of the capture
  43. % operation by 'startOffsetSecs' seconds. The optional 'maxDurationSecs'
  44. % allows to define an upper limit onto the duration of the audio capture
  45. % operation. The operation will stop automatically after the given number
  46. % of seconds. By default, capture will run for 30 seconds. For
  47. % robustness and efficiency, you should specify a reasonable upper limit if
  48. % possible. 'bufferBaseAddress' is the memory start address of the audio
  49. % buffer for capture, 'numBufferFrames' is the size of the buffer. See
  50. % "Datapixx SetAudioSchedule?" for more info. Leave at default settings if
  51. % possible.
  52. %
  53. %
  54. % DatapixxAudioKey('CaptureAtFlip'[, flipCount=next][, maxDurationSecs=30][, startOffsetSecs=0][,bufferBaseAddress=20e6] [, numBufferFrames=maxScheduleFrames]);
  55. % - Schedule start of audio capture synchronized to the visual stimulus
  56. % onset of a future Screen('Flip') or Screen('AsyncFlipBegin') command.
  57. % All parameters are identical to the ones for DatapixxAudioKey('CaptureNow',...),
  58. % except for the first optional parameter 'flipCount'. 'flipCount' defines
  59. % at which Screen('Flip') audio capture should be started. If omitted or
  60. % set to zero or [], capture will be triggered by the next flip command.
  61. % Otherwise it will be triggered by the flip command with the given
  62. % 'flipCount'. You can query the current flipCount via PsychDatapixx('FlipCount').
  63. %
  64. % Example: Setting flipCount = PsychDatapixx('FlipCount') + 10; would start
  65. % audio capture at the 10'th invocation of a flip command from now.
  66. %
  67. % This function doesn't return a capture start timestamp as capture will
  68. % only happen in the future, so the timestamp is only available in the
  69. % future.
  70. %
  71. %
  72. % [audiodata, onsetTimeSecs] = DatapixxAudioKey('GetResponse'[, amountSecs=current][, blocking=1][, stopCapture=0]);
  73. % - Try to fetch available captured audiodata from a running capture
  74. % operation. Only call this function after a capture operation has been
  75. % started via DatapixxAudioKey('CaptureNow') or scheduled for start at a
  76. % certain flipCount via DatapixxAudioKey('CaptureAtFlip') and the flip is
  77. % imminent, i.e., after the flip command for that flip has been called
  78. % already. The function will error-out if you call it too early!
  79. %
  80. % The optional 'amountSecs' asks the driver to return exactly
  81. % 'amountSecs' worth of audio data. By default it will return whatever
  82. % is available at time of call. If 'amountSecs' is specified, but the
  83. % requested amount is not yet available, the optional 'blocking' flag will
  84. % define behaviour: If set to 1 (default), the driver will wait until the
  85. % specified amount becomes available. If set to 0, the driver will return
  86. % with empty [] return arguments so you can retry later. The optional
  87. % 'stopCapture' flag if set to 1 will stop audio capture, its default
  88. % setting of zero will keep capture running until manually stopped or until
  89. % it stops by itself.
  90. %
  91. % 'audiodata' is the vector or matrix of captured audiodata. 1 Row for mono
  92. % recording, or a 2-row matrix (one row for each audio channel) in stereo
  93. % recording modes. Each value is an audio signal sample in range [-1 ; 1].
  94. %
  95. % 'onsetTimeSecs' is the time since start of audio capture when a certain
  96. % level of loudness (as set by DatapixxAudioKey('TriggerLevel')) was first
  97. % exceeded, e.g., due to onset of a vocal response of a subject.
  98. %
  99. % 'readOffset' is the sample index of the next sample that will be read on
  100. % the next call to this function, if any.
  101. %
  102. %
  103. % oldLevel = DatapixxAudioKey('TriggerLevel' [, newLevel]);
  104. % - Return old and optionally set new trigger threshold level for the
  105. % timestamping of onset of audio signals in DatapixxAudioKey('GetResponse').
  106. %
  107. % 'oldLevel' is the current/old level. 'newLevel' is the optional new
  108. % level. Level can be between 0 and 1, with a default level of 0.1 for 10%
  109. % of max signal intensity as trigger level.
  110. %
  111. %
  112. % DatapixxAudioKey('StopCapture');
  113. % - Stop audio capture as soon as possible.
  114. %
  115. %
  116. persistent dpixaudioin;
  117. if nargin < 1 || isempty(cmd)
  118. error('Required Subcommand missing or empty!');
  119. end
  120. if isempty(dpixaudioin) || (dpixaudioin.refcount == 0)
  121. % Driver closed at startup:
  122. dpixaudioin.refcount = 0;
  123. dpixaudioin.samplerate = 0;
  124. dpixaudioin.pendingForFlip = -1;
  125. dpixaudioin.triggerLevel = 0.1;
  126. dpixaudioin.readOffset = 0;
  127. dpixaudioin.lrMode = 0;
  128. end
  129. if strcmpi(cmd, 'Open')
  130. % Open the audiokey: Performs basic init and setup of audio input
  131. % subsystem.
  132. if dpixaudioin.refcount > 0
  133. error('Open: Tried to open DatapixxAudioKey, but audio key already open!');
  134. end
  135. % Open device connection if not already open:
  136. PsychDataPixx('Open');
  137. if PsychDataPixx('IsBusy')
  138. error('Open: Tried to open DatapixxAudioKey, but Datapixx is busy! Screen flip pending?');
  139. end
  140. % Stop potentially running audio input schedule:
  141. Datapixx('StopMicrophoneSchedule');
  142. % TODO FIXME: How to decide if this is needed or harmful?
  143. Datapixx('InitAudio');
  144. % Disable loopback:
  145. Datapixx('DisableAudioLoopback');
  146. % Execute:
  147. Datapixx('RegWrRd');
  148. micstatus = Datapixx('GetMicrophoneStatus');
  149. % Assign new optional sampleRate:
  150. if length(varargin) >= 1 && ~isempty(varargin{1})
  151. dpixaudioin.samplerate = varargin{1};
  152. else
  153. dpixaudioin.samplerate = micstatus.scheduleRate;
  154. end
  155. % Assign new optional lrMode:
  156. if length(varargin) >= 2 && ~isempty(varargin{2})
  157. dpixaudioin.lrMode = varargin{2};
  158. else
  159. dpixaudioin.lrMode = micstatus.lrMode;
  160. end
  161. % Assign new optional input source:
  162. if length(varargin) >= 3 && ~isempty(varargin{3})
  163. inputjack = varargin{3};
  164. else
  165. % Leave current input setting "as is":
  166. inputjack = micstatus.source;
  167. % If no input set at all, default to microphone input:
  168. if inputjack == 0
  169. inputjack = 1;
  170. end
  171. end
  172. % Assign new optional input gain:
  173. if length(varargin) >= 4 && ~isempty(varargin{4})
  174. inputgain = varargin{4};
  175. else
  176. inputgain = micstatus.gain;
  177. end
  178. % Assign audio input source and input gain:
  179. Datapixx('SetMicrophoneSource', inputjack, inputgain);
  180. Datapixx('RegWrRd');
  181. % Mark us as open:
  182. dpixaudioin.refcount = dpixaudioin.refcount + 1;
  183. return;
  184. end
  185. if strcmpi(cmd, 'Close')
  186. if dpixaudioin.refcount <= 0
  187. % Noop:
  188. dpixaudioin.refcount = 0;
  189. return;
  190. end
  191. if PsychDataPixx('IsBusy')
  192. error('Close: Tried to close DatapixxAudioKey, but Datapixx is busy! Screen flip pending?');
  193. end
  194. % Stop potentially running audio input schedule:
  195. Datapixx('StopMicrophoneSchedule');
  196. % Execute:
  197. Datapixx('RegWrRd');
  198. % Close our connection:
  199. PsychDataPixx('Close');
  200. % Closed:
  201. dpixaudioin.refcount = 0;
  202. return;
  203. end
  204. % All following commands need the audiokey to be open:
  205. if dpixaudioin.refcount <= 0
  206. error('Tried to use audiokey, but audiokey not open!');
  207. end
  208. if strcmpi(cmd, 'CaptureNow')
  209. if dpixaudioin.pendingForFlip > -1
  210. error('CaptureNow: Tried to start or engage audiokey, but audiokey already started or engaged!');
  211. end
  212. if length(varargin) >= 1 && ~isempty(varargin{1})
  213. maxScheduleFrames = varargin{1} * dpixaudioin.samplerate;
  214. else
  215. % Default to 30 seconds if nothing provided:
  216. maxScheduleFrames = 30 * dpixaudioin.samplerate;
  217. end
  218. if length(varargin) >= 2 && ~isempty(varargin{2})
  219. scheduleOffsetSecs = varargin{2};
  220. else
  221. scheduleOffsetSecs = 0;
  222. end
  223. if PsychDataPixx('IsBusy')
  224. error('CaptureNow: Datapixx is busy! Screen flip pending?');
  225. end
  226. % Stop potentially running audio input schedule:
  227. Datapixx('StopMicrophoneSchedule');
  228. Datapixx('RegWrRd');
  229. % Init audio input schedule:
  230. if length(varargin) >= 3
  231. Datapixx('SetMicrophoneSchedule', scheduleOffsetSecs, dpixaudioin.samplerate, maxScheduleFrames, dpixaudioin.lrMode, varargin{3:end});
  232. else
  233. Datapixx('SetMicrophoneSchedule', scheduleOffsetSecs, dpixaudioin.samplerate, maxScheduleFrames, dpixaudioin.lrMode);
  234. end
  235. % Reset readOffset in audio capture buffer:
  236. dpixaudioin.readOffset = 0;
  237. % Start audio input schedule:
  238. Datapixx('StartMicrophoneSchedule');
  239. % Execute:
  240. Datapixx('RegWrRd');
  241. if nargout > 0
  242. % Return boxtime of start of capture:
  243. tStartBox = Datapixx('GetTime');
  244. varargout{1} = tStartBox + scheduleOffsetSecs;
  245. end
  246. if nargout > 1
  247. % Return GetSecs mapped time as well:
  248. tStartGetSecs = PsychDataPixx('FastBoxsecsToGetsecs', tStartBox);
  249. varargout{2} = tStartGetSecs;
  250. end
  251. % Mark capture as started:
  252. dpixaudioin.pendingForFlip = 0;
  253. return;
  254. end
  255. if strcmpi(cmd, 'CaptureAtFlip')
  256. if dpixaudioin.pendingForFlip > -1
  257. error('CaptureAtFlip: Tried to start or engage audiokey, but audiokey already started or engaged!');
  258. end
  259. if length(varargin) < 1 || isempty(varargin{1}) || varargin{1} == 0
  260. targetFlip = [];
  261. else
  262. targetFlip = varargin{1};
  263. end
  264. if targetFlip <= PsychDataPixx('FlipCount')
  265. % targetFlip set to a count that is already over. Pointless! Set it
  266. % to "next flip" instead:
  267. targetFlip = [];
  268. end
  269. if length(varargin) >= 2 && ~isempty(varargin{2})
  270. maxScheduleFrames = varargin{2} * dpixaudioin.samplerate;
  271. else
  272. % Default to 30 seconds if nothing provided:
  273. maxScheduleFrames = 30 * dpixaudioin.samplerate;
  274. end
  275. if length(varargin) >= 3 && ~isempty(varargin{3})
  276. scheduleOffsetSecs = varargin{3};
  277. else
  278. scheduleOffsetSecs = 0;
  279. end
  280. if PsychDataPixx('IsBusy')
  281. error('CaptureAtFlip: Datapixx is busy! Screen flip pending?');
  282. end
  283. % TODO FIXME: Should stop and configuration of schedule be part of the
  284. % command packet to be executed at flip? Would provide the ability to
  285. % "preprogram" complex sequences of audio response collection, but make
  286. % overall design much more complex?
  287. %
  288. % For now we go with the simple solution:
  289. % Stop potentially running audio input schedule:
  290. Datapixx('StopMicrophoneSchedule');
  291. % Init audio input schedule:
  292. if length(varargin) >= 4
  293. Datapixx('SetMicrophoneSchedule', scheduleOffsetSecs, dpixaudioin.samplerate, maxScheduleFrames, dpixaudioin.lrMode, varargin{4:end});
  294. else
  295. Datapixx('SetMicrophoneSchedule', scheduleOffsetSecs, dpixaudioin.samplerate, maxScheduleFrames, dpixaudioin.lrMode);
  296. end
  297. % Execute:
  298. Datapixx('RegWrRd');
  299. % Reset readOffset in audio capture buffer:
  300. dpixaudioin.readOffset = 0;
  301. % Build command string for psync-triggered start of schedule at target
  302. % flipcount:
  303. cstr = 'Datapixx(''StartMicrophoneSchedule'');';
  304. % Schedule cstr for execution at stimulus onset for the 'targetFlip'th
  305. % flip command execution:
  306. PsychDataPixx('ExecuteAtFlipCount', targetFlip, cstr);
  307. dpixaudioin.pendingForFlip = targetFlip;
  308. % Ready.
  309. return;
  310. end
  311. if strcmpi(cmd, 'StopCapture')
  312. if PsychDataPixx('IsBusy')
  313. error('StopCapture: Datapixx is busy! Screen flip pending?');
  314. end
  315. % Submit stop command immediately:
  316. Datapixx('StopMicrophoneSchedule');
  317. Datapixx('RegWrRd');
  318. % Reset flag:
  319. dpixaudioin.pendingForFlip = -1;
  320. return;
  321. end
  322. if strcmpi(cmd, 'TriggerLevel')
  323. varargout{1} = dpixaudioin.triggerLevel;
  324. if length(varargin) >= 1 && ~isempty(varargin{1})
  325. dpixaudioin.triggerLevel = varargin{1};
  326. end
  327. return;
  328. end
  329. if strcmpi(cmd, 'GetResponse')
  330. if dpixaudioin.pendingForFlip < 0
  331. error('GetResponse: Tried to get response from audiokey, but audiokey not active or engaged!');
  332. end
  333. % If audio recording was scheduled to start in sync with a certain
  334. % Screen flip, check if that specific target flip count has been
  335. % reached:
  336. % if (dpixaudioin.pendingForFlip > 0) && (PsychDataPixx('FlipCount') < dpixaudioin.pendingForFlip)
  337. if (dpixaudioin.pendingForFlip > 0) & (PsychDataPixx('FlipCount') < dpixaudioin.pendingForFlip)
  338. % Audio key shall start recording at a certain flipcount which
  339. % likely hasn't been reached yet (according to PsychDataPixx('FlipCount')).
  340. %
  341. % Multiple options:
  342. %
  343. % 1 We're more than one flip away from startpoint. In that case no
  344. % point waiting for start, as usercode would first need to
  345. % execute some Screen('Flip/AsyncFlipBegin') command to make it
  346. % even possible to start audio capture.
  347. %
  348. % 2 We're exactly one flip away and...
  349. % a) No flip command submitted by usercode -> No way this is
  350. % gonna fly.
  351. %
  352. % b) Asyncflip command submitted: Flip will eventually happen and
  353. % get us going. This is the case if the device is marked as "busy
  354. % waiting for psync" and async flip is marked as active.
  355. %
  356. % In case 1 or 2a we can't wait for a response, whereas in case 2b
  357. % we can wait for a response as it will eventually happen.
  358. % Check for case 1:
  359. if PsychDataPixx('FlipCount') < dpixaudioin.pendingForFlip - 1
  360. % More than 1 flip away. That's a no-no:
  361. fprintf('DatapixxAudioKey: Start of audio input scheduled for flipcount %i, but flipcount not yet reached.\n', dpixaudioin.pendingForFlip);
  362. fprintf('DatapixxAudioKey: Current count %i is more than 1 flip away! Impossible to "GetResponse" from device this way!\n', PsychDataPixx('FlipCount'));
  363. error('GetResponse: Tried to get response from audiokey, but audiokey not yet active and impossible to activate by this command-sequence!');
  364. end
  365. % Check for case 2b:
  366. if PsychDataPixx('IsBusy') && Screen('GetWindowInfo', PsychDataPixx('WindowHandle'), 4)
  367. % Asyncflip with Psync op pending. The flip will eventually
  368. % happen at some point in the future, so we can enter a polling
  369. % loop to wait for it to happen.
  370. else
  371. % 1 flip away but no flip scheduled to actually reach that
  372. % trigger-flip:
  373. fprintf('DatapixxAudioKey: Start of audio input scheduled for flipcount %i, but flipcount not yet reached!\n', dpixaudioin.pendingForFlip);
  374. fprintf('DatapixxAudioKey: Current count %i. Impossible to "GetResponse" from device this way,\n', PsychDataPixx('FlipCount'));
  375. fprintf('DatapixxAudioKey: because no flip has been scheduled to reach the trigger flipcount!\n');
  376. error('GetResponse: Tried to get response from audiokey, but audiokey not yet active and impossible to activate by this command-sequence!');
  377. end
  378. end
  379. if length(varargin) < 2 || isempty(varargin{2})
  380. blocking = 1;
  381. else
  382. blocking = varargin{2};
  383. end
  384. % Ok, should be already running or it is at least certain that it will
  385. % eventually run due to trigger by psync mechanism.
  386. %
  387. % Enter a polling loop to wait until the requested minimum amount of
  388. % audio data is available:
  389. if length(varargin) >= 1 && ~isempty(varargin{1})
  390. % Wait or poll for requested amount of audiodata:
  391. numFrames = ceil(varargin{1} * dpixaudioin.samplerate);
  392. while 1
  393. Datapixx('RegWrRd');
  394. micstatus = Datapixx('GetMicrophoneStatus');
  395. if micstatus.newBufferFrames >= numFrames
  396. % Requested amount of audio data available: Exit loop.
  397. break;
  398. else
  399. % Insufficient amount. If this is a polling request, we
  400. % simply return no result:
  401. if ~blocking
  402. varargout{1} = [];
  403. varargout{2} = [];
  404. return;
  405. end
  406. end
  407. % Sleep a msec, then retry:
  408. WaitSecs('YieldSecs', 0.001);
  409. end
  410. else
  411. % Just get current status - current amount of audiodata:
  412. Datapixx('RegWrRd');
  413. micstatus = Datapixx('GetMicrophoneStatus');
  414. numFrames = -1;
  415. end
  416. % Either requested amount of audio data available or no specific amount
  417. % requested. Stop schedule if requested by usercode:
  418. if length(varargin) >= 3 && ~isempty(varargin{3}) && varargin{3} == 1
  419. Datapixx('StopMicrophoneSchedule');
  420. Datapixx('RegWrRd');
  421. dpixaudioin.pendingForFlip = -1;
  422. micstatus = Datapixx('GetMicrophoneStatus');
  423. end
  424. % Fetch all captured frames if no specific amount requested:
  425. if numFrames == -1
  426. numFrames = micstatus.newBufferFrames;
  427. end
  428. % TODO FIXME: Does this make sense to use here?
  429. inDelaySecs = Datapixx('GetMicrophoneGroupDelay', dpixaudioin.samplerate);
  430. % Retrieve recorded data:
  431. audiodata = Datapixx('ReadMicrophoneBuffer', ceil(numFrames), -1);
  432. % Compute timestamp in seconds since start of capture of when the
  433. % triggerLevel was exceeded the first time:
  434. triggerTime = (dpixaudioin.readOffset + min(find(abs(audiodata(1,:)) > dpixaudioin.triggerLevel))) / dpixaudioin.samplerate; %#ok<MXFND>
  435. triggerTime = triggerTime - inDelaySecs;
  436. % Increment readOffset into audio capture buffer:
  437. dpixaudioin.readOffset = dpixaudioin.readOffset + size(audiodata, 2);
  438. % Return all data:
  439. varargout{1} = audiodata;
  440. varargout{2} = triggerTime;
  441. varargout{3} = dpixaudioin.readOffset;
  442. return;
  443. end
  444. error('DatapixxAudioKey: Unknown subcommand provided!');