nback_matlab.m 52 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070
  1. function handles = nback_matlab
  2. %% INFORMATION:
  3. %
  4. % -Keep the 'nback_matlab.m' function within the same folder as the subfolder
  5. % 'Audio', which contains audio files required for the program to run. As
  6. % long as 'nback_matlab.m' is on Matlab's path, the program will handle
  7. % adding the audio files to the path.
  8. % -the folder 'util' contains an important utility function required for
  9. % the program to run
  10. %
  11. % -A 'UserData' subfolder will be written to the same location as
  12. % 'nback_matlab.m the first time the program is run. User performance
  13. % data will be stored in a .mat file within this folder
  14. % ('user_data.mat'). Also, whenever the program closes, the
  15. % 'nback_matlab_settings.txt' file will be updated with the user's most
  16. % recent preferences (this will be loaded upon program reboot).
  17. % -the user_data.mat file contains a variable 'summaryStats' that
  18. % summarizes A' (see below), hit rate, false alarm rate, and lure error
  19. % rate for each stimuli type (position, sound, color); it also contains a
  20. % 'trialData' cell array that shows actual user responses for every trial
  21. % (rows of table) of each round of n-back played (cells)
  22. %
  23. % *Scoring: A' (Discrimination Index), similar to d'
  24. % H = Hit Rate (hits / # signal trials),
  25. % F = False-positive Rate (false pos / # noise trials)
  26. % aPrime = .5 + sign(H - F) * ((H - F)^2 + abs(H - F)) / (4 * max(H, F) - 4 * H * F);
  27. %
  28. % A' references:
  29. % Snodgrass, J. G., & Corwin, J. (1988). Pragmatics of measuring recognition
  30. % memory: applications to dementia and amnesia. Journal of Experimental
  31. % Psychology: General, 117(1), 34.
  32. %
  33. % Stanislaw, H., & Todorov, N. (1999). Calculation of signal detection theory
  34. % measures. Behavior research methods, instruments, & computers, 31(1),
  35. % 137-149.
  36. %
  37. % *For mathematical formula, see Stanislaw & Todorov (1999, p. 142)
  38. %
  39. % *Default Settings:
  40. % nback = 2; % nBack level
  41. % percLure = .2; % percentage of non-match trials to be converted to
  42. % "lure" or interference trials (a lure is n-back +/- 1, requiring
  43. % engagement of executive control to avoid errors)
  44. % sound_on = 1; % sound-type nBack: 1 (on), 0 (off)
  45. % position_on = 1; % position-type nBack: 1 (on), 0 (off)
  46. % color_on = 0; % color-type nBack: 1 (on), 0 (off)
  47. % completely_random = 0; % off (0), on (1) (if on, next 3 settings are irrelevant)
  48. % n_positionHits = 4; % control # of matches for position
  49. % n_soundHits = 4; % control # of matches for sound
  50. % n_colorHits = 4; % control # of matches for color
  51. % advance_thresh = .9; % minimum score (A') required to advance automatically to next nBack level;
  52. % fallback_thresh = .75; % score (A') below which one automatically regresses to previous nBack level;
  53. % nTrials = nback + 20; % # trials
  54. % trial_time = 2.4; % seconds
  55. % volume_factor = 1; % Default: 1 (no change); >1 = increase volume; <1 = decrease volume
  56. % sound_type = 1; % use Numbers-Female (1), Numbers-Male (2), Letters-Female1 (3), or Letters-Female2 (4)
  57. % enable_applause = 1; % 1 (on) or 0 (off); applause on advance to next n-back level
  58. % enable_boos = 0; % 1 (on) or 0 (off); comical boos if fallback to previous n-back level
  59. % realtime_feedback = 1; % 1 (on) or 0 (off); highlight labels red/green based on accuracy
  60. % figure_window_feedback = 1; % 1 (on) or 0 (off); display hits, misses, false alarms in figure window upon completion
  61. % command_line_feedback = 1; % 1 (on) or 0 (off); display hits, misses, false alarms in command window upon completion
  62. % show_remaining = 1; % 1 (on) or 0 (off); show remaining trials
  63. % background_color_scheme = 1; % black (1), white (2)
  64. %
  65. % *Author: Elliot A. Layden (2017-18),
  66. % at The Environmental Neuroscience Laboratory, The University of Chicago
  67. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  68. %% Hotkey Designations:
  69. startSessionKey = 'space';
  70. positionMatchKey = 'a';
  71. soundMatchKey = 'l';
  72. colorMatchKey = 'j';
  73. stopRunningKey = 'escape';
  74. %% ------------------------ Begin ------------------------
  75. customizable = {'nback','percLure','sound_on','position_on','color_on','completely_random',...
  76. 'n_soundHits','n_positionHits','n_colorHits','advance_thresh',...
  77. 'fallback_thresh','nTrials','trial_time','volume_factor',...
  78. 'sound_type','enable_applause','enable_boos','realtime_feedback',...
  79. 'command_line_feedback','figure_window_feedback','show_remaining',...
  80. 'background_color_scheme'};
  81. % Initialize Global Variables:
  82. curr_running = 0; stop_running = 0; ix = 1; canResize = false;
  83. position_mem = []; color_mem = []; sound_mem = [];
  84. position_match_vec = []; sound_match_vec = []; color_match_vec = [];
  85. user_position_match = []; user_sound_match = []; user_color_match = [];
  86. position_lures_vec = []; sound_lures_vec = []; color_lures_vec = [];
  87. advance_thresh = []; fallback_thresh = [];
  88. figure_window_feedback = []; command_line_feedback = []; volume_factor = [];
  89. h_pos_feedback1_pos = []; h_pos_feedback2_pos = [];
  90. h_color_feedback1_pos = []; h_color_feedback2_pos = [];
  91. h_sound_feedback1_pos = []; h_sound_feedback2_pos = [];
  92. percLure = .2; timerVal = 0; times = []; extraKeys = {};
  93. % Initialize object handles:
  94. h_title = 10.384372; h_volume = 28.1717839;
  95. h_pos_feedback1 = 10.384372; h_pos_feedback2 = 10.384372;
  96. h_sound_feedback1 = 10.384372; h_sound_feedback2 = 10.384372;
  97. h_color_feedback1 = 10.384372; h_color_feedback2 = 10.384372;
  98. square_width = .2;
  99. positions = repmat([.05,.05,square_width,square_width],9,1);
  100. % Get script path and subfolders:
  101. script_fullpath = mfilename('fullpath');
  102. [script_path,~,~] = fileparts(script_fullpath);
  103. addpath(genpath(script_path))
  104. audio_path = fullfile(script_path,'Audio');
  105. audio_subfolders = {fullfile(audio_path,'numbers-female'),fullfile(audio_path,'numbers-male'),...
  106. fullfile(audio_path,'letters-female1'),fullfile(audio_path,'letters-female2')};
  107. % Get Settings:
  108. get_settings;
  109. % Calculate Parameters
  110. [~,nBack_spec] = min(abs((1:20)-nback));
  111. poss_lures = 0:.05:1;
  112. [~,lure_spec] = min(abs(poss_lures-percLure));
  113. trial_num_opts = 15:5:100; % nTrials
  114. n_trial_num_opts = length(trial_num_opts);
  115. if nTrials==nback+20
  116. trial_num_spec = 1;
  117. else trial_num_spec = 1+min(abs(trial_num_opts-nTrials));
  118. end
  119. trial_time_opts = 1:.2:4; % seconds;
  120. n_trial_time_opts = length(trial_time_opts);
  121. [~,trial_time_spec] = min(abs(trial_time_opts-trial_time));
  122. % Appearance Customization:
  123. if background_color_scheme==1
  124. txt_color = ones(1,3);
  125. background_color = zeros(1,3);
  126. colors = {'y','m','c','r','g','b','w'};
  127. elseif background_color_scheme==2
  128. txt_color = zeros(1,3);
  129. background_color = ones(1,3);
  130. colors = {'y','m','c','r','g','b','k'};
  131. end
  132. % Load Main Audio:
  133. audio_dat = struct('sound',cell(1,4),'Fs',cell(1,4),'nSound',cell(1,4));
  134. for i = 1:4
  135. listing = dir(audio_subfolders{i});
  136. nSound = length(listing)-2;
  137. audio_dat(i).sound = cell(1,nSound);
  138. audio_dat(i).Fs = cell(1,nSound);
  139. audio_dat(i).nSound = nSound;
  140. for j = 1:nSound
  141. [audio_dat(i).sound{j},audio_dat(i).Fs{j}] = audioread(fullfile(audio_subfolders{i},listing(j+2).name));
  142. end
  143. end
  144. % Load Reaction Sounds (Applause/Boos):
  145. [applause_dat, Fs_applause] = audioread(fullfile(audio_path,'applause.mp3'));
  146. [boo_dat, Fs_boo] = audioread(fullfile(audio_path,'boo.mp3'));
  147. % Initialize Figure:
  148. handles.figure = figure('menubar','none','color',background_color,'numbertitle',...
  149. 'off','name',['nback_matlab: (',num2str(nback),'-Back)'],'units','norm',...
  150. 'Position',[.271,.109,.488,.802],'ResizeFcn',@resizeScreen);
  151. handles.axes1 = gca; set(handles.axes1,'Position',[0,0,1,1],'XLim',[0,1],...
  152. 'YLim',[0,1],'Visible','off');
  153. h_title = annotation('textbox','String','','FontName','Helvetica','FontSize',14,...
  154. 'Position',[.4,.955,.2,.05],'Color',txt_color,'HorizontalAlignment','center',...
  155. 'FontWeight','bold','EdgeColor','none');
  156. %% Menus:
  157. % File Menu:
  158. file_menu = uimenu(handles.figure,'Label','File');
  159. % uimenu(file_menu,'Label','Progress Report','Callback',@show_progress);
  160. uimenu(file_menu,'Label','Exit','Callback',@close_nback_matlab);
  161. % Session Menu:
  162. session_menu = uimenu(handles.figure,'Label','Session');
  163. % N-Back Level:
  164. nBack_num_menu = uimenu(session_menu,'Label','N-Back Level');
  165. h_nBack = zeros(1,20);
  166. for i = 1:20
  167. h_nBack(i) = uimenu(nBack_num_menu,'Label',sprintf('%02g',i),'Callback',{@change_nBack,i});
  168. end; set(h_nBack(nBack_spec),'Checked','on')
  169. % Percent Lures:
  170. percLure_menu = uimenu(session_menu,'Label','Percent Lures/Interference');
  171. h_lures = zeros(1,21);
  172. for i = 1:21
  173. h_lures(i) = uimenu(percLure_menu,'Label',sprintf('%02g',poss_lures(i)),'Callback',{@change_lures,i});
  174. end; set(h_lures(lure_spec),'Checked','on')
  175. % Stimuli Type(s) Menu:
  176. nBack_type_menu = uimenu(session_menu,'Label','Stimuli Type(s)');
  177. if position_on
  178. types_menu(1) = uimenu(nBack_type_menu,'Label','Position','Checked','on','Callback',{@change_types,1});
  179. else types_menu(1) = uimenu(nBack_type_menu,'Label','Position','Checked','off','Callback',{@change_types,1});
  180. end
  181. if sound_on
  182. types_menu(2) = uimenu(nBack_type_menu,'Label','Sound','Checked','on','Callback',{@change_types,2});
  183. else types_menu(2) = uimenu(nBack_type_menu,'Label','Sound','Checked','off','Callback',{@change_types,2});
  184. end
  185. if color_on
  186. types_menu(3) = uimenu(nBack_type_menu,'Label','Color','Checked','on','Callback',{@change_types,3});
  187. else types_menu(3) = uimenu(nBack_type_menu,'Label','Color','Checked','off','Callback',{@change_types,3});
  188. end
  189. trial_num_menu = uimenu(session_menu,'Label','# Trials');
  190. h_trial_num = zeros(1,n_trial_num_opts+1);
  191. h_trial_num(1) = uimenu(trial_num_menu,'Label','20 + N (default)','Callback',{@change_trial_num,1});
  192. for i = 2:n_trial_num_opts+1
  193. h_trial_num(i) = uimenu(trial_num_menu,'Label',sprintf('%1g',trial_num_opts(i-1)),'Callback',{@change_trial_num,i});
  194. end; set(h_trial_num(trial_num_spec),'Checked','on')
  195. trial_length_menu = uimenu(session_menu,'Label','Trial Length');
  196. h_trial_times = zeros(1,n_trial_time_opts);
  197. for i = 1:n_trial_time_opts
  198. h_trial_times(i) = uimenu(trial_length_menu,'Label',sprintf('%1.1f seconds',trial_time_opts(i)),'Callback',{@change_trial_time,i});
  199. end; set(h_trial_times(trial_time_spec),'Checked','on')
  200. advanced_menu = uimenu(session_menu,'Label','Advanced');
  201. num_hits_menu = uimenu(advanced_menu,'Label','# Matches');
  202. sound_matches_menu = uimenu(num_hits_menu,'Label','# Sound Matches');
  203. n_poss = round(.6*nTrials);
  204. h_n_sound_matches = zeros(1,n_poss);
  205. for i = 1:n_poss
  206. h_n_sound_matches(i) = uimenu(sound_matches_menu,'Label',sprintf('%02g',i),'Callback',{@change_sound_matches,i});
  207. end
  208. set(h_n_sound_matches(n_soundHits),'Checked','on')
  209. position_matches_menu = uimenu(num_hits_menu,'Label','# Position Matches');
  210. h_n_position_matches = zeros(1,n_poss);
  211. for i = 1:n_poss
  212. h_n_position_matches(i) = uimenu(position_matches_menu,'Label',sprintf('%02g',i),'Callback',{@change_position_matches,i});
  213. end
  214. set(h_n_position_matches(n_positionHits),'Checked','on')
  215. color_matches_menu = uimenu(num_hits_menu,'Label','# Color Matches');
  216. h_n_color_matches = zeros(1,n_poss);
  217. for i = 1:n_poss
  218. h_n_color_matches(i) = uimenu(color_matches_menu,'Label',sprintf('%02g',i),'Callback',{@change_color_matches,i});
  219. end
  220. set(h_n_color_matches(n_colorHits),'Checked','on')
  221. if completely_random
  222. uimenu(num_hits_menu,'Label',...
  223. 'Completely Random','Checked','on','Callback',@change_completely_random);
  224. else
  225. uimenu(num_hits_menu,'Label',...
  226. 'Completely Random','Checked','off','Callback',@change_completely_random);
  227. end
  228. advance_fallback_menu = uimenu(advanced_menu,'Label','Advance/Fallback');
  229. advance_menu = uimenu(advance_fallback_menu,'Label','Advance Threshold');
  230. advance_thresholds = 1:-0.02:0.80;
  231. h_advances = zeros(1,length(advance_thresholds));
  232. for i = 1:length(advance_thresholds)
  233. h_advances(i) = uimenu(advance_menu,'Label',sprintf('%g%',advance_thresholds(i)),'Callback',{@change_advance_thresh,i});
  234. end
  235. [~,which_advance] = min(abs(advance_thresholds-advance_thresh));
  236. set(h_advances(which_advance),'Checked','on')
  237. set(h_advances(6),'Label',[sprintf('%g%',advance_thresholds(6)),' (default)']);
  238. fallback_menu = uimenu(advance_fallback_menu,'Label','Fallback Threshold');
  239. fallback_thresholds = .80:-.05:.50;
  240. h_fallbacks = zeros(1,length(fallback_thresholds));
  241. for i = 1:length(fallback_thresholds)
  242. h_fallbacks(i) = uimenu(fallback_menu,'Label',sprintf('%g%',fallback_thresholds(i)),'Callback',{@change_fallback_thresh,i});
  243. end
  244. [~,which_fallback] = min(abs(fallback_thresholds-fallback_thresh));
  245. set(h_fallbacks(which_fallback),'Checked','on')
  246. set(h_fallbacks(2),'Label',[sprintf('%g%',fallback_thresholds(2)),' (default)']);
  247. feedback_menu = uimenu(handles.figure,'Label','Feedback');
  248. if show_remaining
  249. uimenu(feedback_menu,'Label','Show Remaining Trials','Checked','on','Callback',@change_show_remaining);
  250. else uimenu(feedback_menu,'Label','Show Remaining Trials','Checked','off','Callback',@change_show_remaining);
  251. end
  252. if realtime_feedback
  253. uimenu(feedback_menu,'Label','Realtime Feedback','Checked','on','Callback',@change_realtime_feedback);
  254. else uimenu(feedback_menu,'Label','Realtime Feedback','Checked','off','Callback',@change_realtime_feedback);
  255. end
  256. if figure_window_feedback
  257. uimenu(feedback_menu,'Label','Results in GUI','Checked','on','Callback',@change_figure_feedback);
  258. else uimenu(feedback_menu,'Label','Results in GUI','Checked','off','Callback',@change_figure_feedback);
  259. end
  260. if command_line_feedback
  261. uimenu(feedback_menu,'Label','Command Line Results','Checked','on','Callback',@change_cmd_feedback);
  262. else uimenu(feedback_menu,'Label','Command Line Results','Checked','off','Callback',@change_cmd_feedback);
  263. end
  264. sound_settings_menu = uimenu(handles.figure,'Label','Sounds');
  265. uimenu(sound_settings_menu,'Label','Volume','Callback',@change_volume);
  266. sound_type_menu = uimenu(sound_settings_menu,'Label','Type');
  267. h_letters = uimenu(sound_type_menu,'Label','Letters');
  268. h_sound_types(3) = uimenu(h_letters,'Label','Female Voice 1','Callback',{@change_sound_types,3});
  269. h_sound_types(4) = uimenu(h_letters,'Label','Female Voice 2','Callback',{@change_sound_types,4});
  270. h_numbers = uimenu(sound_type_menu,'Label','Numbers');
  271. h_sound_types(1) = uimenu(h_numbers,'Label','Female Voice','Callback',{@change_sound_types,1});
  272. h_sound_types(2) = uimenu(h_numbers,'Label','Male Voice','Callback',{@change_sound_types,2});
  273. set(h_sound_types(sound_type),'Checked','on')
  274. reactions_menu = uimenu(sound_settings_menu,'Label','Reactions');
  275. if enable_applause
  276. uimenu(reactions_menu,'Label','Applause','Checked','on','Callback',@change_applause_setting);
  277. else uimenu(reactions_menu,'Label','Applause','Checked','off','Callback',@change_applause_setting);
  278. end
  279. if enable_boos
  280. uimenu(reactions_menu,'Label','Boos','Checked','on','Callback',@change_boos_setting);
  281. else uimenu(reactions_menu,'Label','Boos','Checked','off','Callback',@change_boos_setting);
  282. end
  283. appearance_menu = uimenu(handles.figure,'Label','Appearance');
  284. background_menu = uimenu(appearance_menu,'Label','Background');
  285. if background_color_scheme==1
  286. h_background_menu1 = uimenu(background_menu,'Label','Black','Checked','on','Callback',{@change_background,1});
  287. h_background_menu2 = uimenu(background_menu,'Label','White','Callback',{@change_background,2});
  288. txt_color = ones(1,3);
  289. elseif background_color_scheme==2
  290. h_background_menu1 = uimenu(background_menu,'Label','Black','Callback',{@change_background,1});
  291. h_background_menu2 = uimenu(background_menu,'Label','White','Checked','on','Callback',{@change_background,2});
  292. txt_color = zeros(1,3);
  293. else warning('''background_color'' should be either integer 1 (black) or 2 (white)')
  294. h_background_menu1 = uimenu(background_menu,'Label','Black','Checked','on','Callback',{@change_background,1});
  295. h_background_menu2 = uimenu(background_menu,'Label','White','Callback',{@change_background,2});
  296. txt_color = ones(1,3);
  297. end
  298. %% Add other display elements (gridlines, rectangle, etc.):
  299. % Gridlines:
  300. minMeasure = .05; maxMeasure = .95; one_third = .3497; two_thirds = .6503;
  301. h_grid_lines = zeros(1,4);
  302. h_grid_lines(1) = annotation('line','X',[one_third,one_third],'Y',[minMeasure,maxMeasure],'Color',txt_color,'LineWidth',1);
  303. h_grid_lines(2) = annotation('line','X',[two_thirds,two_thirds],'Y',[minMeasure,maxMeasure],'Color',txt_color,'LineWidth',1);
  304. h_grid_lines(3) = annotation('line','X',[minMeasure,maxMeasure],'Y',[two_thirds,two_thirds],'Color',txt_color,'LineWidth',1);
  305. h_grid_lines(4) = annotation('line','X',[minMeasure,maxMeasure],'Y',[one_third,one_third],'Color',txt_color,'LineWidth',1);
  306. % Initialize Match Button Text and Rectangle:
  307. handles.h_rect = rectangle('Parent',handles.axes1,'Position',...
  308. [.05,.05,square_width,square_width],'Curvature',[.2,.2],'FaceColor',...
  309. 'blue','Visible','off');
  310. h_txt_begin = annotation('textbox',[.37,.48,.26,.04],'String',...
  311. ['Press ',startSessionKey,sprintf(' \nto begin')],...
  312. 'Color',txt_color,'HorizontalAlignment','center','VerticalAlignment',...
  313. 'middle','FontSize',14,'FontWeight','bold','EdgeColor','none');
  314. h_txt_pos = text('position',[.04,.01],'String',['Position Match: ''',positionMatchKey,''''],...
  315. 'Color',txt_color,'HorizontalAlignment','center','VerticalAlignment','top',...
  316. 'FontSize',13,'FontWeight','normal','Visible','off','EdgeColor','none');
  317. h_txt_color = text('position',[.35,.01],'String',['Color Match: ''',colorMatchKey,''''],...
  318. 'Color',txt_color,'HorizontalAlignment','center','VerticalAlignment','top',...
  319. 'FontSize',13,'FontWeight','normal','Visible','off','EdgeColor','none');
  320. h_txt_sound = text('position',[.658,.01],'String',['Sound Match: ''',soundMatchKey,''''],...
  321. 'Color',txt_color,'HorizontalAlignment','center','VerticalAlignment','top',...
  322. 'FontSize',13,'FontWeight','normal','Visible','off','EdgeColor','none');
  323. if position_on; set(h_txt_pos,'Visible','on'); end
  324. if color_on; set(h_txt_color,'Visible','on'); end
  325. if sound_on; set(h_txt_sound,'Visible','on'); end
  326. % Declare Figure Callbacks:
  327. set(handles.figure,'WindowKeyPressFcn',@keypress_callback);
  328. set(handles.figure,'CloseRequestFcn',@close_nback_matlab)
  329. % Resize Figure function:
  330. canResize = true;
  331. resizeScreen;
  332. %% Run Function:
  333. function run_session
  334. extraKeys = cell(nTrials,1); curr_running = 1;
  335. % Get Trial Times:
  336. targetTimes = linspace(1,trial_time * (nTrials-1) + 1, nTrials);
  337. pause(1 - (toc(timerVal)-0));
  338. disableMenus; % disallow users from toggling settings during play
  339. if ishandle(h_pos_feedback1)
  340. delete(h_pos_feedback1); delete(h_pos_feedback2)
  341. end
  342. if ishandle(h_sound_feedback1)
  343. delete(h_sound_feedback1); delete(h_sound_feedback2)
  344. end
  345. if ishandle(h_color_feedback1)
  346. delete(h_color_feedback1); delete(h_color_feedback2)
  347. end
  348. % START TRIALS:
  349. ix = 0; times = zeros(nTrials,1);
  350. while ix<nTrials && ~stop_running
  351. ix = ix + 1; times(ix) = toc(timerVal);
  352. if realtime_feedback
  353. set(h_txt_pos,'Color',txt_color)
  354. set(h_txt_sound,'Color',txt_color)
  355. set(h_txt_color,'Color',txt_color)
  356. end
  357. if show_remaining
  358. set(h_title,'String',num2str(nTrials-ix))
  359. end
  360. tic
  361. if position_on
  362. set(handles.h_rect,'Position',positions(position_mem(ix),:),'Visible','on')
  363. end
  364. if color_on
  365. set(handles.h_rect,'FaceColor',colors{color_mem(ix)})
  366. end
  367. if sound_on
  368. sound(audio_dat(sound_type).sound{sound_mem(ix)}*volume_factor,audio_dat(sound_type).Fs{sound_mem(ix)})
  369. end
  370. if abs(toc(timerVal)-targetTimes(ix)) < .5
  371. pause(trial_time - (toc(timerVal)-targetTimes(ix)));
  372. else
  373. pause(trial_time - sign(toc(timerVal)-targetTimes(ix))*.5);
  374. end
  375. end
  376. curr_running = 0; set(handles.h_rect,'Visible','off')
  377. set(h_txt_begin,'Visible','on'); set(h_title,'String','')
  378. set(h_txt_pos,'Color',txt_color); set(h_txt_sound,'Color',txt_color)
  379. set(h_txt_color,'Color',txt_color)
  380. % Calculate Score:
  381. if ~stop_running
  382. % Position Stats:
  383. position_hits = sum((position_match_vec+user_position_match)==2);
  384. position_false = sum((position_match_vec-user_position_match)==-1);
  385. position_lure_errors = sum((position_lures_vec+user_position_match)==2)/sum(position_lures_vec);
  386. H_pos = position_hits / n_positionHits; F_pos = position_false / (nTrials - n_positionHits);
  387. pos_score = .5 + sign(H_pos - F_pos) * ((H_pos - F_pos)^2 + abs(H_pos - F_pos)) / (4 * max(H_pos, F_pos) - 4 * H_pos * F_pos);
  388. position_stats = cell(5,2);
  389. position_stats(1:5,1) = {'Position','A'':','Hits:','False-Alarms:','Lure Errors:'};
  390. position_stats{2,2} = num2str(round(pos_score,2));
  391. position_stats{3,2} = [num2str(round(100*H_pos)),'%'];
  392. position_stats{4,2} = [num2str(round(100*F_pos)),'%'];
  393. position_stats{5,2} = [num2str(round(100*position_lure_errors)),'%'];
  394. % Sound Stats:
  395. sound_hits = sum((sound_match_vec+user_sound_match)==2);
  396. sound_false = sum((sound_match_vec-user_sound_match)==-1);
  397. sound_lure_errors = sum((sound_lures_vec+user_sound_match)==2)/sum(sound_lures_vec);
  398. H_sound = sound_hits / n_soundHits; F_sound = sound_false / (nTrials - n_soundHits);
  399. sound_score = .5 + sign(H_sound - F_sound) * ((H_sound - F_sound)^2 + abs(H_sound - F_sound)) / (4 * max(H_sound, F_sound) - 4 * H_sound * F_sound);
  400. sound_stats = cell(5,2);
  401. sound_stats(1:5,1) = {'Sound','A'':','Hits:','False-Alarms:','Lure Errors:'};
  402. sound_stats{2,2} = num2str(round(sound_score,2));
  403. sound_stats{3,2} = [num2str(round(100*H_sound)),'%'];
  404. sound_stats{4,2} = [num2str(round(100*F_sound)),'%'];
  405. sound_stats{5,2} = [num2str(round(100*sound_lure_errors)),'%'];
  406. % Color Stats:
  407. color_hits = sum((color_match_vec+user_color_match)==2);
  408. color_false = sum((color_match_vec-user_color_match)==-1);
  409. color_lure_errors = sum((color_lures_vec+user_color_match)==2)/sum(color_lures_vec);
  410. H_color = color_hits / n_colorHits; F_color = color_false / (nTrials - n_colorHits);
  411. color_score = .5 + sign(H_color - F_color) * ((H_color - F_color)^2 + abs(H_color - F_color)) / (4 * max(H_color, F_color) - 4 * H_color * F_color);
  412. color_stats = cell(5,2);
  413. color_stats(1:5,1) = {'Color','A'':','Hits:','False-Alarms:','Lure Errors:'};
  414. color_stats{2,2} = num2str(round(color_score,2));
  415. color_stats{3,2} = [num2str(round(100*H_color,1)),'%'];
  416. color_stats{4,2} = [num2str(round(100*F_color,1)),'%'];
  417. color_stats{5,2} = [num2str(round(100*color_lure_errors)),'%'];
  418. % Command-line Feedback:
  419. if command_line_feedback
  420. if position_on && sound_on && color_on
  421. session_stats = [position_stats,sound_stats,color_stats];
  422. elseif position_on && sound_on && ~color_on
  423. session_stats = [position_stats,sound_stats];
  424. elseif position_on && ~sound_on && ~color_on
  425. session_stats = position_stats;
  426. elseif ~position_on && sound_on && ~color_on
  427. session_stats = sound_stats;
  428. elseif ~position_on && sound_on && color_on
  429. session_stats = [sound_stats,color_stat];
  430. elseif position_on && ~sound_on && color_on
  431. session_stats = [position_stats,color_stats];
  432. elseif ~position_on && ~sound_on && color_on
  433. session_stats = color_stats;
  434. else return;
  435. end
  436. disp(session_stats)
  437. end
  438. % Write data:
  439. user_position_match = user_position_match==1;
  440. user_sound_match = user_sound_match==1;
  441. user_color_match = user_color_match==1;
  442. writeData(pos_score, position_lure_errors, H_pos, F_pos, ...
  443. sound_score, sound_lure_errors, H_sound, F_sound, ...
  444. color_score, color_lure_errors, H_color, F_color, ...
  445. (1:nTrials)', times, position_match_vec', position_lures_vec', ...
  446. user_position_match', position_mem', sound_match_vec', sound_lures_vec', ...
  447. user_sound_match', sound_mem', color_match_vec', color_lures_vec', ...
  448. user_color_match', color_mem',extraKeys)
  449. % Check if Advance:
  450. advance_vec = [(pos_score >= advance_thresh),(sound_score >= advance_thresh),(color_score >= advance_thresh)];
  451. fallback_vec = [(pos_score <= fallback_thresh),(sound_score <= fallback_thresh),(color_score <= fallback_thresh)];
  452. if all(advance_vec(logical([position_on,sound_on,color_on])))
  453. if command_line_feedback
  454. disp('Congratulations, you have been advanced to the next nBack level!')
  455. end
  456. nback = nback+1;
  457. if get(h_trial_num(1),'checked')
  458. nTrials = nTrials + 1;
  459. end
  460. set(handles.figure,'name',['nback_matlab: (',num2str(nback),'-Back)'])
  461. set(h_nBack(nback-1),'Checked','off'); set(h_nBack(nback),'Checked','on');
  462. if enable_applause; sound(applause_dat,Fs_applause); end
  463. if figure_window_feedback
  464. figure_feedback_color = 'g';
  465. end
  466. elseif any(fallback_vec(logical([position_on,sound_on,color_on])))
  467. if nback>1;
  468. nback = nback-1;
  469. if get(h_trial_num(1),'checked')
  470. nTrials = nTrials - 1;
  471. end
  472. if command_line_feedback
  473. disp('Good effort - nBack level lowered.')
  474. end
  475. set(handles.figure,'name',['nback_matlab: (',num2str(nback),'-Back)'])
  476. set(h_nBack(nback+1),'Checked','off'); set(h_nBack(nback),'Checked','on');
  477. else if command_line_feedback; disp('Good effort.'); end
  478. end
  479. if enable_boos; sound(boo_dat,Fs_boo); end
  480. if figure_window_feedback
  481. figure_feedback_color = 'r';
  482. end
  483. else
  484. if command_line_feedback; disp('Good score - keep training!'); end
  485. if figure_window_feedback
  486. if background_color_scheme==1
  487. figure_feedback_color = 'w';
  488. elseif background_color_scheme==2
  489. figure_feedback_color = 'k';
  490. end
  491. end
  492. end
  493. if figure_window_feedback
  494. if position_on
  495. main_char = char('Position','A'':','Hits:','False-Alarms:','Lure Errors:');
  496. char2 = char('',position_stats{2,2},position_stats{3,2},position_stats{4,2},position_stats{5,2});
  497. h_pos_feedback1 = text('String',main_char,'FontName','Helvetica','FontSize',13,...
  498. 'Position',h_pos_feedback1_pos,'Color',figure_feedback_color,...
  499. 'FontWeight','bold','EdgeColor','none','HorizontalAlignment','left');
  500. h_pos_feedback2 = text('String',char2,'FontName','Helvetica','FontSize',13,...
  501. 'Position',h_pos_feedback2_pos,'Color',figure_feedback_color,'HorizontalAlignment','right',...
  502. 'FontWeight','normal','EdgeColor','none');
  503. end
  504. if sound_on
  505. main_char = char('Sound','A'':','Hits:','False-Alarms:','Lure Errors:');
  506. char2 = char('',sound_stats{2,2},sound_stats{3,2},sound_stats{4,2},sound_stats{5,2});
  507. h_sound_feedback1 = text('String',main_char,'FontName','Helvetica','FontSize',13,...
  508. 'Position',h_sound_feedback1_pos,'Color',figure_feedback_color,...
  509. 'FontWeight','bold','EdgeColor','none');
  510. h_sound_feedback2 = text('String',char2,'FontName','Helvetica','FontSize',13,...
  511. 'Position',h_sound_feedback2_pos,'Color',figure_feedback_color,'HorizontalAlignment','right',...
  512. 'FontWeight','normal','EdgeColor','none');
  513. end
  514. if color_on
  515. main_char = char('Color','A'':','Hits:','False-Alarms:','Lure Errors:');
  516. char2 = char('',color_stats{2,2},color_stats{3,2},color_stats{4,2},color_stats{5,2});
  517. h_color_feedback1 = text('String',main_char,'FontName','Helvetica','FontSize',13,...
  518. 'Position',h_color_feedback1_pos,'Color',figure_feedback_color,...
  519. 'FontWeight','bold','EdgeColor','none');
  520. h_color_feedback2 = text('String',char2,'FontName','Helvetica','FontSize',13,...
  521. 'Position',h_color_feedback2_pos,'Color',figure_feedback_color,'HorizontalAlignment','right',...
  522. 'FontWeight','normal','EdgeColor','none');
  523. end
  524. end
  525. else stop_running = 0;
  526. end
  527. enableMenus;
  528. end
  529. % Key Press Callback:
  530. function keypress_callback(~,which_key,~)
  531. switch which_key.Key
  532. case startSessionKey
  533. if ~curr_running
  534. timerVal = tic;
  535. curr_running = 1; set(h_txt_begin,'Visible','off')
  536. if completely_random
  537. position_mem = randi(9,[1,nTrials]);
  538. color_mem = randi(7,[1,nTrials]);
  539. sound_mem = randi(audio_dat(sound_type).nSound,[1,nTrials]);
  540. else
  541. % Generate Idx:
  542. wasSuccess = false;
  543. while ~wasSuccess
  544. [position_mem, position_match_vec, position_lures_vec, wasSuccess] = generateIdx(nTrials, nback, n_positionHits, percLure, 9);
  545. end; wasSuccess = false;
  546. while ~wasSuccess
  547. [sound_mem, sound_match_vec, sound_lures_vec, wasSuccess] = generateIdx(nTrials, nback, n_soundHits, percLure, audio_dat(sound_type).nSound);
  548. end; wasSuccess = false;
  549. while ~wasSuccess
  550. [color_mem, color_match_vec, color_lures_vec, wasSuccess] = generateIdx(nTrials, nback, n_colorHits, percLure, 7);
  551. end
  552. user_position_match = nan(1, nTrials);
  553. user_sound_match = nan(1, nTrials);
  554. user_color_match = nan(1, nTrials);
  555. end
  556. run_session
  557. end
  558. case positionMatchKey
  559. if curr_running
  560. user_position_match(ix) = 1;
  561. if realtime_feedback
  562. if position_match_vec(ix)
  563. set(h_txt_pos,'Color',[0,1,0])
  564. else
  565. set(h_txt_pos,'Color',[1,0,0])
  566. end
  567. end
  568. end
  569. case colorMatchKey
  570. if curr_running
  571. user_color_match(ix) = 1;
  572. if realtime_feedback
  573. if color_match_vec(ix)
  574. set(h_txt_color,'Color',[0,1,0])
  575. else
  576. set(h_txt_color,'Color',[1,0,0])
  577. end
  578. end
  579. end
  580. case soundMatchKey
  581. if curr_running
  582. user_sound_match(ix) = 1;
  583. if realtime_feedback
  584. if sound_match_vec(ix)
  585. set(h_txt_sound,'Color',[0,1,0])
  586. else
  587. set(h_txt_sound,'Color',[1,0,0])
  588. end
  589. end
  590. end
  591. case stopRunningKey
  592. stop_running = 1;
  593. otherwise
  594. if curr_running
  595. extraKeys{ix} = [extraKeys{ix}, ',', which_key.Key];
  596. end
  597. end
  598. end
  599. % Change nBack Types:
  600. function change_types(~,~,which_type)
  601. switch which_type
  602. case 1,
  603. switch position_on
  604. case 1, set(types_menu(1),'Checked','off'); position_on = false;
  605. set(h_txt_pos,'Visible','off')
  606. case 0, set(types_menu(1),'Checked','on'); position_on = true;
  607. set(h_txt_pos,'Visible','on')
  608. end
  609. case 2,
  610. switch sound_on
  611. case 1, set(types_menu(2),'Checked','off'); sound_on = false;
  612. set(h_txt_sound,'Visible','off')
  613. case 0, set(types_menu(2),'Checked','on'); sound_on = true;
  614. set(h_txt_sound,'Visible','on')
  615. end
  616. case 3,
  617. switch color_on
  618. case 1, set(types_menu(3),'Checked','off'); color_on = false;
  619. set(h_txt_color,'Visible','off')
  620. set(handles.h_rect,'FaceColor','b')
  621. case 0, set(types_menu(3),'Checked','on'); color_on = true;
  622. set(h_txt_color,'Visible','on')
  623. end
  624. end
  625. end
  626. % Change nBack Level:
  627. function change_nBack(~,~,nBack_spec)
  628. nback = nBack_spec;
  629. for ix1 = 1:20
  630. set(h_nBack(ix1),'Checked','off');
  631. end
  632. set(h_nBack(nback),'Checked','on')
  633. set(handles.figure,'name',['nback_matlab: (',num2str(nback),'-Back)'])
  634. end
  635. function change_lures(~,~,lure_spec)
  636. percLure = poss_lures(lure_spec);
  637. for ix1 = 1:21
  638. set(h_lures(ix1),'Checked','off');
  639. end
  640. set(h_lures(lure_spec),'Checked','on')
  641. end
  642. % Change N Position Matches:
  643. function change_position_matches(~,~,change_n)
  644. n_positionHits = change_n;
  645. for ixx = 1:n_poss
  646. set(h_n_position_matches(ixx),'Checked','off');
  647. end
  648. set(h_n_position_matches(n_positionHits),'Checked','on')
  649. end
  650. % Change N Sound Matches:
  651. function change_sound_matches(~,~,change_n)
  652. n_soundHits = change_n;
  653. for ixx = 1:n_poss
  654. set(h_n_sound_matches(ixx),'Checked','off');
  655. end
  656. set(h_n_sound_matches(n_soundHits),'Checked','on')
  657. end
  658. % Change N Color Matches:
  659. function change_color_matches(~,~,change_n)
  660. n_colorHits = change_n;
  661. for ixx = 1:n_poss
  662. set(h_n_color_matches(ixx),'Checked','off');
  663. end
  664. set(h_n_color_matches(n_colorHits),'Checked','on')
  665. end
  666. % Change Completely Random:
  667. function change_completely_random(hObject,~,~)
  668. switch completely_random
  669. case 1, set(hObject,'Checked','off'); completely_random = 0;
  670. case 0, set(hObject,'Checked','on'); completely_random = 1;
  671. end
  672. end
  673. % Change Advance Threshold:
  674. function change_advance_thresh(~,~,advance_spec)
  675. advance_thresh = advance_thresholds(advance_spec);
  676. for ixx = 1:length(advance_thresholds)
  677. set(h_advances(ixx),'Checked','off')
  678. end
  679. set(h_advances(advance_spec),'Checked','on')
  680. end
  681. % Change Advance Threshold:
  682. function change_fallback_thresh(~,~,fallback_spec)
  683. fallback_thresh = fallback_thresholds(fallback_spec);
  684. for ixx = 1:length(fallback_thresholds)
  685. set(h_fallbacks(ixx),'Checked','off')
  686. end
  687. set(h_fallbacks(fallback_spec),'Checked','on')
  688. end
  689. % Change # Trials:
  690. function change_trial_num(~,~,trial_num_spec)
  691. if (trial_num_spec > 1)
  692. nTrials = trial_num_opts(trial_num_spec-1);
  693. else
  694. nTrials = nback + 20;
  695. end
  696. for ixx = 1:n_trial_num_opts+1
  697. set(h_trial_num(ixx),'Checked','off')
  698. end
  699. set(h_trial_num(trial_num_spec),'Checked','on')
  700. end
  701. % Change Trial Time:
  702. function change_trial_time(~,~,trial_time_spec)
  703. trial_time = trial_time_opts(trial_time_spec);
  704. for ixx = 1:n_trial_time_opts
  705. set(h_trial_times(ixx),'Checked','off')
  706. end
  707. set(h_trial_times(trial_time_spec),'Checked','on')
  708. end
  709. % Change Show Remaining Trials:
  710. function change_show_remaining(hObject,~,~)
  711. switch show_remaining
  712. case 1, set(hObject,'Checked','off'); show_remaining = 0;
  713. case 0, set(hObject,'Checked','on'); show_remaining = 1;
  714. end
  715. end
  716. function change_realtime_feedback(hObject,~,~)
  717. switch realtime_feedback
  718. case 1, set(hObject,'Checked','off'); realtime_feedback = 0;
  719. case 0, set(hObject,'Checked','on'); realtime_feedback = 1;
  720. end
  721. end
  722. function change_figure_feedback(hObject,~,~)
  723. switch figure_window_feedback
  724. case 1, set(hObject,'Checked','off'); figure_window_feedback = 0;
  725. case 0, set(hObject,'Checked','on'); figure_window_feedback = 1;
  726. end
  727. end
  728. function change_cmd_feedback(hObject,~,~)
  729. switch command_line_feedback
  730. case 1, set(hObject,'Checked','off'); command_line_feedback = 0;
  731. case 0, set(hObject,'Checked','on'); command_line_feedback = 1;
  732. end
  733. end
  734. % Change Sound Settings:
  735. function change_volume(~,~,~)
  736. if ~ishandle(h_volume)
  737. h_volume = figure('menubar','none','color',background_color,'numbertitle',...
  738. 'off','name','Adjust Volume','units','norm','Position',[.4729,.8646,.2174,.0352]);
  739. uicontrol(h_volume,'Style','slider','units','normalized','Position',...
  740. [0,.2,1,.6],'Min',0,'Max',1.2,'Value',volume_factor,'Callback',@change_volume_slider);
  741. else figure(h_volume)
  742. end
  743. end
  744. function change_volume_slider(hObject,~,~)
  745. volume_factor = hObject.Value;
  746. end
  747. function change_sound_types(~,~,which_type)
  748. for ixx = 1:4; set(h_sound_types(ixx),'Checked','off'); end
  749. set(h_sound_types(which_type),'Checked','on')
  750. sound_type = which_type;
  751. end
  752. function change_applause_setting(hObject,~,~)
  753. switch enable_applause
  754. case 1, set(hObject,'Checked','off'); enable_applause = 0;
  755. case 0, set(hObject,'Checked','on'); enable_applause = 1;
  756. end
  757. end
  758. function change_boos_setting(hObject,~,~)
  759. switch enable_boos
  760. case 1, set(hObject,'Checked','off'); enable_boos = 0;
  761. case 0, set(hObject,'Checked','on'); enable_boos = 1;
  762. end
  763. end
  764. % Appearance:
  765. function change_background(~,~,which_background)
  766. switch which_background
  767. case 1,
  768. if strcmp(h_background_menu1.Checked,'on')
  769. background_color_scheme = 2;
  770. colors = {'y','m','c','r','g','b','k'};
  771. set(h_background_menu1,'Checked','off')
  772. set(h_background_menu2,'Checked','on')
  773. elseif strcmp(h_background_menu1.Checked,'off')
  774. background_color_scheme = 1;
  775. colors = {'y','m','c','r','g','b','w'};
  776. set(h_background_menu1,'Checked','on')
  777. set(h_background_menu2,'Checked','off')
  778. end
  779. case 2,
  780. if strcmp(h_background_menu2.Checked,'on')
  781. background_color_scheme = 1;
  782. colors = {'y','m','c','r','g','b','w'};
  783. set(h_background_menu1,'Checked','on')
  784. set(h_background_menu2,'Checked','off')
  785. elseif strcmp(h_background_menu2.Checked,'off')
  786. background_color_scheme = 2;
  787. colors = {'y','m','c','r','g','b','k'};
  788. set(h_background_menu1,'Checked','off')
  789. set(h_background_menu2,'Checked','on')
  790. end
  791. end
  792. if background_color_scheme==1
  793. txt_color = ones(1,3);
  794. set(handles.figure,'Color',zeros(1,3))
  795. elseif background_color_scheme==2
  796. txt_color = zeros(1,3);
  797. set(handles.figure,'Color',ones(1,3))
  798. end
  799. for ixxx = 1:4
  800. set(h_grid_lines(ixxx),'Color',txt_color)
  801. end
  802. set(h_title,'Color',txt_color); set(h_txt_begin,'Color',txt_color)
  803. set(h_txt_pos,'Color',txt_color); set(h_txt_color,'Color',txt_color)
  804. set(h_txt_sound,'Color',txt_color)
  805. end
  806. % Write Current Trial Data:
  807. function writeData(pos_score, pos_lure_errors, H_pos, F_pos, ...
  808. sound_score, sound_lure_errors, H_sound, F_sound, ...
  809. color_score, color_lure_errors, H_color, F_color, trials, times, ...
  810. position_matches, pos_lures, position_user, position_stimuli,...
  811. sound_matches, sound_lures, sound_user, sound_stimuli, ...
  812. color_matches, color_lures, color_user, color_stimuli, extraKeys)
  813. if ~isdir(fullfile(script_path,'UserData')); mkdir(fullfile(script_path,'UserData')); end
  814. dataPath = fullfile(script_path,'UserData','user_data.mat');
  815. if exist(dataPath,'file')
  816. load(dataPath);
  817. summaryStats2 = table({char(datetime)}, nback, pos_score, ...
  818. pos_lure_errors, H_pos, F_pos, sound_score, ...
  819. sound_lure_errors, H_sound, F_sound, color_score, ...
  820. color_lure_errors, H_color, F_color,'VariableNames',...
  821. {'date_time','nback_level',...
  822. 'A_pos','lure_errors_pos','hits_pos','false_alarms_pos',...
  823. 'A_sound','lure_errors_sound','hits_sound','false_alarms_sound',...
  824. 'A_color','lure_errors_color','hits_color','false_alarms_color'});
  825. summaryStats = [summaryStats; summaryStats2]; %#ok
  826. trialData{length(trialData)+1} = table(trials, times, ...
  827. position_matches,pos_lures, position_user, position_stimuli, ...
  828. sound_matches, sound_lures, sound_user, sound_stimuli, ...
  829. color_matches, color_lures, color_user, color_stimuli, extraKeys,...
  830. 'VariableNames',{'trial_num','seconds_from_start',...
  831. 'position_matches','position_lures','user_position_matches','position_stimuli',...
  832. 'sound_matches','sound_lures','user_sound_matches','sound_stimuli',...
  833. 'color_matches','color_lures','user_color_matches','color_stimuli','extra_keys'}); %#ok
  834. else
  835. summaryStats = table({char(datetime)}, nback, pos_score, ...
  836. pos_lure_errors, H_pos, F_pos, sound_score, ...
  837. sound_lure_errors, H_sound, F_sound, color_score, ...
  838. color_lure_errors, H_color, F_color,'VariableNames',...
  839. {'date_time','nback_level',...
  840. 'A_pos','lure_errors_pos','hits_pos','false_alarms_pos',...
  841. 'A_sound','lure_errors_sound','hits_sound','false_alarms_sound',...
  842. 'A_color','lure_errors_color','hits_color','false_alarms_color'}); %#ok
  843. trialData{1} = table(trials, times, ...
  844. position_matches,pos_lures, position_user, position_stimuli, ...
  845. sound_matches, sound_lures, sound_user, sound_stimuli, ...
  846. color_matches, color_lures, color_user, color_stimuli, extraKeys,...
  847. 'VariableNames',{'trial_num','seconds_from_start',...
  848. 'position_matches','position_lures','user_position_matches','position_stimuli',...
  849. 'sound_matches','sound_lures','user_sound_matches','sound_stimuli',...
  850. 'color_matches','color_lures','user_color_matches','color_stimuli','extra_keys'}); %#ok
  851. end
  852. save(dataPath,'summaryStats','trialData')
  853. end
  854. % Get Default Settings:
  855. function get_defaults
  856. nback = 2; % nBack level
  857. percLure = .2; % proportion of non-match trials to become lures
  858. nTrials = nback + 20;
  859. trial_time = 2.4; % seconds
  860. sound_on = true; % sound-type nBack: 1 (on), 0 (off)
  861. position_on = true; % position-type nBack: 1 (on), 0 (off)
  862. color_on = false; % color-type nBack: 1 (on), 0 (off)
  863. completely_random = 0; % off (0), on (1) (if on, next 3 settings are irrelevant)
  864. n_positionHits = 4; % control # of matches for position
  865. n_soundHits = 4; % control # of matches for sound
  866. n_colorHits = 4; % control # of matches for color
  867. advance_thresh = .9; % A' default
  868. fallback_thresh = .75; % A' default
  869. volume_factor = 1; % Default: 1 (no change); >1 = increase volume; <1 = decrease volume
  870. sound_type = 3; % use Numbers-Female (1), Numbers-Male (2), Letters-Female1 (3), or Letters-Female2 (4)
  871. enable_applause = 1; % 1 (on) or 0 (off)
  872. enable_boos = 0; % 1 (on) or 0 (off); comical boos if fail to advance to next nBack
  873. realtime_feedback = 1; % 1 (on) or 0 (off); highlight labels red/green based on accuracy
  874. figure_window_feedback = 1; % 1 (on) or 0 (off); display hits, misses, false alarms in figure window upon completion
  875. command_line_feedback = 1; % 1 (on) or 0 (off); display hits, misses, false alarms in command window upon completion
  876. show_remaining = 1; % 1 (on) or 0 (off); show remaining trials as title
  877. background_color_scheme = 1; % black (1), white (2)
  878. end
  879. % Get Custom Settings:
  880. function success = get_settings
  881. if ~isdir(fullfile(script_path,'UserData')); mkdir(fullfile(script_path,'UserData')); end
  882. listing = dir(fullfile(script_path,'UserData','nback_matlab_settings.txt'));
  883. if isempty(listing);
  884. success = 0;
  885. get_defaults
  886. return;
  887. end
  888. nback_matlab_settings = importdata(fullfile(script_path,'UserData',listing(1).name));
  889. for ix1 = 1:length(customizable); eval(nback_matlab_settings{ix1}); end
  890. success = 1;
  891. end
  892. % Write Custom Settings:
  893. function write_settings
  894. % Change Settings Data:
  895. nback_matlab_settings = cell(length(customizable),1);
  896. for ix1 = 1:length(customizable)
  897. setting1 = eval(customizable{ix1});
  898. nback_matlab_settings{ix1} = [customizable{ix1},'=',num2str(setting1),';'];
  899. end
  900. nback_matlab_settings = char(nback_matlab_settings);
  901. % Write Text File:
  902. if ~isdir(fullfile(script_path,'UserData')); mkdir(fullfile(script_path,'UserData')); end
  903. fileID = fopen(fullfile(script_path,'UserData','nback_matlab_settings.txt'),'w');
  904. for ix1 = 1:length(customizable)
  905. fprintf(fileID,'%s\r\n',nback_matlab_settings(ix1,:));
  906. end
  907. fclose(fileID);
  908. end
  909. % Close Requests:
  910. function close_nback_matlab(varargin)
  911. % Save Custom Settings:
  912. write_settings
  913. % Close:
  914. delete(handles.figure)
  915. end
  916. function enableMenus
  917. set(handles.figure,'resize','on')
  918. set(file_menu,'Enable','on'); set(session_menu,'Enable','on')
  919. set(nBack_type_menu,'Enable','on'); set(feedback_menu,'Enable','on')
  920. set(sound_settings_menu,'Enable','on'); set(appearance_menu,'Enable','on')
  921. end
  922. function disableMenus
  923. clear sound % stop any sound if playing
  924. set(handles.figure,'resize','off')
  925. set(file_menu,'Enable','off')
  926. set(session_menu,'Enable','off')
  927. set(nBack_type_menu,'Enable','off')
  928. set(feedback_menu,'Enable','off')
  929. set(sound_settings_menu,'Enable','off')
  930. set(appearance_menu,'Enable','off')
  931. end
  932. function resizeScreen(varargin)
  933. if canResize
  934. % Determine which dimension needs shrinking to become square:
  935. for ixx = 1:4; set(h_grid_lines(ixx),'units','norm'); end
  936. set(h_grid_lines(1),'Y',[minMeasure,maxMeasure])
  937. set(h_grid_lines(2),'Y',[minMeasure,maxMeasure])
  938. set(h_grid_lines(3),'X',[minMeasure,maxMeasure])
  939. set(h_grid_lines(4),'X',[minMeasure,maxMeasure])
  940. % Change units to points and shrink width or height to match:
  941. for ixx = 1:4; set(h_grid_lines(ixx),'units','points'); end
  942. ySpec = get(h_grid_lines(1),'Y'); xSpec = get(h_grid_lines(3),'X');
  943. height = diff(ySpec); width = diff(xSpec);
  944. axesRatio = height/width;
  945. diffPixels = abs(height - width);
  946. if height > width % shrink height to match
  947. set(h_grid_lines(1),'Y',[ySpec(1) + diffPixels/2, ySpec(2) - diffPixels/2])
  948. set(h_grid_lines(2),'Y',[ySpec(1) + diffPixels/2, ySpec(2) - diffPixels/2])
  949. elseif height < width % shrink width to match
  950. set(h_grid_lines(3),'X',[xSpec(1) + diffPixels/2, xSpec(2) - diffPixels/2])
  951. set(h_grid_lines(4),'X',[xSpec(1) + diffPixels/2, xSpec(2) - diffPixels/2])
  952. end
  953. % Change units back to normalized, and adjust grid spacing:
  954. for ixx = 1:4; set(h_grid_lines(ixx),'units','norm'); end
  955. xSpec = get(h_grid_lines(3),'X'); ySpec = get(h_grid_lines(1),'Y');
  956. oneThirdX = (diff(xSpec)/3) + xSpec(1);
  957. twoThirdsX = xSpec(2) - (diff(xSpec)/3);
  958. oneThirdY = (diff(ySpec)/3) + ySpec(1);
  959. twoThirdsY = ySpec(2) - (diff(ySpec)/3);
  960. set(h_grid_lines(1),'X',[oneThirdX, oneThirdX])
  961. set(h_grid_lines(2),'X',[twoThirdsX, twoThirdsX])
  962. set(h_grid_lines(3),'Y',[oneThirdY, oneThirdY])
  963. set(h_grid_lines(4),'Y',[twoThirdsY, twoThirdsY])
  964. % Square Positions:
  965. ylim(handles.axes1,[0,axesRatio]); % adjust Y-axes limits based on its ratio w/ X
  966. square_width = .95*(twoThirdsX-oneThirdX);
  967. useGapX = ((twoThirdsX-oneThirdX)-square_width)/2;
  968. oneThirdY = oneThirdY*axesRatio; twoThirdsY = twoThirdsY*axesRatio;
  969. useGapY = ((twoThirdsY-oneThirdY)-square_width)/2;
  970. positions = repmat([.05,.05,square_width,square_width],9,1);
  971. positions(1,1:2) = [xSpec(1)+useGapX,twoThirdsY+useGapY];
  972. positions(2,1:2) = [oneThirdX+useGapX,twoThirdsY+useGapY];
  973. positions(3,1:2) = [twoThirdsX+useGapX,twoThirdsY+useGapY];
  974. positions(4,1:2) = [xSpec(1)+useGapX,oneThirdY+useGapY];
  975. positions(5,1:2) = [oneThirdX+useGapX,oneThirdY+useGapY];
  976. positions(6,1:2) = [twoThirdsX+useGapX,oneThirdY+useGapY];
  977. positions(7,1:2) = [xSpec(1)+useGapX,ySpec(1)*axesRatio+useGapY];
  978. positions(8,1:2) = [oneThirdX+useGapX,ySpec(1)*axesRatio+useGapY];
  979. positions(9,1:2) = [twoThirdsX+useGapX,ySpec(1)*axesRatio+useGapY];
  980. % Adjust match button text positions:
  981. set(h_txt_pos,'position',[median([xSpec(1),oneThirdX]),ySpec(1)*axesRatio])
  982. set(h_txt_color,'position',[median([oneThirdX, twoThirdsX]),ySpec(1)*axesRatio])
  983. set(h_txt_sound,'position',[median([twoThirdsX,xSpec(2)]),ySpec(1)*axesRatio])
  984. % Adjust Feedback Positions:
  985. h_pos_feedback1_pos = [xSpec(1)+.005*xSpec(1), median([ySpec(1)*axesRatio, oneThirdY])];
  986. h_pos_feedback2_pos = [oneThirdX-.005*oneThirdX, median([ySpec(1)*axesRatio, oneThirdY])];
  987. h_color_feedback1_pos = [oneThirdX+.005*oneThirdX, median([ySpec(1)*axesRatio, oneThirdY])];
  988. h_color_feedback2_pos = [twoThirdsX-.005*twoThirdsX, median([ySpec(1)*axesRatio, oneThirdY])];
  989. h_sound_feedback1_pos = [twoThirdsX+.005*twoThirdsX, median([ySpec(1)*axesRatio, oneThirdY])];
  990. h_sound_feedback2_pos = [xSpec(2)-.005*xSpec(2), median([ySpec(1)*axesRatio, oneThirdY])];
  991. if ishandle(h_pos_feedback1); set(h_pos_feedback1,'position',h_pos_feedback1_pos); end
  992. if ishandle(h_pos_feedback2); set(h_pos_feedback2,'position',h_pos_feedback2_pos); end
  993. if ishandle(h_color_feedback1); set(h_color_feedback1,'position',h_color_feedback1_pos); end
  994. if ishandle(h_color_feedback2); set(h_color_feedback2,'position',h_color_feedback2_pos); end
  995. if ishandle(h_sound_feedback1); set(h_sound_feedback1,'position',h_sound_feedback1_pos); end
  996. if ishandle(h_sound_feedback2); set(h_sound_feedback2,'position',h_sound_feedback2_pos); end
  997. end
  998. end
  999. end