REM>Reversal REM REM by Rudolf Cardinal & Hannah Clarke, 15 Feb 2004. REM Modified from "REVER5B" code (Andrew Pears 19 Oct 2001), itself modified from "CRFDIS6" (Angela Roberts?) REM cp stands for correction procedure REM REM HISTORY REM - 19 Apr 2004 (repeating earlier fix of H's): touchscreen_coord% should have read touchscreen_coord$ everywhere REM - 19 Apr 2004 (RNC): randomization bug if max_trials% not exactly divisible by 4 (fixed) REM - 19 Apr 2004 PROCpunish_timeout timer length now Rft%*100 REM REM ********** THINGS TO DO: REM - check max variable length - if first N characters aren't unique, random bugs will appear. What's N? REM H has checked - unique to 30 characters, so N larger than that... perhaps 35 (A...Z1...9) but needs checking REM and some variables here are longer REM - timers should be names rather than magic numbers REM Hitherto, nobody's actually set the random number generator to be random! We need a single command like this: dummy% = RND(-TIME) REM Initialize Arachnid PROCinit PROCkill_all REM Set up graphics environment MODE 28 VDU 19,1,4,0,0,0 BREAK%=0 @%=&20107 :REM sets format for number printing (e.g. number of decimal places) REM clear screen CLS:CLG PROCtake_control_of_lines PROCsetup_parameters PROCbegin REM Having set things up, we surrender control to Arachnid and the program will effectively begin. REM Everything else is driven by Arachnid events. PROCwait(E%):*AE END : DEF PROCtake_control_of_lines REM "free up" inputs (line 0) PROCfree_switch(0,E%) REM "govern" outputs (lines 1-15) LOCAL switch% FOR switch% = 1 TO 15 PROCgovn_switch(switch%,E%) NEXT ENDPROC : DEF PROCsetup_parameters REM line numbers houselight_line% = 1 pump_line% = 2 tone_line% = 3 REM sides left% = 0 right% = 1 REM maximum number of stimuli max_num_stimuli% = 5 Monkey$="":SessNum$="":Date$="":session_length_min%=0:CorrectStimulusNumber%=0:IncorrectStimulusNumber%=0:InCorrectionProcedure%=0 Crf%=0:NonCrf%=0:Rft%=0:UsLoop%=0:Ok$="":trial%=0:BigTouchableResponseAreas$="":OtherLicks%=0 touch_area_left_x1_coord%=0:touch_area_left_x2_coord%=0:touch_area_right_x1_coord%=0:touch_area_right_x2_coord%=0:touch_area_top_coord%=0:touch_area_bottom_coord%=0 stimulus_on_time=0:OtherTouches%=0:num_left_choices%=0:num_right_choices%=0:V=0 Missed%=0:already_touched_this_trial%=0:FILE$="" iti_length_cs%=0:UseCorrectionProcedure$="":max_trials%=0 NonprefRespToEndNormalCorrection%=0:CriterionToStartCorrection%=0:cp_side_to_be_incorrect$="":NumNonprefToEndThisCorrection%=0 NonprefRespToEndInitialCorrection%=0:SideForIncorrectStimForInitialCorr$="":StartWithCorrectionProcedure$="":Median=0:FirstTrialOfSequenceOnCurrentSide%=0 INPUT TAB(2,0) "MONKEY NAME"; Monkey$ INPUT TAB(2,1) "SESSION NUMBER"; SessNum$ File$=Monkey$+SessNum$ INPUT TAB(2,2) "DATE"; Date$ INPUT TAB(2,3) "SESSION LENGTH"; session_length_min% REPEAT PRINT TAB(2,5) "Correct stimulus (1 to ";max_num_stimuli%;") "; INPUT CorrectStimulusNumber% UNTIL CorrectStimulusNumber% >= 1 AND CorrectStimulusNumber% <= max_num_stimuli% REPEAT PRINT TAB(2,6) "Incorrect stimulus (1 to ";max_num_stimuli%;") "; INPUT IncorrectStimulusNumber% UNTIL IncorrectStimulusNumber% >= 1 AND IncorrectStimulusNumber% <= max_num_stimuli% AND IncorrectStimulusNumber% <> CorrectStimulusNumber% INPUT TAB(2,8) "MAXIMUM NO OF TRIALS"; max_trials% INPUT TAB(2,10) "ITI LENGTH AFTER STIMULUS RESPONSE"; iti_length_cs% INPUT TAB(2,11) "RFT LENGTH"; Rft% INPUT TAB(2,12) "CORRECTION PROCEDURE (Y/N)"; UseCorrectionProcedure$ INPUT TAB(2,13) "CORRECTION PROCEDURE STARTS AFTER HOW MANY PRESSES TO ONE SIDE"; CriterionToStartCorrection% INPUT TAB(2,14) "CORRECTION PROCEDURE STOPS AFTER HOW MANY PRESSES TO NON-PREFERRED SIDE"; NonprefRespToEndNormalCorrection% NumNonprefToEndThisCorrection%=NonprefRespToEndNormalCorrection% REPEAT INPUT TAB(2,15) "START SESSION ON CORRECTION PROCEDURE (Y/N)";StartWithCorrectionProcedure$ UNTIL StartWithCorrectionProcedure$="Y" OR StartWithCorrectionProcedure$="N" IF StartWithCorrectionProcedure$="Y" THEN REPEAT INPUT TAB(2,16) "INCORRECT STIMULUS ON WHICH SIDE FOR INITIAL CORRECTION PROCEDURE (L/R)";SideForIncorrectStimForInitialCorr$ UNTIL SideForIncorrectStimForInitialCorr$="L" OR SideForIncorrectStimForInitialCorr$="R" REPEAT INPUT TAB(2,17) "HOW MANY RESPONSES TO NON-PREFERED SIDE TO REMOVE CORRECTION PROCEDURE";NonprefRespToEndInitialCorrection% UNTIL NonprefRespToEndInitialCorrection%>0 NumNonprefToEndThisCorrection%=NonprefRespToEndInitialCorrection% cp_onset_trial%=1 InCorrectionProcedure%=1 cp_side_to_be_incorrect$=SideForIncorrectStimForInitialCorr$ ENDIF INPUT TAB(2,18) "BIG RESPONSE AREA (Y/N)"; BigTouchableResponseAreas$ IF BigTouchableResponseAreas$="Y" THEN :REM Note case sensitivity! Bad style, but nothing elegant here anyway. REM big touch-sensitive areas touch_area_left_x1_coord%=4 touch_area_left_x2_coord%=25 touch_area_right_x1_coord%=45 touch_area_right_x2_coord%=80 touch_area_top_coord%=10 touch_area_bottom_coord%=38 ELSE REM smaller touch-sensitive areas touch_area_left_x1_coord%=6 touch_area_left_x2_coord%=35 touch_area_right_x1_coord%=43 touch_area_right_x2_coord%=78 touch_area_top_coord%=14 touch_area_bottom_coord%=31 ENDIF REPEAT INPUT TAB(2,20) "ANY MISTAKES"; Ok$ UNTIL Ok$="N" REM These *should* be dimensioned as ...(max_trials%) - but unfortunately, REM the code that writes out a byte-based output file (PROCsave) writes out a FIXED NUMBER of bytes. REM So unless we want to rewrite that - and the other program that reads data back out again - we're stuck with poor coding. DIM LeftStimulusNumber(500),RightStimulusNumber(500),LicksDuringStimulus(500),Pair(500),PairLicks(500),Pairlick_latency(500),CopyCorrectRLat(500),CopyIncorrectRLat(500) DIM RespSide$(500),response_latency(500),lick_latency(500) DIM PStart(500),Defunct_CorrTrial(500),Defunct_CorrType(500),Defunct_CorrNum(500),Licks(500),TotalPumpTime(500),IsACorrectionTrial(500) DIM ChosenStimulusNumber(1000) : REM This makes trials pseudorandom (in groups of 4) REM We want two trials of type A and two trials of type B in every 4 trials - otherwise, completely random. DIM temp_array_representing_left(4) temp_array_representing_left(1) = CorrectStimulusNumber% temp_array_representing_left(2) = CorrectStimulusNumber% temp_array_representing_left(3) = IncorrectStimulusNumber% temp_array_representing_left(4) = IncorrectStimulusNumber% REM FOR t%=1 TO (max_trials%-3) STEP 4 :REM was a bug - never filled in some values if max_trials not exactly divisible by 4 FOR t%=1 TO (max_trials%) STEP 4 :REM may go beyond required no. stimuli, PROCshuffle_array(temp_array_representing_left(), 1, 4) LeftStimulusNumber(t%)=temp_array_representing_left(1) LeftStimulusNumber(t%+1)=temp_array_representing_left(2) LeftStimulusNumber(t%+2)=temp_array_representing_left(3) LeftStimulusNumber(t%+3)=temp_array_representing_left(4) NEXT FOR t%=1 TO max_trials% IF LeftStimulusNumber(t%)=CorrectStimulusNumber% THEN RightStimulusNumber(t%)=IncorrectStimulusNumber% IF LeftStimulusNumber(t%)=IncorrectStimulusNumber% THEN RightStimulusNumber(t%)=CorrectStimulusNumber% NEXT ENDPROC : DEF PROCshuffle_array(array(), firstposition%, lastposition%) LOCAL i%, j%, temp REM Algorithm: go through array, swapping each element with a randomly-chosen REM other element that is either the original element or a later one. FOR i%=firstposition% TO lastposition%-1 j% = FNrandom_integer(i%, lastposition%) REM Now swap i'th and j'th elements temp = array(i%) array(i%) = array(j%) array(j%) = temp NEXT ENDPROC : DEF FNrandom_integer(min%,max%) IF min%=max% THEN =min% IF min%>max% THEN =FNrandom_integer(max%,min%) = min% + RND(max% - min% + 1) - 1 : DEF PROCbegin PROCswitch_on(houselight_line%,E%) CLS:CLG VDU23,1,0;0;0;0;:REM Turn off cursor trial%=0 :REM will be incremented to 1 by PROCstart_trial PROCpipe_timer(0,session_length_min%*6000,0,"FNend(",0,E%) PROCstart_trial REM that was piped via 1s timer to FNstart_trial, but (a) unnecessary, (b) may have REM been responsible for crashes if touchscreen touched in first second. REM If touchscreen doesn't work, consider restoring this line: REM PROCpipe_timer(1,100,0,"FNstart_trial(",0,E%) ENDPROC : DEF FNstart_trial(P%,R%) IF R%=0 =0 PROCstart_trial =0 : DEF PROCstart_trial CLS:CLG IF trial%>=max_trials% THEN PROCend trial%+=1 already_touched_this_trial%=0 we_are_in_the_iti% = 0 COLOUR 1: PRINT TAB(0,0) trial%,InCorrectionProcedure%,FirstTrialOfSequenceOnCurrentSide% PROCpipe_switch(0,On,1,"FNOtherLicks(",0,E%) IF UseCorrectionProcedure$="Y" AND InCorrectionProcedure%=1 AND cp_side_to_be_incorrect$="L" THEN LeftStimulusNumber(trial%)=IncorrectStimulusNumber% RightStimulusNumber(trial%)=CorrectStimulusNumber% IsACorrectionTrial(trial%)=1 ENDIF IF UseCorrectionProcedure$="Y" AND InCorrectionProcedure%=1 AND cp_side_to_be_incorrect$="R" THEN LeftStimulusNumber(trial%)=CorrectStimulusNumber% RightStimulusNumber(trial%)=IncorrectStimulusNumber% IsACorrectionTrial(trial%)=1 ENDIF PROCdraw_stimulus(LeftStimulusNumber(trial%), left%) PROCdraw_stimulus(RightStimulusNumber(trial%), right%) stimulus_on_time=TIME PROCpipe_fkey(1,0,1,"FNmimic_left(",0,E%) PROCpipe_fkey(2,0,1,"FNmimic_right(",0,E%) PROCset_up_touchscreen ENDPROC : DEF FNmimic_left(P%,R%) IF R%=0 =0 REM operator pressed F1 key to mimic monkey responding to left stimulus num_left_choices%+=1:response_latency(trial%)=TIME-stimulus_on_time PROCkill_all_fkeys PROCmonkey_responded_left =0 : DEF FNmimic_right(P%,R%) IF R%=0 =0 REM operator pressed F2 key to mimic monkey responding to right stimulus num_right_choices%+=1:response_latency(trial%)=TIME-stimulus_on_time PROCkill_all_fkeys PROCmonkey_responded_right =0 : DEF FNOtherLicks(P%,R%) IF R%=0 =0 OtherLicks%+=1 =0 : DEF FNLicksDuringStimulus(P%,R%) IF R%=0 =0 LicksDuringStimulus(trial%)+=1 =0 : DEF PROCset_up_touchscreen PROCsclr_rbuf(0,E%) PROCsroll_rbuf(0,E%) PROCsroll_tbuf(0,E%) PROCsput_tbuf(0,27,E%) PROCsput_tbuf(0,91,E%) PROCsput_tbuf(0,37,E%) PROCsput_tbuf(0,48,E%) PROCsput_tbuf(0,79,E%) PROCpipe_serial(0,0,0,"FNincoming_touchscreen_coordinate(",0,E%) touchscreen_coord$="" touchscreen_awaiting_escape%=TRUE ENDPROC : DEF FNincoming_touchscreen_coordinate(P%,R%) IF R%=0 =0 LOCAL B% REM Incoming coordinates start with ASCII 27 (ESC). REM Then the coordinate comes in (we'll store it in touchscreen_coord$) REM It ends with ASCII 10 (LF). B%=FNsget_rbuf(0,E%) IF touchscreen_awaiting_escape%=TRUE THEN IF B%=27 THEN touchscreen_awaiting_escape%=FALSE ELSE IF B%=10 PROCscreen_touched ELSE touchscreen_coord$+=CHR$B% ENDIF =0 : DEF PROCscreen_touched Y%=VAL MID$(touchscreen_coord$,2) X%=VAL MID$(touchscreen_coord$,INSTR(touchscreen_coord$,";")+1) touchscreen_coord$="" touchscreen_awaiting_escape%=TRUE IF already_touched_this_trial%=0 THEN IF X%>touch_area_left_x1_coord% AND X%touch_area_top_coord% AND Y%touch_area_right_x1_coord% AND X%touch_area_top_coord% AND Y%=CriterionToStartCorrection% THEN FOR Z=trial% TO (trial%-CriterionToStartCorrection%) STEP -1 IF RespSide$(Z)="R" THEN RightCount%+=1 IF RespSide$(Z)="L" THEN LeftCount%+=1 NEXT ENDIF IF (LeftCount%>=CriterionToStartCorrection%) THEN REM start correction procedure: monkey's made too many L responses InCorrectionProcedure%=1 cp_onset_trial%=trial% cp_side_to_be_incorrect$="L" ENDIF IF (RightCount%>=CriterionToStartCorrection%) THEN REM start correction procedure: monkey's made too many R responses InCorrectionProcedure%=1 cp_onset_trial%=trial% cp_side_to_be_incorrect$="R" ENDIF ENDPROC : DEF PROCconsider_removing_correction LOCAL LeftCount%,RightCount% FOR Z=trial% TO cp_onset_trial% STEP -1 IF RespSide$(Z)="R" THEN RightCount%+=1 IF RespSide$(Z)="L" THEN LeftCount%+=1 NEXT IF cp_side_to_be_incorrect$="L" AND RightCount%>=NumNonprefToEndThisCorrection% THEN REM stop correction procedure: monkey has successfully avoided L side for a while InCorrectionProcedure%=0 cp_onset_trial%=0 cp_side_to_be_incorrect$="" NumNonprefToEndThisCorrection%=NonprefRespToEndNormalCorrection% FirstTrialOfSequenceOnCurrentSide%=trial% ENDIF IF cp_side_to_be_incorrect$="R" AND LeftCount%>=NumNonprefToEndThisCorrection% THEN REM stop correction procedure: monkey has successfully avoided R side for a while InCorrectionProcedure%=0 FirstTrialOfSequenceOnCurrentSide%=trial% cp_onset_trial%=0 cp_side_to_be_incorrect$="" NumNonprefToEndThisCorrection%=NonprefRespToEndNormalCorrection% ENDIF ENDPROC : DEF FNstart_iti(P%,R%) IF R%=0 =0 IF Licks(trial%)=0 THEN Missed%+=1 PROCiti =0 : DEF PROCiti CLS:CLG IF InCorrectionProcedure%=0 THEN PROCconsider_starting_correction IF InCorrectionProcedure%=1 THEN PROCconsider_removing_correction PROCswitch_off(pump_line%,E%) PROCswitch_off(tone_line%,E%) PROCpipe_switch(0,On,1,"FNOtherLicks(",0,E%) PROCpipe_timer(5,iti_length_cs%*100,0,"FNstart_trial(",0,E%) we_are_in_the_iti% = 1 PROCset_up_touchscreen ENDPROC : DEF FNbinbytes(P%,R%) LOCAL B% IF R%=0 =0 B%=FNsget_rbuf(0,E%) ENDIF =0 : DEF FNend(P%,R%) IF R%=0 =0 PROCkill_all_switches PROCkill_all_timers PROCend =0 : DEF PROCend CLS:CLG Session=((session_length_min%*6000)-FNtimer(0,E%))/6000 VDU 23,1,1;0;0;0;:REM Switch on cursor VDU 19,3,7,0,0,0 PROCkill_all_switches PROCkill_all_timers PROCswitch_off(pump_line%,E%) PROCswitch_off(houselight_line%,E%) PROCswitch_off(tone_line%,E%) COLOUR 3 PROCsave LOCAL Printer$ REPEAT INPUT TAB(2,2)"PRINTER READY ";Printer$: UNTIL Printer$="Y" CLS:CLG VDU 2 :REM turn on printer VDU 1,12 PRINT TAB(2,0)"SIMPLE DISCRIMINATION" PRINT TAB(2,1)"MONKEY NAME : ";:PRINT TAB(45,1)Monkey$ PRINT TAB(2,2)"DATE : ";:PRINT TAB(45,2)Date$ PRINT TAB(2,3)"SESSION NUMBER : ";:PRINT TAB(45,3)SessNum$ PRINT TAB(2,5)"SESSION LENGTH (min) : ";:PRINT TAB(45,5)Session PRINT TAB(2,6)"POSITIVE STIMULUS NUMBER";:PRINT TAB(45,6)CorrectStimulusNumber% PRINT TAB(2,8)"NEGATIVE STIMULUS NUMBER";:PRINT TAB(45,8)IncorrectStimulusNumber% PRINT TAB(2,10)"ITI LENGTH : ";:PRINT TAB(45,10)iti_length_cs% PRINT TAB(2,11)"MAXIMUM NO OF TRIALS : ";: PRINT TAB(45,11)max_trials% PRINT TAB(2,13)"ITI LENGTH AFTER STIMULUS RESPONSE : ";: PRINT TAB(45,13)iti_length_cs% PRINT TAB(2,14)"RFT LENGTH : ";: PRINT TAB(45,14)Rft% PRINT TAB(2,15)"BIG RESPONSE AREA : ";: PRINT TAB(45,15)BigTouchableResponseAreas$ PRINT TAB(2,16)"CORRECTION PROCEDURE : ";: PRINT TAB(45,16)UseCorrectionProcedure$ PRINT TAB(2,17) "CORRECTION PROCEDURE STARTS AFTER HOW MANY PRESSES TO ONE SIDE :"; CriterionToStartCorrection% PRINT TAB(2,18) "CORRECTION PROCEDURE STOPS AFTER HOW MANY PRESSES TO NON-PREFERRED SIDE :"; NonprefRespToEndNormalCorrection% PRINT TAB(2,19) "START SESSION ON CORRECTION PROCEDURE (Y/N)";StartWithCorrectionProcedure$ PRINT TAB(2,20) "INCORRECT STIMULUS ON WHICH SIDE FOR INITIAL CORRECTION PROCEDURE (L/R)";SideForIncorrectStimForInitialCorr$ PRINT TAB(2,21) "HOW MANY RESPONSES TO NON-PREFERED SIDE TO REMOVE INITIAL CORRECTION PROCEDURE";NonprefRespToEndInitialCorrection% PROCdoResults VDU 3 :REM turn off printer PROCreset_touchscreen END ENDPROC : DEF PROCreset_touchscreen PROCsput_tbuf(0,27,E%) PROCsput_tbuf(0,91,E%) PROCsput_tbuf(0,37,E%) PROCsput_tbuf(0,50,E%) PROCsput_tbuf(0,79,E%) ENDPROC DEF PROCdoResults REM trial% contains the number of the highest completed trial (see PROCstart_trial) LOCAL PosResp%,NegResp%,Posresponse_latency,CrfLicks,NonCrfLicks,Negresponse_latency LOCAL CrfPres%,CrfCollect%,CrfLicks,CrfLat,Inc%,Corr% LOCAL NonCrfPres%,NonCrfCollect%,NonCrfLicks,NonCrfLat LOCAL TrialLeft%,TrialRight% LOCAL MedianCorrRLat,MedianIncRLat PRINT "" PRINT "" PRINT "---------------------------- SESSION RESULTS ----------------------------" PRINT "" PRINT "TOTAL NUMBER OF RESPONSES :"; Responses% FOR t%=1 TO trial% IF ChosenStimulusNumber(t%)=IncorrectStimulusNumber% THEN NegResp%+=1 Negresponse_latency=Negresponse_latency+response_latency(t%) NonCrfLicks=NonCrfLicks+LicksDuringStimulus(t%) ENDIF IF ChosenStimulusNumber(t%)=CorrectStimulusNumber% THEN PosResp%+=1 Posresponse_latency=Posresponse_latency+response_latency(t%) CrfLicks=CrfLicks+LicksDuringStimulus(t%) ENDIF NEXT PRINT "" PRINT "POSITIVE RESPONSES : "; PosResp% IF PosResp%>0 THEN Posresponse_latency=(Posresponse_latency/PosResp%)/100 PRINT "MEAN POSITIVE RESPONSE LATENCY : "; Posresponse_latency IF PosResp% >0 THEN CrfLicks=CrfLicks/PosResp% : FOR t%=1 TO trial% IF ChosenStimulusNumber(t%)=CorrectStimulusNumber% THEN CopyCorrectRLat(t%)=response_latency(t%) IF ChosenStimulusNumber(t%)=IncorrectStimulusNumber% THEN CopyIncorrectRLat(t%)=response_latency(t%) NEXT MedianCorrRLat = FNMedian(CopyCorrectRLat(),trial%) MedianIncRLat = FNMedian(CopyIncorrectRLat(),trial%) PRINT "MEDIAN POSITIVE RESPONSE LATENCY : "; MedianCorrRLat/100 PRINT "" PRINT "NEGATIVE RESPONSES : "; NegResp% IF NegResp%>0 THEN Negresponse_latency=(Negresponse_latency/NegResp%)/100 PRINT "MEAN NEGATIVE RESPONSE LATENCY : "; Negresponse_latency PRINT "MEDIAN NEGATIVE RESPONSE LATENCY : "; MedianIncRLat/100 IF NegResp%>0 THEN NonCrfLicks=NonCrfLicks/NegResp% PRINT "" PRINT "MISSED REWARDS : "; Missed% PRINT "" PRINT "OTHER LICKS : ";OtherLicks% PRINT "OTHER TOUCHES : "; OtherTouches% PRINT "" PRINT "RESPONSES LEFT/RIGHT : ";num_left_choices%;" / ";num_right_choices% PRINT "" : PROCprint_trial_by_trial_summary ENDPROC : DEF PROCprint_trial_by_trial_summary PRINT "" PRINT "-------------------------- TRIAL BY TRIAL SUMMARY --------------------------" PRINT "" PRINT " TRIAL L R RLAT lick_latency SIDE RESPONSE CORR" LOCAL t% FOR t%=1 TO (num_left_choices%+num_right_choices%) PRINT t%;" "; IF LeftStimulusNumber(t%)=CorrectStimulusNumber% THEN PRINT "Cor "; IF LeftStimulusNumber(t%)=IncorrectStimulusNumber% THEN PRINT "Inc "; IF RightStimulusNumber(t%)=IncorrectStimulusNumber% THEN PRINT "Inc "; IF RightStimulusNumber(t%)=CorrectStimulusNumber% THEN PRINT "Cor "; PRINT response_latency(t%)/100;" "; PRINT lick_latency(t%)/100;" "; PRINT RespSide$(t%);" "; IF ChosenStimulusNumber(t%)=CorrectStimulusNumber% THEN PRINT " Cor "; IF ChosenStimulusNumber(t%)=IncorrectStimulusNumber% THEN PRINT " Inc "; PRINT IsACorrectionTrial(t%) NEXT ENDPROC : DEF FNMedian(Array(),Size%) REM This function appears to return the median of an array of observations, *ignoring* all zero observations. REM We might call that "interesting"... REM So it's not a proper median sort function, but zero lick latencies are to be ignored, hence the code. REM Nor does it give the exact median (taking the mean of the two middle observations if there are an even REM number of observations in total)... REM However, this slightly incorrect median-obtaining procedure is the one that's in use... LOCAL FirstZero% PROCsort(Array(),Size%) FirstZero%=1 FOR X=1 TO Size% IF Array(X)>0 THEN FirstZero%+=1: ENDIF NEXT X =Array(FirstZero%/2) : DEF PROCsort(Array(), Size%) REM Sorts an array in descending order REM Arrays must begin at 1 REM Bubble sort LOCAL Pass,Count,Temp FOR Pass=1 TO Size% FOR Count=1 TO Size%-1 IF Array(Count)