specscope.m 19 KB

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