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.