Runtime Documentation The following program is the source code called runtime.scr that executes (runs) trials and collects data for classical conditioning. This is referred to as the Runtime program. When it is compiled, the Runtime program is usually saved as forth.com and activated by typing forth<return>. The Runtime program requires the use of the file menus.scr for selecting and setting parameters for conditioning. The file menus.scr is in the next appendix. The Runtime program incorporates low level machine language for controlling the events and collecting data (up to 4 A/D channels plus up to 4 discriminated unit channels) during classical conditioning. The assembly language is Forth-like (meaning it uses reverse Polish notation, i.e., it operates like a Hewlitt-Packard calculator) which transparently creates the actual machine code. The higher level programming is in the archaic Forth language, a language originally designed to control large observatory telescopes and has been used in smaller robotics applications. Steinmetz had experience with FIRST language from the Gormezano/Scandrett implementation of classical conditioning which like Forth is a threaded interpretive language. Lavond had independently investigated Forth because it had both high and low level programming capabilities and could be implemented on machines with little memory. Things have changed in the world of programming since the early and mid 1980s so that far more computing speed and memory is now available. When students now ask Lavond what computer language one should learn, he recommends either C (C++, visual C) or Matlab. Although the Forth programs presented here might seem obsolete, it should be noted that the popular C programming language shares many of the advanced programming features of Forth (but not all of them, like the ability to create new compilation structures like value [vlu] and case [see runtime.scr for these examples] and object oriented programming). Having had experience with BASIC, Forth, Fortran and C as higher level languages, and machine language programming for 6502, 6800, 68000, Z80, 8086/8088 microprocessors, one realizes that programming structures and strategies are often similar if not identical, making it is relatively easy to import or translate from one computer language to another. The following program is written in Forth-83 using the MasterForth implementation for IBM-PCs by Micromotion, which is no longer available. We have permission from the owner, Martin Tracy, to distribute a limited number of copies his Forth language, although we have had few occasions to do so. The IBM version of the program was imported and upgraded from our Apple II implementation, using MasterForth for the Apple II by Micromotion. The major programming effort in importing from Apple to IBM was in the machine language code and the differences in the computer interface. Lavond designed the computer interfaces for both the Apple II and IBM systems. Currently, Laboratory Microsystems offers a Windows based version of Forth-83 called WinForth which we have used for development of our cluster program. In the following, text that follows a backslash (\) is a comment to the end of the line. Forth encourages good documentation of the programming definitions by use of space, indents, new lines, and local comments (using either a backslash '\' for a comment to the end of a line, or delimiting parentheses '( )' usually for describing the parameters on the stack. Shorter comments are inside parentheses. Attention should be paid to the base of the numbers being compiled, usually either base 16 (hexadecimal or hex) or base 10 (decimal). (To help in other applications we have also created binary for base 2.) Hexadecimal is mostly used for hardware addressing and assembly language; decimal is used in most other circumstances. To simplify understanding of the source code we have edited out any commands used to control the flow of compilation (for example, you will not find stop, load, or thru in runtime.scr). However, the Runtime program does use these commands in the definitions of words to control the flow of menu selection in the menus.scr file. The menus.scr file is necessary to run trials. The Summary source code (summary.scr), on the other hand, uses compilation control extensively for menu selection so we will see these commands there. References to the screen number indicate the beginning of the original 'page' for that particular code. The screen number was used in compilation, and here it is useful for indicating the location of the code in the original file. In some instances we have combined screens for clarity. We have also added extensive comments for explanation, a luxury in using space that is not available in the original file. Originally, a 'screen' was defined as the size of the data which would fit on a computer monitor, 64 characters by 16 lines for a total of 1024 bytes. Definitions or programming structures were encouraged to be made in 1024 increments. The Forth file control words block and buffer operate on these 1024 byte increments. For convenience with native Forth programming this is the reason why trials begin at 1024 increments and why the available space for data collection for each trial in this implementation is 1024, although other Forth structures would allow any size boundaries. Block 0 reserves 1024 bytes for a comment and other actions, block 1 is the next 1024 bytes used for trial 1, block 2 is the next 1024 bytes used for trial 2, et cetera, and the last trial number is the last block number used for the run. The stack and its comments deserve further discussion. The stack is a temporary storage space ("first in, last out") that is often used for passing parameters in Forth. The comment "( --)" indicates that the just defined word (indicated by the --s) stands alone without being passed or passing any arguments. The comment "( n --)" indicates that the just defined word receives one argument. The comment "( -- n)" indicates that the just defined word passes one argument to the next definition. The comment "( adr n -- x y)" indicates that the just defined word receives two arguments (an 'address' and 'n', usually the length of a string found at the address) and leaves two arguments (here 'x' and 'y'). \ screen 0 \ runtime.scr file \ \ (C) COPYRIGHT 1985 - 2000 DAVE LAVOND AND JOE STEINMETZ.. ALL RIGHTS RESERVED. \ \ this main memory map shows how memory is allocated during the run: \ \ RAM DROP trial data collected if SAFETY ON (1k). \ TRL# @ BUFFER trial data collected if SAFETY OFF (1k). \ RAM DROP 1K + location of ITI display (1k). \ RAM DROP 1K 2* + location of block graphics display (2k). \ screen 1 \ some handy definitions : RAM ( -- adr n) \ identifies the beginning of RAM space that can be used for temporary storage of large amounts of data. PAD 256 + LIMIT OVER - ; : VERSION ( -- adr n) \ identifies the date of compilation for forth.com. " Version: IBM v. 1.2.4 08nov99dgl" ; : COPYRIGHT ( --) ." (C) Copyright 1985 - 2000 Dave Lavond and Joe Steinmetz" cr ." All Rights Reserved." CR CR ; : LAUNCH TX MENU ; ' LAUNCH IS READY \ 'ready' is a defered definition that is specific to Micromotion's MasterForth implementation. \ here 'ready' is resolved as 'launch' which means that when this compilation is first executed in DOS \ with forth<return> that it immediately loads in the program forth.com (the compiled version of \ runtime.scr) and then executes launch. \ screen 2 \ hardware addressing documentation ... \ \ hardware addressing: \ address bits 0-2: within chip addressing \ address bits 3-8: switch selectable comparator \ address bit 9: set to 1 to access all cards \ address bits 10-12: decode 74LS138 \ address bits 13-15: not used; assume set to 0 \ \ current base address (msb>lsb): \ 15 14 13 12 . 11 10 9 8 . 7 6 5 4 . 3 2 1 0 \ ----------------------------------------------------\ 0 0 0 0 0 0 1 1 1 0 0 1 0 0 0 0 binary \ 0 3 9 0 hex \ screen 3 \ base hardware addresses ... \ \ HEX \ 0390 = 8253 timer #1 for intratrial intervals \ 0790 = 0808 a/d converter write \ 0B90 = 8255 i/o #1 intratrial \ 0F90 = 0830 d/a converter \ 1390 = 8253 timer #2 for intertrial intervals \ 1790 = 0808 a/d converter read \ 1B90 = 8255 i/o #2 \ 1F90 \ DECIMAL \ \ screen 4 \ 8253 timer #1 used for runtime intrastimulus interval (isi) & unit counters ... HEX 0390 \ timer #1 base address DUP DUP 1+ DUP 2+ DUP 3 + DROP CONSTANT TIME0 CONSTANT TIME1 CONSTANT TIME2 CONSTANT CW8253#1 : REG! ( n adr --) \ store hi-byte and lo-byte in the correct order into the register. >R SPLIT SWAP R@ PC! R> PC! ; : REG@ ( adr -- n) \ read hi-byte and lo-byte in the correct order from the register. >R R@ PC@ R> PC@ COMBINE ; : LATCH0 ( --) \ save count on latch 0 so that it can be read before resetting it to zero. \ used as isi (binwidth) timer. 00 CW8253#1 PC! ; : LATCH1 ( --) \ save count on latch 1 so that it can be read before resetting it to zero. \ used as unit 1 counter. 40 CW8253#1 PC! ; : LATCH2 ( --) \ save count on latch 2 so that it can be read before resetting it to zero. \ used as unit 2 counter. 80 CW8253#1 PC! ; : INIT8253#1 ( --) \ initialize 8253 timer/counter for intratrial and units. 36 ( mode 3, t0) CW8253#1 PC! 3E8 ( 1 ms) TIME0 REG! 70 ( mode 0, t1) CW8253#1 PC! 0 ( zero) TIME1 REG! B0 ( mode 0, t2) CW8253#1 PC! 0 ( zero) TIME2 REG! ; DECIMAL \ screen 5 \ 8253 timer #2 expansion used for unit counting ... HEX \ bin timer \ unit1 counter \ unit2 counter 1390 \ timer #2 base address. DUP CONSTANT TIME3 DUP 1+ CONSTANT TIME4 DUP 2+ CONSTANT TIME5 DUP 3 + CONSTANT CW8253#2 DROP \ : REG! ( n adr --) >R SPLIT SWAP R@ PC! R> PC! ; \ already defined above. \ : REG@ ( adr -- n) >R R@ PC@ R> PC@ COMBINE ; \ already defined above. : LATCH3 ( --) \ save count on latch 3 so that it can be read before resetting it to zero. \ used as unit 3 counter. 00 CW8253#2 PC! ; : LATCH4 ( --) \ save count on latch 4 so that it can be read before resetting it to zero. \ used as unit 4 counter. 40 CW8253#2 PC! ; : LATCH5 ( --) \ save count on latch 5 so that it can be read before resetting it to zero. \ currently not used but set up for counting in initialization (next). 80 CW8253#2 PC! ; : INIT8253#2 ( --) \ initialize for unit counting. 30 ( mode 0, t3) CW8253#2 PC! 0 TIME3 REG! \ unit 3 70 ( mode 0, t4) CW8253#2 PC! 0 TIME4 REG! \ unit 4 B0 ( mode 0, t5) CW8253#2 PC! 0 TIME5 REG! ; \ unused unit DECIMAL \ screen 6 \ 8255 i/o #1 functions used for runtime stimuli & interface control ... \ \ PORTA input undefined function \ 01= 02= 04= 08= \ 10= 20= 40= 80= \ PORTB output for RUNTIME stimuli (same as EXTERNAL) \ 01=tone 02=light 04= 08=air \ 10=shock 20=tape 40=sync 80= \ PORTC LOWER output for RUNTIME unit collection \ 01=unit1 02=unit2 04=unit3 08=unit4 \ PORTC UPPER input for RUNTIME (similar to INTERNAL) \ 10=out0 20=eoc-a/d 40=out4 80=extsync \ HEX 0B90 \ i/o base address 8255 #1 DUP CONSTANT PORTA DUP 1+ CONSTANT PORTB DUP 2+ CONSTANT PORTC DUP 3 + CONSTANT CW8255#1 DROP DECIMAL \ screen 7 \ 8255 i/o #2 functions used for expansion audio programs ... \ \ PORTD output to dB attenuator \ 01=0.5dB 02=1dB 04=2dB 08=4dB \ 10=8dB 20=16dB 40=32dB 80=64dB \ PORTE output to frequency generator \ 01= 02= 04= 08= \ 10= 20= 40= 80= \ PORTF LOWER output undefined funtion \ 01= 02= 04= 08= \ PORTF UPPER input undefined function \ 10= 20= 40= 80= \ HEX 1B90 \ i/o base address 8255 #2 DUP CONSTANT PORTD DUP 1+ CONSTANT PORTE DUP 2+ CONSTANT PORTF DUP 3 + CONSTANT CW8255#2 DROP DECIMAL \ screen 8 \ initialize 8255s \ \ 8255 programmable mode 0 configurations ... \ mode 0, control word 00, $80: Aout,Bout,CLout,CUout. \ mode 0, control word 04, $88: Aout,Bout,CLout,CUin. \ mode 0, control word 12, $98: Ain,Bout,CLout,CUin. \ screen 9 \ HEX : INIT8255#1 ( --) 98 CW8255#1 PC! ; : INIT8255#2 ( --) 80 CW8255#2 PC! ; DECIMAL \ screen 10 \ 0808 a/d converter used for runtime nictitating membrane data collection ... HEX \ Remember: there are 8 a/d converters at each address 0790 CONSTANT '0808WR \ write a/d converter 1790 CONSTANT '0808RD \ read a/d converter DECIMAL \ handy debugging tools for the a/d converter ... \ \ HEX \ \ : EOC ( --) BEGIN PORTC PC@ 20 AND UNTIL ; \ wait for a/d conversion to be valid \ \ : A/DING ( --) \ \ use: a/ding<return> continually prints values of a/d #1 and #2. \ \ any key press temporarily stops displays; a <return> as the second key press aborts, \ \ any other second key continues display. \ BEGIN CR \ 0 '0808WR PC! EOC '0808RD PC@ 0 8 D.R \ 0 '0808WR 1+ PC! EOC '0808RD 1+ PC@ 0 8 D.R \ KEY? \ IF CR KEY DROP KEY EOL# = IF 1 ELSE 0 THEN ELSE 0 THEN \ UNTIL ; \ \ DECIMAL \ \ \ an extra interface address could be used for additional expansion, for example for a 0830 d/a converter ... HEX 0F90 CONSTANT '0830 \ d/a converter DECIMAL \ screen 11 \ initialize hardware configuration : RESET ( --) INIT8253#1 INIT8253#2 INIT8255#1 INIT8255#2 ; \ the following arrays are needed by new unit collection routine ... hex create ^latch 40 , 80 , 00 , create ^control cw8253#1 , cw8253#1 , cw8253#2 , create ^read time1 , time2 , time3 , create ^reset 01 , 02 , 04 , decimal 40 , cw8253#2 , time4 , 08 , \ screen 12 \ declaration of values ... \ \ "values" are treated like constants except that their values can be changed. \ a "value" has the execution speed of a constant but the ability to change like a variable. \ in fact, in forth, a constant can be changed by using 'is', but using a "value" more formally \ declares the intention of changing its worth. here, this is more of a stylistic decision for \ documentation purposes. although this is a trivial example, forth's ability to create new \ compiling functions is a strength that is seen in object-oriented languages. : VLU \ create a 'value' having 'name' and doing a fetch operation of the name. \ in reality this is the same definition as a constant. CREATE ( name) 0 , DOES> @ ( --n) ; \ words conncerned with timing stimulus presentations: VLU TONEON VLU TONEOFF VLU LTON VLU LTOFF VLU AIRON VLU AIROFF VLU SHKON \ bin number to turn on tone. \ bin number to turn off tone. \ bin number to turn on light. \ bin number to turn off light. \ bin number to turn on air puff. \ bin number to turn off air puff. \ bin number to turn on shock; also used for \ arrival of air puff at cornea. \ bin number to turn off shock. \ bin number of auxilliary pulse $80 to PORTB, \ e.g., trigger oscilloscope. VLU SHKOFF VLU PULSEON \ the following are used for isi timing: VLU BIN# VLU PTS \ current intratrial bin number. \ "number of points", the total number of bins per trial. \ miscellaneous: VLU BLN \ "baseline", used to save the PCS NM baseline. \ screens 13 and 14 \ declaration of variables ... VARIABLE BIN \ binwidth, record in microseconds but only whole \ number milliseconds are legal. for example, \ 4 msec binwidth = 4000 bin ! VARIABLE MINIMUM VARIABLE RANGE \ minimum intertrial interval. \ range of intertrial intervals, add to minimum \ intertrial value. VARIABLE #A/D \ number of nictitating membrane channels to \ collect during trial. \ number of unit channels to collect during trial. \ total number of trials to run this session. \ total number of blocks to run this session. \ number of trials per block to run this session. \ next trial number (current trial number + 1). VARIABLE #UNITS VARIABLE #TRLS VARIABLE #BLKS VARIABLE #T/BLK VARIABLE TRL# VARIABLE TYPE# \ previous trial type: tone, tone-air, etc.; used for \ correct display. VARIABLE BASELN 160 baseln ! VARIABLE CRITLEVEL 5 critlevel ! \ msec before CS onset to look for bad trials. \ "criterion level" for defining a conditioned response. \ for example, a critlevel of 5 means that the \ response must be equal to or greater than \ 0.5 mm to be counted as a conditioned response. \ time in msec after CS onset to look for bad trials. \ amount of movement above or below baseline \ which results in declaring a bad trial. for example, \ a bada/d of 7 means that movement greater than \ or equal to 0.7 mm relative to baseline is \ considered to be too much to reliably measure \ behavior in the trial. \ count of preCS unit activity which causes declaration \ of a bad trial. for example, badunits of 100 means \ that a bad trial occurs when baseline activity is VARIABLE BADTIME VARIABLE BADA/D 25 badtime ! 7 bada/d ! VARIABLE BADUNITS 100 badunits ! \ equal to or greater than a count of 100 spikes. \ the following are used for adapting this software to collecting data from human subjects ... variable maxeye 255 maxeye ! 255 constant 8bita/d \ maximum eyelid opening made with a ruler with \ human subjects, this is used to calibrate \ measurements in human studies. The value is \ equal to the distance * 0.1 mm. for example, a \ maxeye of 255 (the value used for all rabbits, this is \ the maximum a/d converter range) means eye \ closures up to 25.5 mm can be measured. \ maximum a/d converter value; used for detecting \ errors determining relative proportions in data \ collection human studies. : rescale ( n -- n') 8bita/d um* maxeye @ um/mod swap maxeye @ 2/ + maxeye @ / + ; : descale ( n -- n') maxeye @ um* 8bita/d um/mod swap 8bita/d 2/ + 8bita/d / + ; \ the following are used to ensure correct unit counts ... VARIABLE SANE# VARIABLE UNITDURATION 200 ( usec) UNITDURATION ! \ best guess of correct number of units \ countable in bin duration. \ unit discriminator pulse in usec -- used to calculate \ max #units poss ( SANE#) to count during BIN. \ see #ERRORS array. \ Haer discriminator output pulse duration. \ screen 15 \ declaration of soft switches ... \ \ "soft switches" are simply variables that can have values of logic 0 (no or off) or logic 1 (yes or on). \ testing of the condition of a soft switch (usually with "if" statements in the program) can determine the \ flow of the program, for example, if "printing" was a soft switch (as it is in the summary programs) and \ turned on then the output would be redirected to a printer. in fact, there is nothing that prevents a \ switch from taking on a value other than 0 or 1. the use of a "switch" is primarily for documentation in \ declaring its function. : SWITCH \ in reality this is the same definition as a variable. CREATE ( name) 0 , DOES> ( --adr) ; SWITCH FILTER SWITCH FLASH SWITCH USE SWITCH TAPE SWITCH PAUSE SWITCH SAFETY SWITCH PICTURE SWITCH LIVE SWITCH PCSPULSE : USED ( --) USE ON ; \ ON = filter nm data. \ ON = flash inhibit message. \ ON = previous data to save; used at start. \ ON = collect data on tape recorder. \ ON = pause between blocks. \ ON = do NOT store data to disk. \ ON = show picture during iti; OFF = show data. \ ON = collect online; OFF = collect offline. \ ON = PCS syncpulse; OFF = no PCS syncpulse. : UNUSED ( --) USE OFF ; \ screens 16 and 17 \ declaration of arrays and strings ... 4 CONSTANT MAX#A/D 4 CONSTANT MAX#UNITS 32 CONSTANT AREA \ max allowable number of a/d channels to collect. \ max allowable number of unit channels to collect. \ data field size for summarizing a/d data. CREATE INFO AREA MAX#A/D * 1+ ALLOT \ area used for on-line data computations. \ see measurement screens ahead for information on \ this array. : CLEAN ( --) \ erase area used for on-line data computations. INFO [ AREA MAX#A/D * ] LITERAL ERASE ; MAX#A/D 2* CREATE NMRV CREATE (NMRV) CREATE ^NMR DROP DUP ALLOT DUP ALLOT DUP ALLOT \ allow up to 4 a/d data pointers. \ points to previous trial's display data during iti. \ intratrial/current pointer, used by trial words. MAX#UNITS 2* CREATE UNITV DUP ALLOT CREATE (UNITV) DUP ALLOT CREATE ^UNIT DUP ALLOT CREATE #ERRORS DUP ALLOT DROP \ allow up to 4 unit data pointers. \ points to previous trial's display data during iti. \ intratrial/current pointer, used by trial words. \ total unit counter errors in session. \ declaration of (diskette and/or hard) drives... 2 STRING PROGRAMDRIVE " e" PROGRAMDRIVE S! \ drive where program files are found. \ initial default drive for program disk. 2 STRING DATADRIVE " e" DATADRIVE S! \ drive where raw trial data is stored. \ initial default drive for data disk. 2 string librarydrive " e" librarydrive s! \ drive where on-line computations are stored. \ initial default drive for library disk. \ screen 18 \ 'scale' converts milliseconds into number of bins ... \ 'scale' is used on the isi parameter screen in the menus file to convert the time-bins (in msec) for an \ event during a trial into the bin number (i.e., the number of the bin from the beginning of the trial). If \ the number of msec is not evenly divisible by the bin width, then an error message occurs and nothing \ is stored in the appropriate event variable. if one does not see the error message and tries to run trials, \ then the program will crash. a typical symptom is that the stimuli will come on and stay on. the fact \ that a 0 is stored is a source of "bug" -- it is not really a bug in the sense that an attempt was made to \ enter an incorrect value and one should be made aware of the problem. the newer, faster computers \ go so fast that one may not see the error message. : SCALE ( word ( n--) BIN @ 1000 / ( usec --> msec) /MOD SWAP ( same as 0 <> ) IF ." BINWIDTH ERROR" DROP CR ELSE ' >BODY ! THEN ; \ screen 19 \ intratrial events documentation: \ \ the following code contain words that primarily control \ trial events during the intertrial interval. \ primitive port control \ event port control \ binwidth timing \ data collection \ trial timing and execution \ assembly language code for hardware manipulations ... \ in the following, forth definitions are given for documentation purposes. the forth definitions can be \ compiled for testing purposes but they will be slower than the compiled machine code. the actual \ runtime code is the given assembly language code which is compiled as machine code. \ screen 20 \ port set and reset ... \ : PSET ( b --) PORTB PC@ OR PORTB PC! ; LABEL 'PSET ( --) \ load BX with mask value before using this routine. PORTB # DX MOV DX AL IN BL AL OR AH AH SUB DX AL OUT RET C; CODE PSET ( b --) BX POP 'PSET # CALL NEXT C; \ the Forth equivalent is ... \ : PRESET ( b --) PORTB PC@ TUCK AND NOT AND PORTB PC! ; LABEL 'PRESET ( --) \ load BX with mask value before using this routine. PORTB # DX MOV DX AL IN AX PUSH BX AX AND AL NOT BX POP BL AL AND AH AH SUB DX AL OUT RET C; CODE PRESET ( b --) BX POP 'PRESET # CALL NEXT C; \ screen 21 \ the following code is used to give output pulses. \ sychronization (sync) pulses are used to mark trial onset, CS (tone and light) onset and US (shock) \ onset. pulse is used to put an arbitrary pulse out at any time during the trial (originally to trigger an \ oscilloscope in the middle of the CS period). HEX LABEL 'SYNCPULSE ( -- adr) \ give synchronization pulse to tape. 40 # BX MOV 'PSET # CALL 40 # BX MOV 'PRESET # CALL RET C; \ for debugging use ... \ CODE SYNCPULSE ( --) 'SYNCPULSE # CALL NEXT C; LABEL 'PULSE ( -- adr) \ give auxilliary pulse to PORTB at $80. 80 # BX MOV 'PSET # CALL 80 # BX MOV 'PRESET # CALL RET C; DECIMAL \ screen 22 \ the following code is useful for testing the unit counter: \ HEX \ \ CODE READ ( --n) \ CW8253#1 # DX MOV 40 # AL MOV DX AL OUT \ AX AX XOR TIME1 # DX MOV DX AL IN AL AH MOV \ DX AL IN AH AL XCHG AX NEG \ AX PUSH NEXT C; \ \ CODE ZERO ( --) TIME1 # DX MOV 00 # AL MOV \ DX AL OUT DX AL OUT NEXT C; \ \ CODE STROBE ( --) \ PORTC # DX MOV DX AL IN 01 # AL OR DX AL OUT \ FE # AL AND DX AL OUT NEXT C; \ DECIMAL \ \ screens 23, 24, 25 and 26 \ data collection ... HEX LABEL 'VALID ( --) \ prevent false readings of unit counter. \ input AL = count; output AL = best guess < SANE# or 1. FF # AH MOV ( starting mask) 2 L: SANE# ) AL CMP 1 L# JBE AH SHR AH AL AND CLC 2 L# JNC ( test until < sane#) 1 L: 00 # AL CMP 3 L# JNE 01 # AL MOV 3 L: AH AH XOR RET C; LABEL 'DATSR ( -- addr) \ 'datsr is the main subroutine for collecting data during the trial. 0 # #A/D ) CMP 1 L# JE ( branch if no a/d to collect) \ collect multiple nictitating membrane data: 0 # DI MOV ( channel#) CX PUSH #A/D ) CX MOV ( #channels) 0 # AL MOV '0808RD # DX MOV ( base ch 0) DX DEC ( dummy) 5 L: DX INC ( adr ch x) EFFF # DX AND ( prep to write a/d ch x) DX AL OUT ( start a/d ch x) DX PUSH PORTC # DX MOV 3 L: DX AL IN 20 # AL AND 3 L# JE DX POP 1000 # DX OR ( prep to read a/d x) DX AL IN ( read a/d x) ^NMR # BX MOV 0 [BX+DI] BX MOV ( 'data for proper channel) AL 0 [BX] BYTE MOV ( store data) ^NMR # BX MOV 0 [BX+DI] INC ( advance data pointer) DI INC DI INC ( advance channel pointer) 5 L# LOOP CX POP 1 L: 0 # #UNITS ) CMP 2 L# JE ( branch if no units to collect) \ collect unit count data: cx push ( save) 0 # di mov ( offset into tables by channel #) #units ) cx mov ( CX = # loops for channels) 6 l: ^latch # bx mov byte 0 [bx+di] al mov ^control # bx mov word 0 [bx+di] dx mov dx al out ( latch) ^read # bx mov 0 [bx+di] dx mov dx al in al ah mov dx al in ( read) al ah xchg ax neg ( reconstruct in AX) sane# ) al cmp 7 l# jbe ( branch if valid) #errors # bx mov 0 [bx+di] inc 'valid # call ( else error) 7 l: ^unit # bx mov 0 [bx+di] bx mov al 0 [bx] byte mov ^unit # bx mov 0 [bx+di] inc ( store) ^read # bx mov 0 [bx+di] dx mov 0 # al mov dx al out dx al out ( zero) ( NOTE: pulse works bec low portc = out, hi portc = in) ^reset # bx mov byte 0 [bx+di] al mov portc # dx mov dx al out 0 # ax mov dx al out ( pulse) di inc di inc ( advance to next unit) 6 l# loop cx pop ( restore) 2 L: RET C; DECIMAL \ Note ^NMR and ^UNIT data storage pointers are incremented automatically for trial word. \ screen 27 \ tone control ... HEX LABEL 'TONE-ON/OFF ( -- addr) ' BIN# >BODY ) AX MOV ' TONEON >BODY ) AX CMP 1 L# JNE 'SYNCPULSE # CALL 01 # BX MOV 'PSET # CALL RET 1 L: ' TONEOFF >BODY ) AX CMP 2 L# JNE 01 # BX MOV 'PRESET # CALL 2 L: RET C; \ for debugging use ... \ CODE TONE-ON/OFF ( --) 'TONE-ON/OFF # CALL NEXT C; \ \ the Forth equivalent is ... \ : TONE-ON/OFF ( --) \ BIN# TONEON = \ IF 1 PSET THEN \ BIN# TONEOFF = \ IF 1 PRESET THEN ; DECIMAL \ screen 28 \ light control ... HEX LABEL 'LIGHT-ON/OFF ( -- addr) ' BIN# >BODY ) AX MOV ' LTON >BODY ) AX CMP 1 L# JNE 'SYNCPULSE # CALL 02 # BX MOV 'PSET # CALL RET 1 L: ' LTOFF >BODY ) AX CMP 2 L# JNE 02 # BX MOV 'PRESET # CALL 2 L: RET C; \ for debugging use ... \ CODE LIGHT-ON/OFF ( --) 'LIGHT-ON/OFF # CALL NEXT C; \ \ the Forth equivalent is ... \ : LIGHT-ON/OFF ( --) \ BIN# LTON = \ IF 2 PSET THEN \ BIN# LTOFF = \ IF 2 PRESET THEN ; DECIMAL \ screen 29 \ air control ... HEX LABEL 'AIR-ON/OFF ( -- addr) ' BIN# >BODY ) AX MOV ' AIRON >BODY ) AX CMP 1 L# JNE ( use shkon/off as sync) 08 # BX MOV 'PSET # CALL RET 1 L: ' AIROFF >BODY ) AX CMP 2 L# JNE 08 # BX MOV 'PRESET # CALL 2 L: RET C; \ for debugging use ... \ CODE AIR-ON/OFF ( --) 'AIR-ON/OFF # CALL NEXT C; \ \ the Forth equivalent is ... \ : AIR-ON/OFF ( --) \ BIN# AIRON = \ IF 8 PSET THEN \ BIN# AIROFF = \ IF 8 PRESET THEN ; DECIMAL \ screen 30 \ shock control ... HEX LABEL 'SHOCK-ON/OFF ( -- addr) ' BIN# >BODY ) AX MOV ' SHKON >BODY ) AX CMP 1 L# JNE 'SYNCPULSE # CALL 10 # BX MOV 'PSET # CALL RET 1 L: ' SHKOFF >BODY ) AX CMP 2 L# JNE 10 # BX MOV 'PRESET # CALL 2 L: RET C; \ for debugging use ... \ CODE SHOCK-ON/OFF ( --) 'SHOCK-ON/OFF # CALL NEXT C; \ \ the Forth equivalent is ... \ : SHOCK-ON/OFF ( --) \ BIN# SHKON = \ IF 16 PSET THEN \ BIN# SHKOFF = \ IF 10 PRESET THEN ; DECIMAL \ screen 31 \ pulse control ... \ pulse can be placed anywhere during the trial; it can be used to trigger an oscilloscope, for example, \ at an arbitrary time. LABEL 'PULSE-ON ( -- addr) ' BIN# >BODY ) AX MOV ' PULSEON >BODY ) AX CMP 1 L# JNE 'PULSE # CALL 1 L: RET C; \ screen 32 \ vectors ... LABEL 'NOOP RET C; \ used to nullify vectors. \ the following variables are used by the code word TRIAL for indirect calls to stimulus and data routines \ (see TRIAL): these 'vectors' are later resolved as stimulus control or data collection routines. VARIABLE VEC1 VARIABLE VEC2 VARIABLE VEC3 VARIABLE VEC4 VARIABLE VEC5 VARIABLE VEC6 : KIND ( n--) \ set trial type (used by NM display) and trial \ vector for data collection or noop (redundant with 'datsr): TYPE# ! #A/D @ #UNITS @ + IF 'DATSR ELSE 'NOOP THEN VEC1 ! ; \ screens 33 and 34 \ resolutions of vectors ... \ these resolutions set up the vectors for running the appropriate subroutines during the trial. HEX : NOTHING ( --) \ zeroes all vectors; a good starting point. \ use: nothing trial <return> 0 TYPE# ! 'NOOP ( n) DUP VEC1 ! DUP VEC2 ! DUP VEC3 ! DUP VEC4 ! DUP VEC5 ! ( n) VEC6 ! ; : TONE ( --) \ zeroes all vectors then sets vectors for tone trial. \ use: tone trial <return> NOTHING 1 KIND 'TONE-ON/OFF VEC2 ! 'PULSE-ON VEC3 ! ; : LT ( --) \ zeroes all vectors then sets vectors for light trial. \ use: lt trial <return> NOTHING 2 KIND 'LIGHT-ON/OFF VEC2 ! 'PULSE-ON VEC3 ! ; : AIR ( --) \ zeroes all vectors then sets vectors for air trial. \ use: air trial <return> NOTHING 8 KIND 'AIR-ON/OFF VEC3 ! 'SHOCK-ON/OFF VEC4 ! 'PULSE-ON VEC5 ! ; : TONE-AIR ( --) \ zeroes all vectors then sets vectors for tone-air trial. \ use: tone-air trial <return> NOTHING 9 KIND 'TONE-ON/OFF VEC2 ! 'AIR-ON/OFF VEC3 ! 'SHOCK-ON/OFF VEC4 ! 'PULSE-ON VEC5 ! ; : LT-AIR ( --) \ zeroes all vectors then sets vectors for light-air trial. \ use: lt-air trial <return> NOTHING 0A KIND 'LIGHT-ON/OFF VEC2 ! 'AIR-ON/OFF VEC3 ! 'SHOCK-ON/OFF VEC4 ! 'PULSE-ON VEC5 ! ; : TONE-LT ( --) \ zeroes all vectors then sets vectors for tone-light trial. \ use: tone-lt trial <return> NOTHING 03 KIND 'TONE-ON/OFF VEC2 ! 'LIGHT-ON/OFF VEC3 ! 'PULSE-ON VEC4 ! ; : TONE-LT-AIR ( --) \ zeroes all vectors then sets vectors for tone-light-air trial. \ use: tone-lt-air trial <return> NOTHING 0B KIND 'TONE-ON/OFF VEC2 ! 'LIGHT-ON/OFF VEC3 ! 'AIR-ON/OFF VEC4 ! 'SHOCK-ON/OFF VEC5 ! 'PULSE-ON VEC6 ! ; DECIMAL \ screen 35 \ trial documentation: \ \ the following is the approximate FORTH equivalent of TRIAL: \ trial ( --) \ lead ( start tape recorder) \ wait ( synchronize trial with clock or tape read) \ syncpulse \ 0 unit1 pc! 0 unit2 pc! ( reset unit cntrs) \ pts 1+ 0 do ( do the trial) \ i is bin# ( determine within trial timing) \ wait ( synchronize) \ vec1 vec2 vec3 vec4 vec5 vec6 ( do isi events) \ loop \ trail ( end tape recorder) \ \ screen 36 \ tape control ... : DELAY ( n --) \ n=1 will give about 50 msec for the original 4 MHz IBM-PC/XT computer.. \ 50000 DELAY is about 1 second. \ timing here depends upon the computer's speed: faster computers will mess up timing based on \ this word. 2500 * 0 ?DO LOOP ; : DELAYS DELAY ; VARIABLE BEFORE VARIABLE AFTER : SECONDS ( n--) \ an approximation, where n is the number of desired seconds of delay. \ since this uses "delay" which depends upon the computer's speed, faster computers will mess up this \ timing. this is only used for turning on and off a tape recorder, where the exact timing is not critical. \ nevertheless, faster computers than the original (4 MHz) will be messed up using this software delay. ( n) 0 ?DO 21 DELAYS LOOP ; : LEAD ( --) TAPE @ \ turn tape recorder on then wait 1.5 seconds. \ since "delay" depends upon the computer's speed, faster computers mess up the timing as noted \ above. IF 32 PSET BEFORE @ SECONDS THEN ; : TRAIL ( --) TAPE @ \ wait 1 second then turn tape off. \ since "delay" depends upon the computer's speed, faster computers mess up the timing as noted \ above. IF AFTER @ SECONDS 32 PRESET THEN ; : BELL ( --) \ more pleasant sounding than the IBM beep. \ since "delay" depends upon the computer's speed, faster computers mess up the timing as noted \ above. 97 PC@ DUP 3 OR 97 PC! 2 DELAYS 97 PC! ; : roadrunner ( --) \ "beep beep" is better for getting attention than a single "beep". \ since "delay" depends upon the computer's speed, faster computers mess up the road runner-like sound \ as noted above. bell 2 delay bell ; \ screens 37, 38, 39, 40 and 41 \ this is the actual trial word ... HEX CODE TRIAL ( --) \ the actual assembly code for running a single trial. [: LEAD ;] ( tape on) ' PTS >BODY ) CX MOV ( setup isi counter with PTS to collect) 0 # AX MOV AX ' BIN# >BODY ) MOV ( zero isi counter for data) TIME0 # DX MOV BIN ) AX MOV DX AL OUT ( setup isi tmr) AH AL MOV DX AL OUT PORTC # DX MOV 4 L: DX AL IN 10 # AL AND 4 L# JE ( sync) 5 L: DX AL IN 10 # AL AND 5 L# JNE 0 # LIVE ) CMP 6 L# JE ( online synchronization) PORTC # DX MOV ( offline synchronization) 8 L: \ 01 # AH MOV 16 INT A L# JNZ ( key?) DX AL IN 80 # AL AND 8 L# JNE ( +sync?) 9 L: \ 01 # AH MOV 16 INT A L# JNZ ( key?) DX AL IN 80 # AL AND 9 L# JE ( -sync?) PORTC # DX MOV a L: DX AL IN 10 # AL AND a L# JE ( sync) b L: DX AL IN 10 # AL AND b L# JNE 6 l: cli ( prevents interrupts) 00 # al mov ( zero all unit counters) time1 # dx mov dx al out dx al out time2 # dx mov dx al out dx al out time3 # dx mov dx al out dx al out time4 # dx mov dx al out dx al out 0f # al mov portc # dx mov dx al out ( clks 1,2,3,4 on) 00 # al mov dx al out ( clks 1,2,3,4 off) 0 # PCSPULSE ) CMP 1 L# JE ( jump if no PCS pulse) 'SYNCPULSE # CALL ( preCS sync pulse) 1 L: PORTC # DX MOV 3 L: DX AL IN 10 # AL AND 3 L# JE 2 L: DX AL IN 10 # AL AND 2 L# JNE \ Note: many trial execution vectors use BIN# as a counter. VEC1 ) CALL \ data (NM and units) collection VEC2 ) CALL \ tone or light VEC3 ) CALL \ airpuff VEC4 ) CALL \ shock (airpuff and syncpulse) VEC5 ) CALL \ etc. VEC6 ) CALL \ etc. ' BIN# >BODY ) INC ( update isi counter for data) 1 L# LOOP \ next set isi clock very fast until next trial; \ this is needed primarily for offline synchronization but \ is also used by online for simplicity of code. TIME0 # DX MOV 01 # AX MOV DX AL OUT ( setup isi tmr) AH AL MOV DX AL OUT STI ( allow interrupts again) [: TRAIL ;] ( finish recording) NEXT C; DECIMAL \ compile the following for manual delivery of trials, i.e., for debugging. \ : TRIAL ( --) \ \ use: tone trial <return>, etc. \ GET-BUFFER TRIAL ; \ screen 42 \ intertrial events documentation: \ \ the following screens are primarily concerned with words that occur between trials. \ these events include: \ trial graphics \ block graphics \ storage of the data to disk \ control of intertrial timing \ \ screen 43 \ data pointers ... : PRESERVE ( --) \ sets up the data pointers. \ see also the analysis code in the summary programs. RAM DROP 1K + ( n) #A/D @ 0 ?DO ( n) DUP I PTS * + I 2* (NMRV) + ! LOOP #UNITS @ 0 ?DO ( n) DUP I #A/D @ + PTS * + I 2* (UNITV) + ! LOOP ( n) DROP ; : ARCHIVE ( --) \ select space for data from ram (safety on) or from a disk buffer (safety off). \ set to display from ram 1k +. SAFETY @ IF RAM DROP ELSE TRL# @ 1- ( last trl) BLOCK THEN ( fr) RAM DROP 1K + ( to) 1K ( #) CMOVE PRESERVE ; : WORKSPACE ( -- buffer) \ select space for data from ram (safety on) or from a disk buffer (safety off). SAFETY @ IF RAM DROP ELSE TRL# @ BUFFER THEN ( n) DUP 1K ERASE ( n) ; : EASEL ( -- buffer) \ space used for block graphics. RAM DROP 1K 2* + ; : SAVEPOINTERS ( --) \ used for displaying block graphics. NMRV ( fr) ^NMR ( to) MAX#A/D 2* ( #) CMOVE UNITV ( fr) ^UNIT ( to) MAX#UNITS 2* ( #) CMOVE ; : RESTOREPOINTERS ( --) \ used for restoring display of trial graphics. ^NMR ( fr) NMRV ( to) MAX#A/D 2* ( #) CMOVE ^UNIT ( fr) UNITV ( to) MAX#UNITS 2* ( #) CMOVE ; \ screen 44 \ get-buffer ... : CLEARPOINTERS ( --) NMRV MAX#A/D 2* ERASE ^NMR MAX#A/D 2* ERASE UNITV MAX#UNITS 2* ERASE ^UNIT MAX#UNITS 2* ERASE ; : TRL&BLK ( -- trial block) TRL# @ 1- #T/BLK @ /MOD 1+ ; : ALLOCATE ( buffer --) #A/D @ MAX#A/D MIN ( to) 0 ( fr) ?DO DUP ( -- buf buf) I PTS * + ( add offset to buffer) I 2* NMRV + ! ( find position in nmrv table and store) LOOP #UNITS @ MAX#UNITS MIN 0 ( store <= 4 unit cntr chans) ?DO DUP ( -- buf buf) #A/D @ PTS * I PTS * + + ( add offset after a/d data) I 2* UNITV + ! ( find position in unitv table and store) LOOP DROP ; : GET-BUFFER ( --) CLEARPOINTERS WORKSPACE ALLOCATE SAVEPOINTERS USED ; \ screens 45 and 46 \ data filter ... CODE FILTERER ( adr # --) \ three-point sliding average of a/d data. used to smooth a/d data. \ in practice used only if filter switch is set to be on (see smooth). \ once filtered, the data is permanently altered -- it is no longer the original data. CX POP ( #) 3 # CX CMP 2 L# JB ( exit if < 3 elements) CX DEC CX DEC ( do not filter start or end) BX POP ( adr) 0 # AX MOV 0 [BX] AL MOV AX PUSH 1 L: AX POP ( 1st element this loop was middle last time) 0 # DX MOV ( initialize high byte) CLC 2 [BX] DL MOV DL AX ADD 1 [BX] DL MOV DL AX ADC ( total) DX PUSH ( save middle value for next loop) 3 # DX MOV DL DIV ( average) AL 1 [BX] BYTE MOV ( store back into array) BX INC ( next element) 1 L# LOOP 2 L: AX POP ( clear stack before forced exit) NEXT C; \ for documentation, the following is the forth equivalent of filterer: \ \ : FILTERER ( adr # --) \ 2- BOUNDS DUP C@ -ROT ( 1st pt) 1+ \ DO I C@ ( get 2nd data pt) TUCK ( save) \ I 1+ C@ ( get 3rd data pt) + + 3 / ( average) \ I C! \ LOOP DROP ; : SMOOTH ( --) \ filter a/d channels. \ this routine averages adjacent data found at addresses in table called nmrv in for length pts. \ NOTE: filtering destroys the original data. FILTER @ ( true if filtering is desired) IF #A/D @ MAX#A/D MIN ( to) 0 ( fr) ?DO I 2* NMRV + @ PTS FILTERER LOOP THEN ; \ screens 47, 48, 49 and 50 \ graphics primitives ... \ we needed to create our own graphics primitives to make drawings on the screeen. \ \ the following is the forth code for documentation purposes. the actual code used is the machine \ language code that follows. \ \ this screen is based on MicroMOTION's graphics file by MJT. \ \ HEX \ \ CREATE BIT-MASKS \ bit masks for dot position. \ 80 C, 40 C, 20 C, 10 C, 08 C, 04 C, 02 C, 01 C, \ \ : +/-DOT ( x y -- ^byte value mask) \ prepare dot at x,y. \ OVER 7 AND BIT-MASKS + C@ ( select bit mask) \ >R ( calc byte adr) DUP 1 AND 0<> ( odd?) 2000 AND \ SWAP 2/ 50 UM* DROP + SWAP 3 >SHIFT + ( ^byte) \ DUP B800 ( vseg) C@L R> ; \ \ : +DOT ( x y --) \ plots dot at x,y. \ +/-DOT OR SWAP B800 C!L ; \ \ : -DOT ( x y --) \ removes dot at x,y. \ +/-DOT TUCK OR SWAP NOT AND SWAP B800 ( vseg) C!L ; \ \ DECIMAL \ \ machine language adr/byte/bit ... HEX CREATE MASKS 80 C, 40 C, 20 C, 10 C, 8 C, 4 C, 2 C, 1 C, CODE +/-DOT ( x y -- offset value mask) B800 # AX MOV AX DS MOV AX POP ( y) AX SHR ( odd/even) PUSHF CLC 50 # BL MOV ( byts/ln) BL BYTE MUL POPF 1 L# JNC CLC 2000 # AX ADD ( odd rows) 1 L: BX POP ( x) BX DX MOV 7 # DX AND ( DX = within byte) BX SHR BX SHR BX SHR CLC AX BX ADD BX PUSH ( offset) 0 [BX] AL BYTE MOV AH AH XOR AX PUSH ( value) CS AX MOV AX DS MOV ( restore) DX BX MOV MASKS [BX] AL BYTE MOV AH AH XOR AX PUSH ( mask) NEXT C; CODE +/DOT ( offset value mask --) B800 # AX MOV AX DS MOV DX POP ( mask) AX POP ( value) DL AL OR ( answer) BX POP ( offset) AL 0 [BX] BYTE MOV ( store) CS AX MOV AX DS MOV ( restore) NEXT C; : +DOT ( x y --) +/-DOT +/DOT ; CODE -/DOT ( offset value mask --) B800 # AX MOV AX DS MOV DX POP ( mask) AX POP ( value) FF # DL XOR DL AL AND ( answer) AH AH XOR BX POP ( offset) AL 0 [BX] BYTE MOV ( store) CS AX MOV AX DS MOV ( restore) NEXT C; : -DOT ( x y --) +/-DOT -/DOT ; DECIMAL \ graphics eraser primitive ... HEX CODE GR-ERASE ( starting-line #lines --) \ #lines is 2 mod. \ this version stores 0 as a word string using DI. \ STOS has protection for CX = 0. CLD for string increment. B800 # AX MOV AX ES MOV 28 # BX MOV ( words/line) AX POP ( #lines) AX SHR CLC BX MUL AX CX MOV ( length in words) AX POP ( starting line) AX SHR 50 # BX MOV ( bytes/ln) CLC BX MUL AX DI MOV ( starting word) CX PUSH ( copy) AX PUSH 0 # AX MOV CLD REP WORD STOS AX POP CLC 2000 # ( odd/even offset) AX ADD AX DI MOV CX POP 0 # AX MOV CLC REP WORD STOS CS AX MOV AX ES MOV ( restore ES) NEXT C; DECIMAL \ screens 51 and 52 \ line drawing ... : WITHINLIMITS ( x1 y1 x2 y2 -- x1 y1 x2 y2 ) 2 0 DO 3 ROLL 0 MAX 599 MIN 3 ROLL 0 MAX 199 MIN LOOP ; 2VARIABLE M \ slope of the line: y2-y1 and x2-x1 : SLOPE ( x1 y1 x2 y2 --) 3 PICK 2 PICK U< \ m = y2-y1 / x2-x1 IF 2SWAP THEN ( rank order by x's) ROT SWAP - ( x2 x1 y2-y1 ) -ROT - ( y2-y1 x2-x1 ) M 2! ; VARIABLE B : Y-INTERCEPT ( x y --) \ b = y-mx SWAP M 2@ */ - B ! ; : +Y! ( x --) \ y = mx + b DUP M 2@ */ B @ + +DOT ; : +X! ( x y --x) \ y-intercept of the line \ x = y-b / m OVER SWAP +DOT ; : ORDERED ( n1 n2 --n1 n2|n2 n1) 2DUP > IF SWAP THEN 1+ SWAP ; : +XLINE ( x1 x2 --) ORDERED ?DO I +Y! LOOP ; : +YLINE ( y1 y2 --) ORDERED 1+ ?DO I B @ - M 2@ SWAP */ I +DOT LOOP ; : +VERTICAL ( x1 x2 y1 y2 --n1 n2) ORDERED ?DO I +X! LOOP ; : PREP ( x1 y1 x2 y2 -- x1 x2 y1 y2 ) WITHINLIMITS 2OVER 2OVER SLOPE 2OVER Y-INTERCEPT ROT SWAP ; : +LINE ( x1 y1 x2 y2 --) PREP 2OVER = IF +VERTICAL 2DROP EXIT THEN M 2@ SWAP ABS SWAP > IF +YLINE 2DROP ELSE 2DROP +XLINE THEN ; 2VARIABLE COORDINATES : DOT ( x y --) 2DUP COORDINATES 2! +DOT ; : LINE ( x y --) \ draw line from last DOT or LINE to x,y. 2DUP COORDINATES 2@ 2SWAP WITHINLIMITS +LINE COORDINATES 2! ; : BLOT ( y --) 1+ 0 OVER AT 8 * 8 GR-ERASE ; : TX ( --) 2 VMODE ; : GR ( --) 6 VMODE ; : MIXED ( --) NOOP ; \ screen 53 \ graphics display variables, constants, values ... variable up 21 up ! 1 constant xscalex 1 constant xscale/ vlu a/dx vlu a/d/ 1 constant a/d1x 3 constant a/d1/ 1 constant a/d2x 3 constant a/d2/ 1 constant a/d3x 3 constant a/d3/ 1 constant a/d4x 3 constant a/d4/ \ x axis multiplier. \ x axis divisor. \ y axis generic a/d multiplier. resolve as a/d1x etc. \ y axis generic a/d divisor. resolve as a/d1/ etc. \ y axis a/d channel 1 multiplier. \ y axis a/d channel 1 divisor. \ y axis a/d channel 2 multiplier. \ y axis a/d channel 2 divisor. \ y axis a/d channel 3 multiplier. \ y axis a/d channel 3 divisor. \ y axis a/d channel 4 multiplier. \ y axis a/d channel 4 divisor. vlu unitx vlu unit/ 1 constant unit1x 1 constant unit1/ 1 constant unit2x 1 constant unit2/ 1 constant unit3x 1 constant unit3/ 1 constant unit4x 1 constant unit4/ 28 constant ceiling \ y axis generic unit multiplier. resolve as unit1x etc. \ y axis generic unit divisor. resolve as unit1/ etc. \ y axis unit channel 1 multiplier. \ y axis unit channel 1 divisor. \ y axis unit channel 2 multiplier. \ y axis unit channel 2 divisor. \ y axis unit channel 3 multiplier. \ y axis unit channel 3 divisor. \ y axis unit channel 4 multiplier. \ y axis unit channel 4 divisor. \ maximum height of display for units. 639 constant xmaximum 199 constant ymaximum \ maximum x axis screen dimension. \ maximum y axis screen dimension. 0 constant zero variable #lfs 3 #lfs ! : lfs ( n --) ?dup \ line feed n times 8 dots. if at? >r >r 0 24 at 0 ?do cr loop r> r> at then ; : lf 1 lfs ; \ one line feed = 8 dots. \ screen 54 \ case ... \ forth does not have the handy if-case-then structure for making selections, so we gave it one. : of ( --) noop ; : cases ( index max#cases --) \ where 1 <= index <= max#cases. \ indices out of range are not executed. \ use: 2 of 3 cases case1 case2 case3 endcase ... swap ?dup if 2dup swap 1+ u< if 1- over 1- min ( limit) 2* ip@ + @ execute ( pick) else drop then then 1+ 2* ip> + >ip ( skip by max#cases to endcase) ; : endcase ( --) noop ( use bell for debugging) ; \ screens 55, 56, 57, 58 and 59 \ continue graphics ... : yscale ( yvalue -- yvalue') 200 swap - ( needed to correct for y offset) a/dx a/d/ */ ( scale) 200 swap - ( needed to correct for y offset) up @ - ( set up from bottom of screen) 0 max ymaximum min ( limit to within range) ; variable dots dots on : a/d-graph ( adr --) \ nictitating membrane type data. 0 pts xmaximum min bounds ?do i ( xvalue) xscalex xscale/ */ over i + c@ ( yvalue) yscale i 0<> if ( all other data points) dots @ if dot else line then else ( 1st data point always a dot) dot then loop drop ; : makeline ( y-position --) 0 over dot pts xscalex xscale/ */ swap line ; : a/dmark ( n --) xscalex xscale/ */ ymaximum 2dup 2dup dot 10 - line swap 1+ swap 2dup dot 10 - line ; : a/dmarks ( --) ymaximum 5 - ( i.e., 5 from bottom) makeline 1 type# @ and if toneon a/dmark then 2 type# @ and if lton a/dmark then 8 type# @ and if shkon a/dmark then ; : latency ( index limit data -- latency) rot area * + @ ?dup if nip bin @ 1000 / / toneon + then xscalex xscale/ */ ; : position ( index xposition -- index xposition yposition) over area * info + @ 10 + ; variable datadots datadots on : draw-data ( index --) dup shkon info 8 + latency position yscale dot dup pts info 14 + latency position yscale dot dup pts info 16 + latency position yscale dot drop ; : scalea/d1 a/d1x is a/dx a/d1/ is a/d/ ; : scalea/d2 a/d2x is a/dx a/d2/ is a/d/ ; : scalea/d3 a/d3x is a/dx a/d3/ is a/d/ ; : scalea/d4 a/d4x is a/dx a/d4/ is a/d/ ; : draw-a/d ( --) 0 #a/d @ bounds ?do i 1+ of 4 cases scalea/d1 scalea/d2 scalea/d3 scalea/d4 endcase i 2* (nmrv) + @ a/d-graph datadots @ if i draw-data then i #a/d @ 1- <> if #lfs @ lfs then loop #a/d @ if a/dmarks #lfs @ lfs then ; : u-graph ( adr --) 0 pts xmaximum min bounds ?do i xscalex xscale/ */ over i + c@ ( get value) unitx unit/ */ ( scale) ceiling min ( limit height) ymaximum swap - ( set to bottom of screen) zero max ymaximum min ( limit to within range) dup ymaximum <> if dot i xscalex xscale/ */ ymaximum 1- line else 2drop then loop drop ; : unitmark ( n --) xscalex xscale/ */ ymaximum 2dup -dot swap 1+ swap -dot ; : unitmarks ( --) ymaximum ( i.e., on bottom) makeline 1 type# @ and if toneon unitmark then 2 type# @ and if lton unitmark then 8 type# @ and if shkon unitmark then ; : scaleunit1 unit1x is unitx unit1/ is unit/ ; : scaleunit2 unit2x is unitx unit2/ is unit/ ; : scaleunit3 unit3x is unitx unit3/ is unit/ ; : scaleunit4 unit4x is unitx unit4/ is unit/ ; : draw-units ( --) 0 #units @ bounds ?do i 1+ of 4 cases scaleunit1 scaleunit2 scaleunit3 scaleunit4 endcase i 2* (unitv) + @ u-graph unitmarks #lfs @ lfs loop ; : draw ( --) draw-a/d draw-units 2 lfs ; \ screen 60 \ measurements documentation: \ \ the INFO array (65 bytes long) holds the following information: \ NICTITATING MEMBRANE: \ 00 PCS baseline \ 02 PCS absolute deviation \ 04 CS peak amplitude \ 06 CS cumulative area \ 08 CS peak latency \ 10 UCS peak amplitude \ 12 UCS cumulative area \ 14 UCS peak latency \ 16 1/2mm response latency \ 30 PCS positive area? \ UNITS: \ 18 PCS units 1 \ 20 CS units 1 \ 22 UCS units 1 \ 24 PCS units 2 \ 26 CS units 2 \ 28 UCS units 2 \ the last byte is made a blank for use by the summary routines. \ screen 61 \ basic calculations variable ptr \ tmp pointer for correct nm from (nmrv). : BASELINE ( --) \ calculate the average NM in the preCS period. 0 ( dummy) TONEON 0 ?DO PTR @ 2* (NMRV) + @ I + C@ + LOOP ( dummy | n) TONEON U/ IS BLN ; : +UNITS ( adr 0 to fr --n) \ add units. ?DO OVER I + C@ + LOOP ; : >area ( adr -- adr+o) \ skip in INFO array. ptr @ area * + ; : stuff ( i -- value) ptr @ 2* (nmrv) + @ + c@ bln - negate ; : STAT ( to from -- area ampl) \ calculate area and amplitude statistics in given range. 2dup 0 -rot 2dup 0 -rot ( to fr 0 to fr 0 to fr) ?do i stuff max loop ( ampl) >r ?do i stuff 0 max + loop ( area) >r ?do [ info 16 + ] literal >area @ 0<> if leave then i stuff critlevel @ 1- rescale > if i toneon - [ info 16 + ] literal >area ! leave then loop r> r> ; ( latency) \ screen 62 \ PreCS trial measurements ... : PCS-MEAS ( --) \ calculate PCS period statistics. 0 0 ( dummy-arguments) TONEON ( to) bin @ 100 / baseln @ swap >r 10 um* r@ um/mod swap r@ 2/ + r> / + ( see ROUND in SUMMARY program) ( from) ?DO PTR @ 2* (NMRV) + @ I + C@ BLN - SWAP OVER ABS MAX >R DUP 0< IF DROP 0 THEN + R> LOOP [ INFO 2+ ] LITERAL >AREA ! ( absolute PCS deviation from baseline) [ INFO 30 + ] LITERAL >AREA ! ( partiall area of positive deviation from baseline) BLN [ INFO ] LITERAL >AREA ! ; ( average baseline in whole PCS period) : PCS-UNITS ( a -- a n) \ calculate total # units in PCS period. 0 TONEON 0 +UNITS ; \ screen 63 \ CS trial measurements ... : CS-MEAS ( --) \ calculate CS period statistics. SHKON ( to) TONEON ( from) STAT ( do the calculations) [ INFO 4 + ] LITERAL >AREA ! ( maximum amplitude) [ INFO 6 + ] LITERAL >AREA ! ( cumulative area) SHKON ( to) TONEON ( from) ?DO ( find the latency of the peak amplitude) PTR @ 2* (NMRV) + @ I + C@ BLN - NEGATE [ INFO 4 + ] LITERAL >AREA @ = IF I TONEON - [ INFO 8 + ] LITERAL >AREA ! LEAVE THEN LOOP ; : CS-UNITS ( a -- a n) \ calculate total # units in CS period. 0 SHKON TONEON +UNITS ; \ screen 64 \ UCS trial measurements ... : UCS-MEAS ( --) \ calculate the UCS period statistics. PTS ( to) SHKON ( from) STAT ( do the calculations) [ INFO 10 + ] LITERAL >AREA ! ( maximum amplitude) [ INFO 12 + ] LITERAL >AREA ! ( cumulative area) PTS ( to) SHKON ( from) ?DO ( find the latency of the peak amplitude) PTR @ 2* (NMRV) + @ I + C@ BLN - NEGATE [ INFO 10 + ] LITERAL >AREA @ = IF I TONEON - [ INFO 14 + ] LITERAL >AREA ! LEAVE THEN LOOP ; : UCS-UNITS ( a -- a n) \ calculate total # units in UCS period. 0 PTS SHKON +UNITS ; \ screens 64 and 65 \ routines to correct for on-line calculations for amplitude and time ... \ \ this became necessary when human eyelid studies had to become calibrated for each individual \ subject. basically, this allows the user to enter a value (maxeye) for the normal eyelid opening of the \ subject. this information is then used to calibrate the a/d readings in mm. : fixampl ( adr i --) 32 * + dup @ descale swap ! ; : fixlat ( adr i --) 32 * + dup @ bin @ 10 / um* 50 s>d d+ 100 um/mod nip swap ! ; : translate ( --) \ scale amplitude and time in info array 0 #a/d @ bounds ?do [ info ] literal i fixampl \ PCS baseline [ info 2 + ] literal i fixampl \ PCS deviation [ info 4 + ] literal i fixampl \ CS ampl [ info 6 + ] literal i fixampl \ CS area (ampl * bin) \ [ info 6 + ] literal i fixlat \ no easy way to do ampl * ms [ info 8 + ] literal i fixlat \ CS peak lat \ [ info 10 + ] literal i fixampl \ US ampl [ info 12 + ] literal i fixampl \ US area (ampl * bin) [ info 12 + ] literal drop \ no easy way to do ampl * ms [ info 14 + ] literal i fixlat \ US peak lat [ info 16 + ] literal i fixlat \ 1/2 mm lat; depends on maxeyeloop ; \ screen 66 \ measurements ... : MEASURE ( --) \ max#a/d & up to 2 units are calcd & stored. CLEAN ( clean data array for new trial information) \ nictitating membrane analysis: #A/D @ MAX#A/D MIN ( to) 0 ( fr) ?DO I PTR ! BASELINE PCS-MEAS CS-MEAS UCS-MEAS LOOP translate \ unit channel(s) analysis(es): #UNITS @ 2 ( NOTE: this is not MAX#UNITS) MIN 0 ?DO (UNITV) I 2* + @ PCS-UNITS [ INFO 18 + ] LITERAL I 6 * + ! CS-UNITS [ INFO 20 + ] LITERAL I 6 * + ! UCS-UNITS [ INFO 22 + ] LITERAL I 6 * + ! DROP LOOP ; \ screens 67, 68, 69, 70 and 71 \ auxilliary memory management ... 2 VALUE SEGMENT \ available segments: 0 (DOS, FORTH), 1, 2, 3. \ WARNING: programming may go as high as $1600, so don't use segment 1. 0 VALUE AUXILLIARY ( used by summary program) HEX CODE >A ( from to # --) \ move to "auxilliary" memory area. CX POP ( #) BX POP AX POP ES PUSH DS PUSH SI PUSH DI PUSH BP PUSH \ save registers. AX SI MOV ( from DS+SI) BX DI MOV ' SEGMENT >BODY ) AX MOV 1K 4 * # BX MOV BX BYTE MUL AX ES MOV ( to ES+DI) CLD ( increment up) REP BYTE MOVS BP POP DI POP SI POP DS POP ES POP \ restore registers. NEXT C; CODE A> ( from to # --) \ move from "auxilliary" memory area. CX POP ( #) BX POP AX POP ES PUSH DS PUSH SI PUSH DI PUSH BP PUSH BX DI MOV ( to ES+DI) AX SI MOV ' SEGMENT >BODY ) AX MOV 1K 4 * # BX MOV BX BYTE MUL AX DS MOV ( from DS+SI) CLD ( increment up) REP BYTE MOVS BP POP DI POP SI POP DS POP ES POP NEXT C; DECIMAL : CLEANAUX ( --) \ erase the "auxilliary" memory area. RAM 1K U< IF EXIT THEN 1K ERASE 2 IS SEGMENT 64 0 DO RAM DROP I 1K * 1K >A LOOP 3 IS SEGMENT 64 0 DO RAM DROP I 1K * 1K >A LOOP ; \ moving trial data to or from auxilliary memory ... : TRIAL>AUX ( n --) \ n = trial number = trl# @ . 2 IS SEGMENT #A/D @ MAX#A/D MIN #units @ max#units min 1 min max ( to) 0 ( fr) ?DO INFO AREA I * + ( from) OVER 1+ #TRLS @ I * + AREA * ( to) AREA ( #) >A LOOP DROP ; : AUX>TRIAL ( n --) \ n = trial number = trl# @ . \ 1 aux>trial returns results of trial number 1 (first trial). 2 IS SEGMENT #A/D @ MAX#A/D MIN #units @ max#units min 1 min max ( to) 0 ( fr) ?DO DUP 1+ #TRLS @ I * + AREA * ( from) INFO AREA I * + ( to) AREA ( #) A> LOOP DROP ; \ moving block data to and from auxilliary memory ... : BLOCK>AUX ( n --) \ n = block number = trl&blk nip 1-. 2 IS SEGMENT INFO ( fr) #TRLS @ AREA * #A/D @ * 64 + ( end of trial data) ROT 1- AREA * + ( to) AREA ( #) >A ; : AUX>BLOCK ( n --) \ n = block number = trl&blk nip 1-. \1 aux>block returns results of block number 1 (first block). 2 IS SEGMENT #TRLS @ AREA * #A/D @ * 64 + ( find end of trial data) SWAP 1- AREA * + ( fr) INFO ( to) AREA ( #) A> ; : GRAPH>AUX ( n --) \ n = block number = trl&blk nip 2-. 3 IS SEGMENT EASEL ( from) SWAP 1- 1K * ( to) 1K ( #) >A ; : AUX>GRAPH ( n --) \ n = block number = trl&blk nip 2-. \1 aux>graph returns results of block number 1 (first block). 3 IS SEGMENT 1- 1K * ( from) EASEL ( to) 1K ( #) A> ; \ screen 72 \ moving the header/comment management ... : HEADER:DISK>AUX ( --) SAFETY @ NOT IF 2 IS SEGMENT 0 BLOCK 64 + ( fr) 0 ( to) 64 ( #) >A THEN ; : HEADER:INFO>DISK ( --) SAFETY @ NOT IF 0 BLOCK 2+ 1K 2- BLANK ( don't erase trl# in first word) INFO ( fr) 0 BLOCK 64 + ( to) 64 ( #) CMOVE BL 0 BLOCK 64 + 65 + C! UPDATE THEN ; : HEADER:AUX>DISK ( --) SAFETY @ NOT IF 2 IS SEGMENT 0 BLOCK 2+ 1K 2- BLANK ( don't erase trl# in first word) 0 ( fr) 0 BLOCK 64 + ( to) 64 ( #) A> UPDATE THEN ; \ screens 73 and 74 \ block graphics ... VARIABLE COUNTER : ADDRESS ( --adr|0) #A/D @ IF (NMRV) @ EXIT THEN #UNITS @ IF (UNITV) @ EXIT THEN 0 ; : TOTALPTS ( -- cnt) #A/D @ #UNITS @ + PTS * ; : +GRAPH ( --) \ add current trial to graphics accumulator for later averaging. ADDRESS TOTALPTS 0 ?DO DUP I + C@ ( value) EASEL I 2* + ( adr) +! LOOP DROP 1 COUNTER +! ; : -GRAPH ( --) \ remove current trial from graphics accumulator to prevent later averaging. \ use this only if the trial has already been added using +graph and you now want to remove it. ADDRESS TOTALPTS 0 ?DO DUP I + C@ NEGATE EASEL I 2* + +! LOOP DROP -1 COUNTER +! ; : /GRAPHS ( --) \ divide graphics accumulator by number of added trials to yield the averaged block graphics. \ average and recompress a/ds: #A/D @ MAX#A/D MIN PTS * ( to) 0 ( fr) ?DO EASEL I 2* + @ ( dividend) COUNTER @ ( dvsr) / ( quot) EASEL I + ( adr) C! LOOP \ recompress units: #A/D @ MAX#A/D MIN PTS * ( fr) DUP #UNITS @ PTS * + ( to) SWAP ?DO EASEL I 2* + @ EASEL I + C! LOOP ; : WIPE ( --) \ erase area used to add and average graphics. EASEL LIMIT OVER - ERASE 0 COUNTER ! ; \ screen 75 \ recognize and average only trials of the correct type ... CREATE TYPETABLE 0 C, 1 C, 9 C, 2 C, 10 C, 8 C, 11 C, \ table values are bit patterns on PORTB port. \ table positions are the trial types (see below). VARIABLE BLOCKTYPE# : BLKSUM(A) ( n--) TYPETABLE + C@ BLOCKTYPE# C! ; : BLKSUM(B) ( n--) TYPETABLE + C@ BLOCKTYPE# 1+ C! ; \ blksum(a) and blksum(b), if both active, will COMBINE into one summary picture. \ table indexes (i.e., n): \ 0 = nothing trial \ 1 = tone trial \ 2 = tone-air trial \ 3 = light trial \ 4 = light-air trial \ 5 = air trial \ 6 = tone-light-air trial \ screen 76 \ blockdisplay ... : DRAW-A/D-BLK ( --) 0 #a/d @ bounds ?do i 1+ of 4 cases scalea/d1 scalea/d2 scalea/d3 scalea/d4 endcase i pts * easel + a/d-graph i #a/d @ 1- <> if #lfs @ lfs then loop #a/d @ if blocktype# ( 1+) c@ type# ! a/dmarks #lfs @ lfs then ; : DRAW-Unit-BLK ( --) 0 #units @ bounds ?do i 1+ of 4 cases scaleunit1 scaleunit2 scaleunit3 scaleunit4 endcase #a/d @ + pts * easel + u-graph unitmarks #lfs @ lfs loop ; : SQUARE ( --) \ a square in the upper left of the graphics display during the run indicates that the picture being shown is \ a block average. 0 0 DOT 0 6 LINE 10 6 LINE 10 0 LINE 0 0 LINE ; : BLOCKDISPLAY ( --) PICTURE @ if dark counter @ 0 <> IF DRAW-A/D-BLK DRAW-Unit-BLK THEN 2 lfs square THEN ; \ screen 77 \ blockmeasurements ... : BLOCKMEASUREMENTS ( --) #A/D @ 0 ?DO (NMRV) I 2* + @ LOOP #UNITS @ 0 ?DO (UNITV) I 2* + @ LOOP PTR @ 1 PTR ! #A/D @ 1 #A/D ! EASEL ALLOCATE NMRV ( fr) (NMRV) ( to) #A/D @ 2* ( #) CMOVE UNITV ( fr) (UNITV) ( to) #UNITS @ 2* ( #) CMOVE MEASURE #A/D ! TRL&BLK NIP 1- BLOCK>AUX PTR ! #UNITS @ 0 ?DO (UNITV) #UNITS @ 2* + I 1+ 2* - ! LOOP #A/D @ 0 ?DO (NMRV) #A/D @ 2* + I 1+ 2* - ! LOOP ; \ screen 78 \ blocktrials ... : BADTRIAL ( -- flag | true flag=bad, false flag=good) \ bad baseline movement? bada/d @ INFO 2+ @ < ( tf=bad, =pcs movement) INFO 16 + @ DUP IF badtime @ < THEN ( tf=bad,alpha resp) OR ; : BLOCKTRIALS ( --) BLOCKTYPE# c@ TYPETABLE C@ <> IF ( true if not equal to "nothing" trial) TYPE# @ BLOCKTYPE# C@ = TYPE# @ BLOCKTYPE# 1+ C@ = OR BADTRIAL NOT AND ( +graph if correct type and not bad-trial) IF +GRAPH THEN TRL&BLK DROP 0= ( true if at the end of a block) IF /GRAPHS TRL&BLK NIP 1- GRAPH>AUX BLOCKMEASUREMENTS BLOCKDISPLAY WIPE THEN THEN ; \ screen 79 \ iti timing ... \ IBM clock (55 ms interrupt) is used for intertrial interval. \ because the IBM clock is used for timing, TSR (terminate stay resident) programs that interfer with \ the RAM location of Forth and can interfer with timing, and vice versa, the Forth program can mess \ up other programs and the computer time. HEX : @TIME ( -- du) \ fetch time. \ returns cumulative 55 ms of computer time 46C 0 C@L 46D 0 C@L COMBINE ( lsb) 46E 0 C@L 46F 0 C@L COMBINE ( msb) ; : !TIME ( du --) \ store time. \ sets computer timer SPLIT 46F 0 C!L 46E 0 C!L ( msb) SPLIT 46D 0 C!L 46C 0 C!L ( lsb) ; DECIMAL \ screen 80 \ code to print time to screen (.time) ... VARIABLE DAWN \ elapsed time at start of trial. VARIABLE DUSK \ elapsed time to end of trial. : ELAPSED ( -- n) \ converts @TIME into cumulative seconds. @TIME 1000 55 / UM/MOD NIP ; ( an approximation) : LAP ( --n) \ count down to end of trial. DUSK @ ELAPSED - ; : .TIME ( t1 -- t1|t2) \ prints new time to the second. LAP 2DUP SWAP U< IF NIP 0 24 AT DUP 0< IF DROP 0 THEN ( prevent negative) DUP 0 2 D.R ELSE DROP THEN ; \ screen 81 \ timing the intertrial interval (start, ending) : START ( --) 0 0 !time ELAPSED DAWN ! \ elapsed time at start of trial. 1 TRL# +! ; ( next trial will have this number) VARIABLE SEED @TIME DROP SEED ! : RANDOM ( -- n) SEED @ 65421 * 1+ DUP SEED ! RANGE @ UM* NIP ; : ENDING ( --) \ calc elapsed time to end of trial. MINIMUM @ RANDOM + DAWN @ + DUSK ! ; \ screen 82 \ code to display information at the bottom of the screen during the session (formletter etc.) ... : FORMLETTER ( --) \ print unchanging part of iti display. \ print #s for a/d channels: #A/D @ MAX#A/D MIN ( to) 0 ( fr) ?DO I 15 * 21 AT I 1+ 0 1 D.R ASCII > EMIT LOOP \ display nictitating membrane data titles: #A/D @ 1 = 1 TRL# @ U< AND IF 0 22 AT ." PCSamp" 20 22 AT ." CSamp" 40 22 AT ." UCSamp" 59 22 AT ." Latency" THEN \ display for iti count down: 2 24 AT ." of " DUSK @ DAWN @ - 0 2 D.R ." secs until trial " TRL# @ 1- #T/BLK @ /MOD 1+ 0 2 D.R ( .block #) ." , " 1+ U. ( .trial #) ascii ( pad c! trl# @ 0 <# #s #> tuck pad 1+ swap cmove ascii ) over pad + 1+ c! pad swap 2+ type 80 at? drop - 2- spaces ; \ screens 83, 84 and part of 85 \ data displays ... : ANALOG ( --) \ display analog channels. \ used to sample the a/d converters during the intertrial interval. #A/D @ MAX#A/D MIN ( to) 0 ( from) ?DO 0 '0808WR I + PC! ( start) BEGIN PORTC PC@ 32 AND UNTIL I 15 * 3 + 21 AT '0808RD I + PC@ ( read) 0 3 D.R ( .data) LOOP ; : FAST ( --) \ sample nm, delay. 2 DELAY ANALOG 2 DELAY ANALOG ; : PRINT ( info-index --) \ print formatted data from info array. INFO + @ 0 <# # ASCII . HOLD #S #> 6 OVER - SPACES TYPE ; : 1AMPLITUDE ( --) #A/D @ \ display a/d data from last trial. IF 0 23 AT 2 PRINT ( PCSamp) 20 23 AT 4 PRINT ( CSamp) 40 23 AT 10 PRINT ( UCSamp) 60 23 AT 16 INFO + @ 0 6 D.R ( ms) THEN ; \ conditions display ... : AMPLITUDES ( --) #A/D @ MAX#A/D MIN ( to) 0 ( fr) ?DO I 15 * 3 + 22 AT 4 I AREA * + PRINT ( CSamp) I 15 * 3 + 23 AT 10 I AREA * + PRINT ( UCSamp) LOOP ; \ display last trial's a/d. : AMPLITUDE ( --) #A/D @ 1 = IF 1AMPLITUDE ELSE AMPLITUDES THEN ; : STATUS ( --) \ current NM and previous trial statistics. PAUSE @ IF 70 21 AT ." Pause" ELSE 70 21 AT 5 SPACES THEN SAFETY @ IF 60 21 AT ." Safety" ELSE 60 21 AT 6 SPACES THEN ; : ERADICATE ( from to --) \ erase range of lines. -CURSOR 1+ SWAP ?DO I BLOT LOOP +CURSOR ; \ screen 85 \ interpreter ... \ \ this function allows execution of forth commands entered on the keyboard during the session. : INTERPRETER ( --) \ access Forth interpretter. \ this allows executing Forth commands during a run of trials. \ most often used to change the intertrial interval while running. 20 22 ERADICATE ( erase bottom 4 lines) BEGIN -CURSOR 20 BLOT +CURSOR ( erase message line) " Interpreter: " DIALOG DUP WHILE 21 22 ERADICATE ( erase bottom 4 lines) 0 21 AT ( position for response to interpretter) EVAL REPEAT 2DROP 20 22 ERADICATE -CURSOR ; \ screen 86 \ menu selection ... : MENU ( --) \ main entry point for setting parameters & to run. CLEAR RESET FLUSH PROGRAMDRIVE DV " MENUS" FILE DARK COPYRIGHT clean 10 DELAYS 5 LOAD ; \ screen 87 \ inhibiter ... defer stub ' noop is stub \ 'stub' is set aside for future function in the intertrial interval (see inhibitor next). \ by defining a new word and resolving stub to that word (' newword is stub') a keypress during the \ iti could be interpreted to perform a certain action rather than causing a pause in trials. \ by using stub as a deferred definition, we do not have to compile the new function at this time -\ the new function can be defined and changed any time in the future. \ used elsewhere in this code, this strategy is a programming forethought adding greater flexibility. \ this strategy is notoriously difficult to decode and understand when encountered. \ see also 'vec1', 'vec2' etc. above and 'user' below. : INHIBITER ( --) \ interprets some keys for action or pauses between trials. \ most often used to pause, for example to check the subject or inject a drug. KEY? IF @TIME ( save) KEY DUP CONTROL C = IF PICTURE @ IF TX THEN DARK HEADER:DISK>AUX FLUSH ." Return PROGRAM disk to drive " programdrive type +CURSOR KEY DROP MENU ELSE DUP ESC# = IF DROP +CURSOR INTERPRETER -CURSOR FORMLETTER AMPLITUDE ELSE stub ( for adding keys during iti) DROP 72 24 ( n1 n2) ( put message at end of line) BEGIN ANALOG 2DUP AT ." inhibit" FAST FAST FLASH @ IF 2DUP AT 7 SPACES THEN FAST FAST KEY? UNTIL ( n1 n2) AT 7 SPACES KEY DROP THEN THEN 0 24 AT !TIME ( rstr) DUP 0 2 D.R ( .tm) THEN ; \ screen 88 \ timing between trials ... : TIMING ( --) \ do intertrial timing and display. ENDING FORMLETTER LAP ( seconds) BEGIN .TIME ELAPSED DUSK @ u< WHILE ANALOG STATUS INHIBITER 2 DELAYS REPEAT .TIME ( seconds) DROP INHIBITER START ; \ screen 89 \ numeric display ... : .TITLE ( n --) \ print the title for intertrial display. 0 0 AT ." TRL PCS PCS CS CS CS UCS UCS " ." UCS LAT PU1 CU1 UU1 PU2 CU2 UU2 " 0 1 AT ." # base amp amp area lat amp area " ." lat crit pcs cs ucs pcs cs ucs " ; : .LINE ( n --) \ print data & title for intertrial numeric display. \ print the line of data: DUP 1+ 18 MIN ( select line at trl# @ 1+ and <= 18) 0 SWAP AT ( position cursor at line) 0 2 D.R ASCII > EMIT ( print trial #) INFO 30 + ( to) INFO ( fr) DO I @ 0 5 D.R 2 +LOOP ( print data) ; \ screen 90 \ bulletin ... : BLOCKED ( --) \ print last block's data. TRL&BLK NIP 1- ?DUP IF 0 19 AT DUP AUX>BLOCK 0 2 D.R ASCII * EMIT INFO 30 + ( to) INFO ( fr) DO I @ 0 5 D.R 2 +LOOP ( .data) THEN ; \ : TRIED ( --) \ \ print data from each trial in the last block. \ TRL# @ ( to) TRL&BLK NIP 1- #T/BLK @ * 1+ ( fr) \ ?DO I AUX>TRIAL I #T/BLK @ MOD .LINE LOOP ; \ : BULLETIN ( --) \ \ print stats of trial in current block. \ -CURSOR 1 TRL# @ U< IF .TITLE THEN \ TRIED BLOCKED +CURSOR ; \ screen 91 : .INFO ( --) USE @ \ print last trial's measurements. IF ( check to see if this is the first trial given; if no last trial then do NOT do the following:) TRL# @ 1- 0= IF EXIT ( don't print trl 0) THEN TRL# @ 1- #T/BLK @ MOD DUP 0= IF DROP #T/BLK @ .LINE ( last trial in block) EXIT THEN DUP 1 = ( new block) IF LIVE @ IF 17 136 GR-ERASE THEN -CURSOR .TITLE .LINE EXIT THEN .LINE ( all other trials in the block) THEN ; \ screen 92 \ display ... : DISPLAY ( --) \ choose between graphic or numeric display. PICTURE @ IF dark DRAW ELSE .INFO THEN ; \ screen 93 \ the following are handy test words for new unit counter ... : h. ( n --) \ regardless of current base, print number in hexadecimal. base @ swap hex u. base ! ; : binary \ make base to be base 2; use is similar to decimal, hex. 2 base ! ; : set^units ( --) \ for debugging, prepares buffer space and sets pointers into buffer. 10 sane# ! #errors max#units 2* erase ram erase ram drop dup ^unit ! 2+ dup ^unit 2+ ! 2+ dup ^unit 4 + ! 2+ ^unit 6 + ! ; : .units ( --) cr ." ^unit1 " ram drop dup h. @ h. ." , ^unit2 " ram drop 2 + dup h. @ h. cr ." ^unit3 " ram drop 4 + dup h. @ h. ." , ^unit4 " ram drop 6 + dup h. @ h. cr ; \ screen 94 \ store ... : ENOUGH ( --) \ allocate more screens if necessary. SIZE 1- #TRLS @ 2DUP < #TRLS @ 200 < AND IF ." Extending DATA file by " SWAP - DUP U. ." screen(s) for " #TRLS @ U. ." trials" MORE ELSE 2DROP THEN ; : STORE ( --) \ if desired, save data of last trial to disk. SAFETY @ NOT ( true if data is to be saved to disk) IF USE @ ( true if a trial has already been collected) IF update ( save data) trl# @ 0 block ! ( next trial number will be this) UPDATE SAVE-BUFFERS THEN THEN EMPTY-BUFFERS ; : center ( adr # --) c/line 2/ over 2/ - spaces type ; \ screen 95 \ disk ... : gamble ( --) \ print symbols for suits of cards. 3 emit space 4 emit space 5 emit space 6 emit ; : conflict? ( --) \ check to see if last session was finished; if not, ask if you want to continue from where you left off. \ this allows session to continue where it left off, for example if there were a catastrophic power failure \ during the original run, training can continue where it left off. 0 block @ #trls @ u< \ begin where left off? if 36 spaces gamble roadrunner cr cr " The program detects that the last session was stopped early." center cr cr " Do you want to start where you left off with trial # " center 0 block @ u. cr " or with the default trial # " center trl# @ u. ." ?" cr cr " Enter L (for last trial) or any key (for default) " center key ascii L over ascii l = -rot = or ( check for pressing "l" for last choice) if 0 block @ trl# ! then then ; : DISK ( --) \ save data to disk if intended by choice of safety flag. SAFETY @ NOT IF DATADRIVE DV " DATA" FILE ENOUGH ( make sure file length is long enough to store data) TRL# @ 1 = IF HEADER:INFO>DISK SAVE-BUFFERS THEN conflict? THEN ; \ screen 96 \ iti events ... : NAP ( --) \ pause between blocks. TRL&BLK NIP 1 <> IF PAUSE @ TRL&BLK DROP 0= AND IF @TIME roadrunner 0 24 AT ." Press a key to continue with block " trl# @ #t/blk @ / 1+ 0 2 d.r 80 at? drop - 2- spaces BEGIN FAST KEY? UNTIL KEY? IF KEY DROP THEN !TIME 0 24 AT 23 SPACES THEN THEN ; : EVENTS ( --) \ do events except on first trial. USE @ IF SMOOTH ARCHIVE MEASURE DISPLAY AMPLITUDE TRL# @ 1- TRIAL>AUX ( order) live @ not IF BLOCKTRIALS formletter PICTURE @ NOT IF BLOCKED then then THEN ; : ITI ( --) \ intertrial interval. EVENTS STORE ( this order is important here) NAP GET-BUFFER ( this order is important here) TIMING ; \ screen 97 \ interpretive pointer ... : >S ( --b) \ slip offset. \ determines which trial in the trial sequence is intended. TRL# @ 1- ( last trl #) #T/BLK @ MOD ; : SLIP ( b --) \ slip within a word. \ used to begin in the middle of a forth definition of the trial sequence. \ make sure b does not exceed word's length, otherwise the computer will crash. 2* IP> + >IP ; \ the word SLIP can be used as in the following example: \ : one ." one" ; \ : two ." two" ; \ : three ." three" ; \ : test ( b--) 0 max 3 min slip one two three ; \ Note: here b = 0, 1 or 2 \ screen 98 \ trial-type stimulus words ... : T& : T&A : L& : L&A : A& : TL& : TL&A ( --) ITI TONE ( --) ITI TONE-AIR ( --) ITI LT ( --) ITI LT-AIR ( --) ITI AIR ( --) ITI TONE-LT ( --) ITI TONE-LT-AIR \ screen 99 \ block sequences of trials ... TRIAL ; TRIAL ; TRIAL ; TRIAL ; TRIAL ; TRIAL ; TRIAL ; \ tone alone trial. \ tone and air paired trial. \ light alone trial. \ light and air paired trial. \ air alone trial. \ tone-light paired trial. \ tone, lt & air paired. \ normally each of the following words will cause one (1) full block of trials to occur. \ however, there are instances where one needs to start in the middle of the sequence, for example, \ where you previously stopping training because of a technical problem and now you want to continue \ where you left off. each of the following words is a "smart" word in the sense that it will start execution \ at the correct trial in the sequence. : 90% ( --) : UNP ( --) : 75%T ( --) : 75%L ( --) : TRACET ( --) : EXTINCTIONT ( --) : 75%TL ( --) L&A L&A ; >S SLIP >S SLIP >S SLIP >S SLIP >S SLIP >S SLIP >S SLIP T& T&A T&A T&A T&A T&A T&A T&A T&A ; T& A& A& T& T& A& T& A& A& T& T& A& T& A& A& T& A& T& ; T& T&A T&A T& T&A T&A T&A T&A ; L& L&A L&A L& L&A L&A L&A L&A ; T&A T&A T&A T&A T&A T&A T&A T&A T&A ; T& T& T& T& T& T& T& T& T& ; T& T&A T&A T& T&A T&A T&A T&A L& L&A L&A L& L&A L&A \ to allow for the creation of new sequences one can use the following (see menus.scr file for use). \ use the same format as for the above 'canned' options. \ for example, \ : user >s slip t& t& a& t& ; creates a 4 trial block having three tone alone and one air alone trials. \ resolve this example (see below) with \ ' user is trialsequence DEFER USER ' NOOP IS USER \ all trial sequences are resolved before running using (see continue) ... DEFER TRIALSEQUENCE ' NOOP IS TRIALSEQUENCE \ screen 100 \ menu selection ... VARIABLE CHOICE 2 CHOICE ! \ choice is user input for menu. : SUMMARIZE ( --) \ summarize the data. ." Do you wish to SUMMARIZE the data? (y)" KEY ASCII N OVER = SWAP ASCII n = OR NOT IF TX DARK programdrive dv " SUMMARY" INCLUDE THEN ; : SANITY ( --) \ determine number of unit counter errors in session. \ the maximum number of units possible in a bin is the bin duration divided by the duration of the \ unit discriminator pulse (e.g., 200 sec for a Haer discriminator) minus 1. BIN @ UNITDURATION @ / 1- SANE# ! 0 0 #ERRORS 2! ; \ screen 101 \ continue is the main controlling word for running a session ... : CONTINUE ( --) \ this is the actual run word. \ if the run is aborted (e.g., by press <esc><esc>) the session will resume after typing continue<return>. -CURSOR ELAPSED DAWN ! ( set elapsed time at start of trial) BEGIN TRL# @ 1+ #TRLS @ < ( while there are more trials) WHILE TRIALSEQUENCE ( give a block of trials) REPEAT EVENTS STORE HEADER:DISK>AUX FLUSH roadrunner 20 23 ERADICATE +CURSOR KEY? IF KEY DROP THEN SUMMARIZE TX DARK MENU ; \ screen 102 \ overflow ... : *TYPES ( #blocks -- #blockpictures) BLOCKTYPE# @ SPLIT IF 1 ELSE 0 THEN SWAP IF 1+ THEN * ; : STARTLE ( flag n1 n2 seg# -- flag) \ lets you know that there is a problem in memory storage. \ i've never seen/heard this warning. -ROT U< IF BELL ." WARNING: Overflow in segment " ( seg#) U. KEY DROP CR ( flag) 1+ ELSE ( flag seg#) DROP ( flag) THEN ( flag | flag+1) ; : OVERFLOW ( -- f) \ warn if there is not enough room in auxilliary memory. 0 ( flag) \ seg 2: 64 header, 32 each trial, 32 each block * each type: [ 64 1K AREA */ ] LITERAL ( max # AREA byte intervals in 64k) #TRLS @ #A/D @ * ( trials) 2 ( header) + ( subtotal) #BLKS @ ( blocks) *TYPES + ( total) 2 STARTLE \ seg 3: 1k each block graph * each type: 64 ( # 1k blocks available) #BLKS @ ( blocks) *TYPES ( total) 3 STARTLE ; \ screen 103 \ session ... : SESSION ( --) \ initialize system then run trials. SANITY ( set sane # units possible to collect in bin) OVERFLOW IF MENU THEN TX DARK ." Put DATA disk in drive " datadrive type ." <return> or <escape>" KEY esc# = IF MENU THEN -CURSOR TRL# @ 1 = IF CLEANAUX THEN ( erase aux) PICTURE @ IF MIXED GR THEN DISK CLEAN ( erase information array) RAM ERASE ( erase data/graphics area) WIPE ( prepare for block graphics) DARK ( erases everything on screen) RESET ( initializes chips 8253, 8255 on computer interface board) UNUSED ( prevents attempting to display graph when waiting for first trial -- no data to display) CONTINUE ( the actual run word) ; \ screen 104 \ menu documentation ... \ the following screens define words used for making the opening menu. \ the default choice for runtime is set to number 1 for 90% paired tone and light training. \ screen 105 \ handy input string and input number routines used in summary program and elsewhere ... : GET$ ( -- adr n) \ input a string. PAD 64 EXPECT PAD SPAN @ ; : GET# ( -- dn) \ input a multidigit integer. BEGIN GET$ VAL IF 1 ( flag) ELSE 2DROP ." is not a number. Try again. " 0 ( flag) THEN UNTIL ; \ screens 106, 107 and 108 \ menu options ... : COMBINATION ( n1 n2 -- n2 | abort) OVER = IF DARK ABORT THEN ; : GO ( key ascii-n --) OVER = IF CR ." ** Running **" 4 LOAD ( switches) CHOICE @ 1- 2* 10 + DUP 1+ THRU ( load chosen session & sequence parameters) SESSION ( run) KEY? IF KEY DROP THEN THEN ; : COMMENT ( key ascii-n --) OVER = IF DARK ." Enter comment of up to 64 characters" CR ." for DATA file: " INFO 65 BL FILL INFO 64 EXPECT BL INFO SPAN @ + C! ( erase return) THEN ; : SEQUENCES ( key ascii-n --) OVER = IF DARK 29 ( to end of menu choices) 10 ( from beginning of menu choices) DO CR ." Choice " I 8 - 2/ 0 2 D.R SPACE I BLOCK 64 -TRAILING TYPE CR 10 SPACES I 1+ BLOCK 64 -TRAILING TYPE NUF? IF LEAVE THEN 2 +LOOP CR CR ." Current choice = " choice @ u. ." Which RUNTIME do you wish? " GET# DROP SPACE CR 1 MAX 10 MIN CHOICE ! 10 DELAYS THEN ; : INTERTRIAL ( key ascii-n --) \ edit intertrial parameters, e.g., number of a/d channels to collect. OVER = IF CHOICE @ 1- 2* 10 + EDIT THEN ; : INTRATRIAL ( key ascii-n --) \ edit intratrial parameters, e.g., toneon. OVER = IF CHOICE @ 1- 2* 11 + EDIT THEN ; : INSTRUCTIONS ( key ascii-n --) \ see instructions for making new sequences of trials (i.e., blocks) for training. OVER = IF DARK 6 LIST KEY DROP THEN ; : SWITCHES ( key ascii-n --) \ edit softswitches, e.g., filtering. OVER = IF 4 EDIT THEN ; : ANALYZE ( key ascii-n --) \ load summary program. OVER = IF CR SUMMARIZE THEN ; : ANSWER ( key --) \ handles key choice in runtime menu. ( key) ASCII 0 ( key ascii-n) ASCII 1 ASCII 2 ASCII 3 ASCII 4 ASCII 5 ASCII 6 ASCII 7 ASCII 8 ( key) COMBINATION GO COMMENT SEQUENCES INTERTRIAL INTRATRIAL INSTRUCTIONS SWITCHES ANALYZE \ exit program. \ run program. \ enter comment. \ change session default. \ change session parameters. \ change isi default. \ for user defined sequences. \ change soft switches. \ analyze data using SUMMARY \ program. DROP ; \ screen 109 \ handy smart compilation words used in summary program and elsewhere ... : need ( -- flag) \ this works on one line only. bl word find swap drop 0= ; : \if ( flag --) \ this works on one line only. 0= if [compile] \ then ; : ignore; ( -- adr) ascii ; word drop ; immediate : \; ( flag --) \ this works until ; 0= if [compile] ignore; then ; : ignore| ( -- adr) ascii | word drop ; immediate : \| ( flag --) \ this works until | 0= if [compile] ignore| then ; : | ( --) noop ; \ uses: \ \ the following works on one line only: \ need hello \if : hello ." hello there" ; \ \ the following works on multiple lines for only one definition: \ need hello \; : hello \ ." hello there" ; \ \ the following works on multiple lines for multiple definitions: \ need hello \| : hello \ ." hello there" ; \ : goodbye \ ." goodbye" ; | \ \ my normal usages: \ \ need printing \if variable printing \ \ or \ \ need it not \if forget it \ create it \ ... (compile other stuff) ... \ forget it ( a cute phrase to clear out all those subsequently compiled words I no longer need). \ also allows recompiling same definitions without redefining them in the forth dictionary. \ \ screen 110 \ legal notices ... \ \ THIS PROGRAM IS AN UNPUBLISHED WORK FULLY PROTECTED BY THE \ UNITED STATES COPYRIGHT LAWS AND IS CONSIDERED A TRADE SECRET \ BELONGING TO THE COPYRIGHT HOLDER. \ \ SOURCE CODE, OBJECT CODE AND DOCUMENTS: \ \ (C) COPYRIGHT 1985 - 2000 \ \ DAVE LAVOND AND JOE STEINMETZ. \ \ ALL RIGHTS RESERVED.