specscopepp.m 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  1. function outdata=specscopepp(indata)
  2. global acq;
  3. h=hamming(5);
  4. mins=5e-008;
  5. maxs=1e-004;
  6. % record and plot audio spectrogram
  7. %
  8. % Usage: outdata=specscope(indata)
  9. %
  10. % Input: indata (optional)
  11. % Displays a recorded piece of data, if an argument is passed
  12. % Otherwise displays audio data from an attached microphone
  13. %
  14. % Output: outdata (optional)
  15. % If present, will return up to 10 minutes
  16. % of captured audio data.
  17. %
  18. % Note: Parameters such as sampling frequency, number of tapers
  19. % and display refresh rate may be set below if desired.
  20. % You can also acquire data from a national instruments card.
  21. %
  22. close all;
  23. %=======================================================================
  24. % Check toolboxes
  25. %=======================================================================
  26. % Check for toolboxes
  27. if not(exist('analoginput','file'));
  28. fprintf('You need to install the DAQ toolbox first\n');
  29. return
  30. end
  31. if not(exist('mtspecgramc','file'));
  32. fprintf('You need to install the Chronux toolbox first from http://chronux.org/\n');
  33. return
  34. end
  35. %=======================================================================
  36. % Set parameters
  37. %=======================================================================
  38. % Set defaults
  39. acq.params.Fs = 44100;
  40. acq.pause = 0;
  41. acq.skips = 0;
  42. acq.stop = 0;
  43. acq.restart = 0;
  44. acq.plot_frequency = 20;
  45. acq.samples_acquired = 0;
  46. acq.spectra = [];
  47. acq.times = [];
  48. defaults
  49. audio_instr;
  50. fig=create_ui;
  51. %=======================================================================
  52. % Check arguments, start DAQ
  53. %=======================================================================
  54. if nargout
  55. % save up to ten minutes data, preallocated...
  56. fprintf('Pre-allocating memory for data save - please be patient.\n');
  57. outdata=zeros( (acq.params.Fs * 60 * 10), 1 );
  58. end
  59. if nargin == 1;
  60. acq.indata = indata;
  61. acq.live = 0;
  62. else
  63. % Create and set up and start analog input daq
  64. input=1;
  65. if input==1;
  66. acq.ai = analoginput('winsound');
  67. addchannel( acq.ai, 1 );
  68. else
  69. acq.ai = analoginput('nidaq', 1);
  70. addchannel(acq.ai, 0);
  71. set(acq.ai,'InputType','SingleEnded')
  72. set(acq.ai,'TransferMode','Interrupts')
  73. set(acq.ai,'TriggerType','Manual');
  74. end
  75. set( acq.ai, 'SampleRate', acq.params.Fs )
  76. acq.params.Fs = get( acq.ai, 'SampleRate' );
  77. acq.samples_per_frame = acq.params.Fs / acq.plot_frequency;
  78. set( acq.ai, 'SamplesPerTrigger', inf )
  79. start(acq.ai)
  80. acq.live = 1;
  81. if input==2;
  82. trigger(acq.ai);
  83. end
  84. end
  85. %=======================================================================
  86. % The scope main loop
  87. %=======================================================================
  88. acq.t0=clock;
  89. acq.tn=clock;
  90. % Loop over frames to acquire and display
  91. while 1;
  92. % Check for quit signal
  93. if acq.stop;
  94. break;
  95. end
  96. % Calculate times
  97. calctime = acq.samples_acquired / acq.params.Fs;
  98. acq.samples_acquired = acq.samples_acquired + acq.samples_per_frame;
  99. acq.t1 = clock;
  100. elapsed = etime(acq.t1,acq.t0);
  101. % Get a small snippet of data
  102. if ( acq.live )
  103. data = getdata( acq.ai, acq.samples_per_frame );
  104. else
  105. while elapsed < acq.samples_acquired / acq.params.Fs
  106. pause( acq.samples_acquired / (acq.params.Fs) - elapsed );
  107. acq.t1=clock;
  108. elapsed = etime(acq.t1,acq.t0);
  109. end
  110. if acq.samples_acquired + 2 * acq.samples_per_frame >= length( acq.indata )
  111. acq.stop=1;
  112. end
  113. data = acq.indata(floor(acq.samples_acquired+1):floor(acq.samples_per_frame+acq.samples_acquired));
  114. end
  115. if nargout
  116. outdata(floor(acq.samples_acquired+1):floor(acq.samples_acquired+length(data))) = data(:);
  117. end
  118. if acq.restart;
  119. acq.restart = 0;
  120. acq.spectra = [];
  121. acq.times = [];
  122. end
  123. data=conv(h,data);
  124. data=sign(data-acq.threshold);
  125. % Calculate spectrogram of data snippet
  126. if acq.deriv
  127. [s, t, f] = mtspecgramc(abs(diff(data)), acq.moving_window, acq.params );
  128. else
  129. [s, t, f] = mtspecgramc(diff(data), acq.moving_window, acq.params );
  130. end
  131. % Add new spectra to that already calculated
  132. acq.times = [acq.times t+calctime];
  133. if acq.log
  134. acq.spectra = [acq.spectra log(s')];
  135. else
  136. acq.spectra = [acq.spectra s'];
  137. end
  138. % Remove old spectra once window reaches desired size
  139. while acq.times(1,end) - acq.times(1,1) > acq.display_size;
  140. % Ring buffer!
  141. y = length(t);
  142. acq.times(:,1:y) = [];
  143. acq.spectra(:,1:y) = [];
  144. end
  145. % Only plot if display is keeping up with real time and not paused
  146. show_plot=1;
  147. if nargin==0
  148. if get(acq.ai, 'SamplesAvailable' ) > 10 * acq.samples_per_frame && acq.pause==0
  149. show_plot=0;
  150. end
  151. else
  152. if elapsed > calctime + 0.5
  153. show_plot=0;
  154. end
  155. end
  156. if acq.pause
  157. show_plot=0;
  158. end
  159. if show_plot
  160. if acq.bgsub
  161. acq.mean_spectra = mean( acq.spectra, 2 );
  162. end
  163. % Normalize until full screen passes by if requested
  164. if acq.normalize>=1;
  165. if acq.normalize==1
  166. acq.tn=clock;
  167. acq.normalize=2;
  168. end
  169. if etime(clock,acq.tn)>1.25*acq.display_size
  170. acq.normalize=0;
  171. end
  172. mins = min(min(acq.spectra));
  173. maxs = max(max(acq.spectra));
  174. end
  175. % Scale the spectra based upon current offset and scale
  176. if acq.bgsub
  177. scaled_spectra = acq.offset + ( acq.scale ) * ( acq.spectra - repmat( acq.mean_spectra, [1,size(acq.spectra,2)]) ) / ( maxs - mins );
  178. else
  179. scaled_spectra = acq.offset + acq.scale * ( acq.spectra - mins ) / ( maxs - mins );
  180. end
  181. % Draw the image to the display
  182. image( acq.times, f, scaled_spectra, 'parent', acq.ax1 ); axis([acq.ax1],'xy');
  183. drawnow;
  184. else
  185. % Keep track of skipped displays
  186. acq.skips = acq.skips + 1;
  187. end
  188. end
  189. %=======================================================================
  190. % Clean up
  191. %=======================================================================
  192. acq.t1=clock;
  193. elapsed = etime(acq.t1,acq.t0);
  194. fprintf( 'Elapsed time %f seconds\n', elapsed );
  195. % Warn if many skips were encountered
  196. if acq.skips > 5;
  197. fprintf( '\nWARNING:\nThis program skipped plotting %d times to keep pace.\n', acq.skips )
  198. fprintf( 'Run again without keyboard interaction or changing the figure size.\n' )
  199. fprintf( 'If this message reappears you should reduce the plot frequency parameter.\n\n' );
  200. end
  201. % Clean up the analoginput object
  202. if acq.live
  203. stop(acq.ai);delete( acq.ai );clear acq.ai;
  204. end
  205. % Clean up the figure
  206. delete(fig);
  207. delete(gcf);
  208. if nargout
  209. % save up to ten minutes data, preallocated...
  210. fprintf('Saving output data\n');
  211. outdata=outdata(1:floor(acq.samples_acquired));
  212. end
  213. return;
  214. %
  215. %
  216. %=======================================================================
  217. % Functions called
  218. %=======================================================================
  219. %
  220. %
  221. %=======================================================================
  222. % Handle Keypresses
  223. %=======================================================================
  224. % Handle figure window keypress events
  225. function keypress(varargin)
  226. global acq;
  227. keypressed=get(gcf,'CurrentCharacter');
  228. % ignore raw control, shift, alt keys
  229. if keypressed;
  230. % Offset changes
  231. increment=1;
  232. if strcmp( keypressed, 'l');
  233. acq.offset = acq.offset - increment;
  234. elseif strcmp( keypressed, 'o');
  235. acq.offset = acq.offset + increment;
  236. % Scale changes
  237. elseif strcmp( keypressed, 'x');
  238. acq.scale = acq.scale - increment;
  239. elseif strcmp( keypressed, 's');
  240. acq.scale = acq.scale + increment;
  241. % Reset defaults
  242. elseif strcmp( keypressed, 'd');
  243. defaults
  244. acq.restart=1;
  245. % Normalize spectra
  246. elseif strcmp( keypressed, 'n');
  247. request_normalize
  248. % Quit
  249. elseif strcmp( keypressed, 'q');
  250. request_quit
  251. % Pause
  252. elseif strcmp( keypressed, 'p');
  253. request_pause
  254. % Help
  255. elseif strcmp( keypressed, 'h');
  256. audio_instr
  257. elseif strcmp( keypressed, 't');
  258. acq.threshold = acq.threshold + 0.01;
  259. elseif strcmp( keypressed, 'g');
  260. acq.threshold = acq.threshold - 0.01;
  261. % Change colormaps for 0-9,a-c
  262. elseif strcmp( keypressed, '0' );
  263. colormap( 'jet' );
  264. elseif strcmp( keypressed, '1' );
  265. colormap( 'bone' );
  266. elseif strcmp( keypressed, '2' );
  267. colormap( 'colorcube' );
  268. elseif strcmp( keypressed, '3' );
  269. colormap( 'cool' );
  270. elseif strcmp( keypressed, '4' );
  271. colormap( 'copper' );
  272. elseif strcmp( keypressed, '5' );
  273. colormap( 'gray' );
  274. elseif strcmp( keypressed, '6' );
  275. colormap( 'hot' );
  276. elseif strcmp( keypressed, '7' );
  277. colormap( 'hsv' );
  278. elseif strcmp( keypressed, '8' );
  279. colormap( 'autumn' );
  280. elseif strcmp( keypressed, '9' );
  281. colormap( 'pink' );
  282. elseif strcmp( keypressed, 'a' );
  283. colormap( 'spring' );
  284. elseif strcmp( keypressed, 'b' );
  285. colormap( 'summer' );
  286. elseif strcmp( keypressed, 'c' );
  287. colormap( 'winter' );
  288. end
  289. update_display
  290. end
  291. return
  292. %=======================================================================
  293. % Defaults
  294. %=======================================================================
  295. % Reset defaults
  296. function defaults()
  297. global acq;
  298. acq.params.raw_tapers = [3 5];
  299. acq.moving_window = [0.02 0.01];
  300. acq.params.tapers=dpsschk(acq.params.raw_tapers,round(acq.params.Fs*acq.moving_window(1)),acq.params.Fs);
  301. acq.offset = 0;
  302. acq.scale = 500;
  303. acq.display_size = 3;
  304. acq.params.fpass = [50 20000];
  305. acq.deriv=1;
  306. acq.log=0;
  307. acq.bgsub = 1;
  308. acq.params.pad= 0;
  309. acq.normalize = 0;
  310. acq.threshold=0;
  311. return
  312. function update_display()
  313. global acq;
  314. set(acq.tapers_ui,'String',sprintf( '%.0f %.0f', acq.params.raw_tapers(1), acq.params.raw_tapers(2) ));
  315. set(acq.window_ui,'String',sprintf( '%.2f %.2f', acq.moving_window(1), acq.moving_window(2) ));
  316. set(acq.offset_ui,'String',sprintf( '%d', acq.offset ));
  317. set(acq.scale_ui,'String',sprintf( '%d', acq.scale ));
  318. set(acq.display_size_ui,'String',sprintf( '%.1f', acq.display_size ));
  319. set(acq.frequency_ui,'String',sprintf( '%.1f %.1f', acq.params.fpass(1), acq.params.fpass(2) ))
  320. set(acq.derivative_ui,'Value',acq.deriv);
  321. set(acq.log_ui,'Value',acq.log);
  322. set(acq.bgsub_ui,'Value',acq.bgsub);
  323. set(acq.threshold_ui,'String',sprintf( '%.2f', acq.threshold ));
  324. return
  325. %=======================================================================
  326. % Update ui controls
  327. %=======================================================================
  328. function request_quit(varargin)
  329. global acq;
  330. acq.stop=1;
  331. return
  332. function request_pause(varargin)
  333. global acq;
  334. acq.pause = not( acq.pause );
  335. return
  336. function request_normalize(varargin)
  337. global acq;
  338. acq.normalize = 2;
  339. return
  340. function update_defaults(varargin)
  341. global acq;
  342. defaults
  343. update_display
  344. acq.restart=1;
  345. return
  346. function update_tapers(varargin)
  347. global acq;
  348. acq.params.raw_tapers = sscanf(get( gco, 'string' ),'%f %d')';
  349. acq.params.tapers=dpsschk(acq.params.raw_tapers,round(acq.params.Fs*acq.moving_window(1)),acq.params.Fs); % check tapers
  350. return
  351. function update_window(varargin)
  352. global acq;
  353. acq.moving_window = sscanf(get( gco, 'string' ),'%f %f');
  354. acq.params.tapers=dpsschk(acq.params.raw_tapers,round(acq.params.Fs*acq.moving_window(1)),acq.params.Fs);
  355. acq.restart = 1;
  356. return
  357. function update_offset(varargin)
  358. global acq;
  359. acq.offset = sscanf(get( gco, 'string' ),'%f');
  360. return
  361. function update_scale(varargin)
  362. global acq;
  363. acq.scale = sscanf(get( gco, 'string' ),'%f');
  364. return
  365. function update_display_size(varargin)
  366. global acq;
  367. acq.display_size = sscanf(get( gco, 'string' ),'%f');
  368. return
  369. function update_fpass(varargin)
  370. global acq;
  371. acq.params.fpass = sscanf(get( gco, 'string' ),'%f %f');
  372. acq.restart = 1;
  373. return
  374. function update_deriv(varargin)
  375. global acq;
  376. acq.deriv=get( gco, 'Value' );
  377. acq.normalize=1;
  378. return
  379. function update_log(varargin)
  380. global acq;
  381. acq.log=get( gco, 'Value' );
  382. acq.normalize=1;
  383. return
  384. function update_bgsub(varargin)
  385. global acq;
  386. acq.bgsub=get( gco, 'Value' );
  387. return
  388. function update_threshold(varargin)
  389. global acq;
  390. acq.threshold = sscanf(get( gco, 'string' ),'%f');
  391. return
  392. %=======================================================================
  393. % UI display
  394. %=======================================================================
  395. function fig=create_ui()
  396. global acq;
  397. bgcolor = [.7 .7 .7];
  398. % ===Create main figure==========================
  399. fig = figure('Position',centerfig(800,600),...
  400. 'NumberTitle','off',...
  401. 'Name','Real-time spectrogram',...
  402. 'doublebuffer','on',...
  403. 'HandleVisibility','on',...
  404. 'Renderer', 'openGL', ...
  405. 'KeyPressFcn', @keypress, ...
  406. 'Color',bgcolor);
  407. acq.ax1 = axes('position', [0.05,0.1,0.9,0.85]);
  408. colormap( 'autumn' );
  409. % ===text==========
  410. uicontrol(gcf,'Style','text',...
  411. 'String', 'tapers',...
  412. 'Position',[225 20 45 20],...
  413. 'BackgroundColor',bgcolor);
  414. uicontrol(gcf,'Style','text',...
  415. 'String', 'moving win',...
  416. 'Position',[300 20 70 20],...
  417. 'BackgroundColor',bgcolor);
  418. uicontrol(gcf,'Style','text',...
  419. 'String', 'offset',...
  420. 'Position',[375 20 30 20],...
  421. 'BackgroundColor',bgcolor);
  422. uicontrol(gcf,'Style','text',...
  423. 'String', 'scale',...
  424. 'Position',[410 20 30 20],...
  425. 'BackgroundColor',bgcolor);
  426. uicontrol(gcf,'Style','text',...
  427. 'String', 't axis',...
  428. 'Position',[445 20 30 20],...
  429. 'BackgroundColor',bgcolor);
  430. uicontrol(gcf,'Style','text',...
  431. 'String', 'f axis',...
  432. 'Position',[480 20 40 20],...
  433. 'BackgroundColor',bgcolor);
  434. uicontrol(gcf,'Style','text',...
  435. 'String', 'abs',...
  436. 'Position',[550 20 35 20],...
  437. 'BackgroundColor',bgcolor);
  438. uicontrol(gcf,'Style','text',...
  439. 'String', 'log',...
  440. 'Position',[580 20 35 20],...
  441. 'BackgroundColor',bgcolor);
  442. uicontrol(gcf,'Style','text',...
  443. 'String', 'bgsub',...
  444. 'Position',[610 20 35 20],...
  445. 'BackgroundColor',bgcolor);
  446. uicontrol(gcf,'Style','text',...
  447. 'String', 'thresh',...
  448. 'Position',[645 20 35 20],...
  449. 'BackgroundColor',bgcolor);
  450. % ===The quit button===============================
  451. uicontrol('Style','pushbutton',...
  452. 'Position',[5 5 45 20],...
  453. 'String','Quit',...
  454. 'Interruptible','off',...
  455. 'BusyAction','cancel',...
  456. 'Callback',@request_quit);
  457. % ===The pause button===============================
  458. uicontrol('Style','pushbutton',...
  459. 'Position',[55 5 45 20],...
  460. 'String','Pause',...
  461. 'Interruptible','off',...
  462. 'BusyAction','cancel',...
  463. 'Callback',@request_pause);
  464. % ===The defaults button===============================
  465. uicontrol('Style','pushbutton',...
  466. 'Position',[105 5 50 20],...
  467. 'String','Defaults',...
  468. 'Interruptible','off',...
  469. 'BusyAction','cancel',...
  470. 'Callback',@update_defaults);
  471. % ===The normalize button===============================
  472. uicontrol('Style','pushbutton',...
  473. 'Position',[160 5 60 20],...
  474. 'String','Normalize',...
  475. 'Interruptible','off',...
  476. 'BusyAction','cancel',...
  477. 'Callback',@request_normalize );
  478. % ===Tapers============================================
  479. acq.tapers_ui = uicontrol(gcf,'Style','edit',...
  480. 'String', sprintf( '%.0f %.0f', acq.params.raw_tapers(1), acq.params.raw_tapers(2) ),...
  481. 'Position',[225 5 70 20],...
  482. 'CallBack', @update_tapers);
  483. % ===Window============================================
  484. acq.window_ui=uicontrol(gcf,'Style','edit',...
  485. 'String', sprintf( '%.2f %.2f', acq.moving_window(1), acq.moving_window(2) ),...
  486. 'Position',[300 5 70 20],...
  487. 'CallBack', @update_window);
  488. % ===Offset============================================
  489. acq.offset_ui = uicontrol(gcf,'Style','edit',...
  490. 'String', sprintf( '%d', acq.offset ),...
  491. 'Position',[375 5 30 20],...
  492. 'CallBack', @update_offset);
  493. % ===Scale============================================
  494. acq.scale_ui = uicontrol(gcf,'Style','edit',...
  495. 'String', sprintf( '%d', acq.scale ),...
  496. 'Position',[410 5 30 20],...
  497. 'CallBack', @update_scale);
  498. % ===display size======================================
  499. acq.display_size_ui = uicontrol(gcf,'Style','edit',...
  500. 'String', sprintf( '%.1f', acq.display_size ),...
  501. 'Position',[445 5 30 20],...
  502. 'CallBack', @update_display_size);
  503. % ===frequency axis=====================================
  504. acq.frequency_ui = uicontrol(gcf,'Style','edit',...
  505. 'String', sprintf( '%.1f %.1f', acq.params.fpass(1), acq.params.fpass(2) ),...
  506. 'Position',[480 5 80 20],...
  507. 'CallBack', @update_fpass);
  508. % ===derivative=====================================
  509. acq.derivative_ui = uicontrol(gcf,'Style','checkbox',...
  510. 'Value',acq.deriv,...
  511. 'Position',[565 5 20 20],...
  512. 'CallBack', @update_deriv);
  513. % ===log=====================================
  514. acq.log_ui = uicontrol(gcf,'Style','checkbox',...
  515. 'Value',acq.log,...
  516. 'Position',[590 5 20 20],...
  517. 'CallBack', @update_log);
  518. % ===bgsub=====================================
  519. acq.bgsub_ui = uicontrol(gcf,'Style','checkbox',...
  520. 'Value',acq.bgsub,...
  521. 'Position',[615 5 20 20],...
  522. 'CallBack', @update_bgsub);
  523. % ===threshold======================================
  524. acq.threshold_ui = uicontrol(gcf,'Style','edit',...
  525. 'String', sprintf( '%.2f', acq.threshold ),...
  526. 'Position',[640 5 40 20],...
  527. 'CallBack', @update_threshold);
  528. return
  529. %=======================================================================
  530. % Assorted functions
  531. %=======================================================================
  532. function pos = centerfig(width,height)
  533. % Find the screen size in pixels
  534. screen_s = get(0,'ScreenSize');
  535. pos = [screen_s(3)/2 - width/2, screen_s(4)/2 - height/2, width, height];
  536. return
  537. function audio_instr()
  538. % Show instructions
  539. fprintf('INSTRUCTIONS:\n');
  540. fprintf('Click on figure window first to activate controls.\n')
  541. fprintf('Adjust tapers, windows, scales, offsets and axes using the gui\n');
  542. fprintf('The abs checkbox toggles abs of the data\n');
  543. fprintf('The log checkbox toggles a log of the spectrum\n');
  544. fprintf('Press d or use defaults button to reset most parameters to defaults.\n')
  545. fprintf('Press n or use normalize button to normalize spectra based upon values in current display.\n')
  546. fprintf('Press 0-9,a-c to choose a colormap (default 0).\n')
  547. fprintf('Press p to pause and unpause display.\n')
  548. fprintf('Press t and g to adjust threshold, or use offset textbox on gui.\n');
  549. fprintf('Press o and l to adjust offset, or use offset textbox on gui.\n');
  550. fprintf('Press s and x to adjust scale, or use scale textbox on gui.\n');
  551. fprintf('Press h for this message.\n')
  552. fprintf('Press q to quit, or use quit button on gui.\n\n')
  553. return