:: Style
:: Cortex state file for reading Cortex data files and then re-creating the experiment  
The following information was taken from a state file contributed by Dr. Andrew R. Mitz, at the Laboratory of Systems Neuroscience, NIMH. This code is provided as is.

The purpose of this program is to help validate external data acquisition equipment, like Plexon and AlphaOmega. This Cortex state file reads an existing Cortex data file and tries to re-create the experiment by encoding the same events as in the original experiment, and generating pulses to a digital output port to represent the original spikes. Analog data are ignored.

If you would like to open the files directly do so here (hardware.h, replay.c) or read below for a description of how these files work.

:: Hardware requirements  
One digital input is required for a 10 KHz clock. This clock allows replay to operate with sufficient precision. We use a carefully adjusted signal generator driving bit 0 on Port A. Digital outputs are required for each spike channel that you want to simulate. We use 4 output channels on Port B.

:: Program setup  
Digital channels are defined in "Hardware definition for clock input and simulated spike outputs" Omit the "hardware.h" include line if you want to hard code the devices. We abstract device names in hardware.h to make software more portable.

FILENAME The file name is hard coded in this #define statement.


SKIPTRIALS n Will skip over the first trials of a file before simulation starts


PAUSE Set this = 1 to help debug the system, then set to 0 to run a replay


MAX_EVENTS Set this as high as your Cortex system allows. It is the number of events that replay can handle in a single trial. We reduce the size of DATA_STRUCTS in CORTEX.CFG to make more space, but we really don't know if that helps.

spike_codes[SPIKE_CODE_CNT] These are the event codes that will be treated as spikes


ignore_codes[IGNORE_CNT] These are groups of event codes to ignore. IGNORE_CNT is always an even number. Codes are in a range. e.g., to ignore codes 50 to 65:
    ignore_codes[0]=50;
    ignore_codes[1]=65;
Aencode() This subroutine replaces encode() and sends events to Plexon or Alpha-Omega. You must modify it to fit your hardware.

:: Programming notes  
Reading a Cortex file from disk using a C compiler that does not recognize unsigned integers is a royal pain. Subroutines char2int() and char2long() do the dirty work of moving signed char data from an array to signed short and signed long integers without interference from the sign bit.

:: Revisions  
V0.01 20 Sept 2004 reads data files, tracks external clock
V0.02 20 Sept 2004 dumps parts of data files, generates spikes
V0.03 5 Oct 2004 SKIPTRIALS option
V0.04 6 Oct 2004 cleaned up documentation

:: Includes  
#include "css_inc.h"
#include "hardware.h" // define hardware devices

:: Hardware definition for clock input and simulated spike outputs  
#define SIM_DEV DIO96_DEV // Spike simulation device
#define SIM_CLOCK DIO96_2A_PIOA // A port gets clock input
#define SIM_SPIKE_PORT DIO96_2A_PIOB // B port gets up to 8 spikes out
#define SIM_CTRL DIO96_2A_CONT // control byte
#define SIM_CLOCK_BIT 0x01 // clock bit
#define sim_init 0x91 // initialization byte (A=in,B=out,C=split)
#define clock_high (DEVinp(SIM_DEV,SIM_CLOCK) & SIM_CLOCK_BIT)

#define TRUE 1
#define FALSE 0
#define NULL 0
#define MIN(X,Y) ((X< #define MAX(X,Y) ((X>Y)?X:Y)
#define SPIKE_DURATION 8 // number of 0.1 ms clock tics for simulated spike
#define FILENAME "za93.1" // test file name
#define SKIPTRIALS 358 // skip this many trials before sending data
#define PAUSE 0 // 1 = pause during the trials, 0 = avoid pauses

:: Persistent variables  
#define trial_number _int0 // trial number
#define reward_port_data _int1 // shadow register for reward port
#define counter_port_data _int2 // shadow register for external counter port
#define sim_spike_port_data _int3 // shadow register for simulated spike ports

#define fh _long0 // file handle
#define MAX_EVENTS 200 // buffer size

int event_codes [MAX_EVENTS];
long event_times[MAX_EVENTS];

:: Global Variables  
Cortex header structure that Cortex state file programming can tolerate. Cortex does not support structures or unsigned integers.

int h_length; // unsigned short
int h_cond_no; // short
int h_repeat_no; // unsigned short
int h_block_no;
int h_trial_no;
int h_isi_size; // unsigned short
int h_code_size;
int h_eog_size;
int h_epp_size;
char h_i_storage_rate; // unsigned char
char h_kHz_resolution; // unsigned char
int h_expected_response; // (enum = short)
int h_response; // (enum = short)
int h_response_error; // (enum = short)
long file_position; // global to help debugging

:: Events that will be treated as spikes  
#define SPIKE_CODE_CNT 4 // number of spikes with digital outputs MAXIMUM IS 8
int spike_codes[SPIKE_CODE_CNT]; // maximum of SPIKE_CODE_CNT
int spike_counts[SPIKE_CODE_CNT];

:: Events that will be completely ignored (e.g., spikes without outputs)  
#define IGNORE_CNT 6 // number of min/max pairs * 2
int ignore_codes[IGNORE_CNT]; // min,max ranges of events to ignore

:: Subroutine Prototypes  
void read_header();
void dump_header();
long read_trial();
int wait_for_clock();
int encode_event(int en);
void Aencode(int code);
int check_keyboard(int waitfor);
int char2int(pchar cbuf);
long char2long(pchar cbuf) ;

:: Main Function  
main() {
    long total_events,event_number,trial_clock;
    int spike_chan,chan_bit,i,j,abort;
    int chan_timer[SPIKE_CODE_CNT];

    spike_codes[0]=1; // events that will be treated as spikes
    spike_codes[1]=2; // array size is SPIKE_CODE_CNT
    spike_codes[2]=3;
    spike_codes[3]=4;

    // ignore events 0 to 10
    ignore_codes[0]=9; // Groups of events that will be completely ignored
    ignore_codes[1]=10; // array size is IGNORE_CNT

    // ignore events 110 to 113
    ignore_codes[2]=110; // Events are ignored from first to last code
    ignore_codes[3]=113; // in a group

    // ignore events 211 to 245
    ignore_codes[4]=211;
    ignore_codes[5]=245;

    // Initialization just before the first trial
    if (trial_number==0) {
      SCREENmode(MODE_TEXT); // put screen into terminal mode
      printf("\nOpening file: %s\n",FILENAME);
      fh=fopen(FILENAME,"rb"); // global file handle

      if (fh==NULL) {
        printf("open failure\n");
        goto ABORT;
      }

      else {
        printf("Initializing hardware\n");
        reward_port_data=0;
        counter_port_data=0;
        sim_spike_port_data=0;

        DEVoutp(REWARD_DEV,REWARD_PORT,reward_port_data); // Initialize reward port
        DEVoutp(COUNTER_DEV,COUNTER_PORT,counter_port_data); // Initialize counter port
        DEVoutp(SIM_DEV,SIM_SPIKE_PORT,sim_spike_port_data); // Initialize simulated spike port
        DEVoutp(ALAB_DEV,ALAB_CTRL,alphalab_init); // PA,PB,PC_hi=out, PC_lo=in
        DEVoutp(SIM_DEV,SIM_CTRL,sim_init); // Spike simulation device init
        printf("Enter F2 to run clock test, or [enter] to skip test.\n");
        abort=0;
      }

      if (check_keyboard(1)==2) {

        for (i=0; i < 5; i++) { // 5 times
          trial_clock=0;
          sim_spike_port_data=0;
          MS_TIMERset(1,2); // synchronize clock
          while(MS_TIMERcheck(1)); // wait for clock to transition
          MS_TIMERset(1,100); // set for 100 ms

          while(MS_TIMERcheck(1)) {
            abort=wait_for_clock(); // watch 10 Khz clock
            trial_clock++;
            sim_spike_port_data= (sim_spike_port_data) ? 0 : 1; // output bit 0 tracks 10 Khz clock
            DEVoutp(SIM_DEV,SIM_SPIKE_PORT,sim_spike_port_data);
            if (abort) goto ABORT;
          }

          printf("%d tics in 100 ms (1000 is ideal)\n",trial_clock); // should get 1000 tics
        } // for (i=0;

        printf("Hit [enter] key to continue.\n");
        check_keyboard(1); // wait for a key
      } // if (check_keyboard(1)==2)

    } // if (trial_number==0)

    // **** Most trials start here ********** //
    trial_number++;
    trial_clock=0; // reset 0.1 ms clock

    Cls();
    printf(" Use F1 to break out of trial\n");
    printf("Reading header for trial %d\n",trial_number);
    read_header(); // read cortex file header for next trial
    dump_header(); // print header to the screen
    printf("Hit [enter] key to continue.\n");
    check_keyboard(PAUSE); // wait for a key

    Cls();
    printf("\nReading trial data for trial %d\n",trial_number);
    total_events=read_trial(); // move data to event_codes[] and event_times[] arrays
    printf("Events read: %d\n",total_events);
    printf("Hit F2 to see codes + times, or [enter] key to continue.\n");

    if (check_keyboard(PAUSE)==2) {
      for (i=0; i < 10; i++) {
        printf("Event %d Time %d\n",event_codes[i],event_times[i]);
      }

      printf("Hit [enter] key to continue.\n");
      check_keyboard(PAUSE); // wait for a key
      Cls();
    }

    if ( trial_number < SKIPTRIALS)
      goto ABORT; // skip first trials if need be
    event_number=0;

    // initialize simulated spike timers
    for (spike_chan=0; spike_chan < SPIKE_CODE_CNT; spike_chan++)
      chan_timer[spike_chan]=0;

    // count the number of spikes.
    for (j=0 ; j < SPIKE_CODE_CNT; j++)
      spike_counts[j]=0; // clear counters
    for (i=0; i < total_events; i++)
      for (j=0 ; j < SPIKE_CODE_CNT; j++)
        if (event_codes[i]==spike_codes[j])
          spike_counts[j]++;

    for (j=0 ; j < SPIKE_CODE_CNT; j++)
      printf("Spike chan %d has %d spikes\n",j,spike_counts[j]);
    printf("Simulating trial\n");

    while (event_number < (total_events-1)) { // continue until all events are done
      // wait for 10 Khz clock to tic
      if(wait_for_clock())
        break; // wait for next tic of the clock, check for trial abort
      trial_clock++;

      // service simulated spike timers
      for (spike_chan=0; spike_chan < SPIKE_CODE_CNT; spike_chan++) { // cycle through the timers
        if (chan_timer[spike_chan] > 0) { // ignore timers that are not running
          chan_timer[spike_chan]--;
          if (chan_timer[spike_chan]==0) { // has a timer run out?
            chan_bit=1; // yes, clear that bit in the simulated spike output port
            chan_bit=chan_bit << spike_chan;
            chan_bit=INV(chan_bit); // mask for output bit
            sim_spike_port_data=sim_spike_port_data & chan_bit;
            DEVoutp(SIM_DEV,SIM_SPIKE_PORT,sim_spike_port_data); // clear bit
          } // if chan_timer==0
        } // if chan_timer > 0
      } /// for spike_chan

      // execute all new events that have occurred since the last clock tic. Event times are in milliseconds.
      while ((trial_clock)/10 >= event_times[event_number]) {
        spike_chan=encode_event(event_codes[event_number]);
        if (PAUSE) {
          printf("time %d event %d, response %d\n",(trial_clock/10),
            event_codes[event_number],spike_chan);
        }

        if ((spike_chan > 0) && (spike_chan < (SPIKE_CODE_CNT-1))){
          // start spike output
          chan_bit=1; // create bit to enable spike output
          chan_bit=chan_bit << (spike_chan-1); // shift bit to appropriate position
          sim_spike_port_data=sim_spike_port_data | chan_bit; // set digital output for that channel
          DEVoutp(SIM_DEV,SIM_SPIKE_PORT,sim_spike_port_data);
          chan_timer[spike_chan-1]=SPIKE_DURATION; // start timer
        } // if spike_chan >= 0
        event_number++;
      } // while trial_clock >= event_times
    } // while event_number < total_events-1
ABORT:
} // end main()

:: read_header() function to fetch header information  
void read_header() {
    int i,header_buffer[13],count;
    char buffer[30];

    file_position=ftell(fh);

    count=fread(buffer,1,26,fh); // read the 13 word header into a buffer
    if (count==0)
      printf("end of file\n");
    else if (count < 26)
      printf("unexpected end of file\n");

    for (i=0; i<13; i++)
      header_buffer[i]=char2int(&buffer[i*2]) ;

    h_length= header_buffer[0];
    h_cond_no= header_buffer[1];
    h_repeat_no= header_buffer[2];
    h_block_no= header_buffer[3];
    h_trial_no= header_buffer[4];
    h_isi_size= header_buffer[5];
    h_code_size= header_buffer[6];
    h_eog_size= header_buffer[7];
    h_epp_size= header_buffer[8];
    h_i_storage_rate= header_buffer[9] & 0xFF; // single byte value
    h_kHz_resolution= header_buffer[9] >> 8 ; // single byte value
    h_expected_response= header_buffer[10];
    h_response= header_buffer[11];
    h_response_error= header_buffer[12];
} // read_header()

:: dump_header() function to dump header information to the screen  
void dump_header() {
    printf("\n header for trial %d (%d)\n",trial_number,h_trial_no);
    printf("Starting at file position %d\n",file_position);
    printf("length %d cond %d repeat %d block %d\n",h_length,h_cond_no, h_repeat_no,h_block_no);
    printf("storage rate %d Khz %d\n",h_i_storage_rate,h_kHz_resolution);
    printf("expected %d response %d error %d\n",h_expected_response,h_response, h_response_error);
    printf("code size %d timestamps %d EOG size %d EPP size %d\n",h_code_size, h_isi_size,h_eog_size,h_epp_size);

    file_position=ftell(fh);
    printf("Ending at file position %d\n",file_position);
} // dump_header()

:: read_trial() function to read one trial of event data, skip over analog data  
long read_trial() {
    long events_in_file,events_to_read,excess_events;
    long trial_start_time;
    char rbuffer[MAX_EVENTS*4];
    int i;

    events_in_file=h_code_size/2; // number of event codes in the file
    events_to_read=MIN(MAX_EVENTS,events_in_file); // maximum number of event codes to memory can hold
    excess_events=MAX(0,(events_in_file-events_to_read)); // number of event codes not replayed.

    printf("Events in file: %d Events to read: %d excess: %d\n",events_in_file,events_to_read,excess_events);

    // times
    file_position=ftell(fh);
    printf("Reading events at file position %d\n",file_position);
    fread(rbuffer,1,events_to_read*4,fh); // Get as many event times as will fit in memory
    trial_start_time=char2long(rbuffer);
    event_times[0]=0;
    for (i=1; i < events_to_read; i++)
      event_times[i]= (char2long(rbuffer+(i*4)) - trial_start_time);
    fseek(fh,excess_events*4,SEEK_CUR);

    // codes
    file_position=ftell(fh);
    printf("Reading codes at file position %d\n",file_position);
    fread(rbuffer,1,events_to_read*2,fh); // Get same number of event codes
    for (i=0; i < events_to_read; i++)
      event_codes[i]=char2int(rbuffer+(i*2)) ;
    file_position=ftell(fh);
    printf("Skipping %d bytes of codes starting at file position %d\n",excess_events*2,file_position);
    fseek(fh,excess_events*2,SEEK_CUR);

    // epp + eog
    file_position=ftell(fh);
    printf("Skipping %d bytes of analog starting at file position %d\n",(h_epp_size+h_eog_size),file_position);
    fseek(fh,(h_epp_size+h_eog_size),SEEK_CUR); // skip reminder of trial

    file_position=ftell(fh);
    printf("Reading is finished at file position %d\n",file_position);

    return(events_to_read); // number of events
} // read_trial()

:: wait_for_clock()  
The purpose of this function is to wait for the next transition of the 10 Khz clock returns with a 1 if an abort was requested

int wait_for_clock() {
    while(clock_high);
    if (check_keyboard(0)==1)
      return 1; // abort with F1
    while(!clock_high);
    return 0;
} // wait_for_clock()

:: encode_events()  
This function will return -1 if event was ignored, 0 if event was encoded, and n if event is spike number n (n>0)

int encode_event(int en) {
    int i,igmin,igmax;

    // weed-out globally ignored events
    for (i=0; i < IGNORE_CNT; i++) {
      igmin=ignore_codes[2*i];
      igmax=ignore_codes[2*i+1];
      if ((en >= igmin) && (en <<= igmax)) {
        return(-1); // return -1 to indicate an ignored event
      }
    }

    // identify spikes
    for (i=0; i < SPIKE_CODE_CNT; i++) {
      if (en == spike_codes[i]) return(i+1); // return spike number
    }
    // encode the rest
    Aencode(en);
    return (0); // return zero for a regular event
} // encode_events()

:: Aencode: encode event and send to AlphaLab  
This replaces encode(). All encodes are forwarded to the AlphaLab device. 16 bits of data are sent via DIO ports A and B. Port C is used for handshaking. AlphaLab requires at least 20 microseconds to recognize a change in the strobe from Cortex. The alphalab_ready bit is high when the AlphaLab is ready.

void Aencode(int code) {
    while (!alphalab_ready); // check AlphaLab ready flag
    DEVoutp(ALAB_DEV,ALAB_BYTE0,(code & 0xFF));
    DEVoutp(ALAB_DEV,ALAB_BYTE1,(code/0x100) & 0xFF);
    encode(code);
    alphalab_strobe_on; // raise strobe bit
    while (!alphalab_ready); // wait for ready flag (25us)
    alphalab_strobe_off; // drop strobe bit
} // Aencode()

:: check_keyboard() function responds to keyboard touches  
Respond to keyboard requests if waitfor==1, print a prompt and wait. otherwise, return to calling program immediately. Returns 1 to 9 indicating F1 to F9. Returns 20 if the return key was hit.

int check_keyboard(int waitfor) {
    int a,r;

    if(waitfor)
      printf(">");
    else if (!KeyPressed())
      return 0;
    a=GetAKey();
    r=0;

    switch (a) {
      case K_F1: r=1; break; // most F keys are not used by the program.
      case K_F2: r=2; break;
      case K_F3: r=3; break;
      case K_F4: r=4; break;
      case K_F5: r=5; break;
      case K_F6: r=6; break;
      case K_F7: r=7; break;
      case K_F8: r=8; break;
      case K_F9: r=9; break;
      case K_RETURN: r=20; break;
    }

    if(waitfor) {
      if (r != 0)
        printf("F%d\n",r);
      else
        printf(" ");
      }
      return r;
    } // check_keyboard()

:: char2int() function to convert signed char buffer to signed integer value  
int char2int(pchar cbuf) {
    int word,byte,i,LAST_BYTE;

    LAST_BYTE=1; // 2 byte conversion
    word=0;
    for (i=LAST_BYTE; i >= 0; i--) {
      if (cbuf[i] >= 0)
        byte=cbuf[i]; // negative value?
      else {
        byte= cbuf[i] & 0x7F; // remove sign bit for assignment
        byte= byte | 0x80; // insert bit back into int
      }
      word= (word << 8) | byte;
    }
    return(word);
} // char2int()

:: char2long() to convert signed char buffer to signed integer value  
long char2long(pchar cbuf) {
    long dword;
    int byte,i,LAST_BYTE;

    LAST_BYTE=3; // 4 byte conversion
    dword=0;
    for (i=LAST_BYTE; i >= 0; i--) {
      if (cbuf[i] >= 0)
        byte=cbuf[i]; // negative value?
      else {
        byte= cbuf[i] & 0x7F; // remove sign bit for assignment
        byte= byte | 0x80; // insert bit back into int
      }
      dword= (dword << 8) | byte;
    }
    return(dword);
} // char2long()

:: Hardware.h file  
// Hardware definition
#define DAS16_DEV 0 // device 0 = Compuboard

#define DAS16_PIOA 0x10
#define DAS16_PIOB 0x11
#define DAS16_PIOC 0x12

#define DIO96_DEV 1 // device 1 = DIO96

#define DIO96_1A_PIOA 0x00 // Cable 1, Connector A, Port A
#define DIO96_1A_PIOB 0x01 // Cable 1, Connector A, Port B
#define DIO96_1A_PIOC 0x02 // Cable 1, Connector A, Port C
#define DIO96_1A_CONT 0x03 // Cable 1, Connector A, Port control

#define DIO96_1B_PIOA 0x04 // Cable 1, Connector B, Port A
#define DIO96_1B_PIOB 0x05 // Cable 1, Connector B, Port B
#define DIO96_1B_PIOC 0x06 // Cable 1, Connector B, Port C
#define DIO96_1B_CONT 0x07 // Cable 1, Connector B, Port control

#define DIO96_2A_PIOA 0x08 // Cable 2, Connector A, Port A
#define DIO96_2A_PIOB 0x09 // Cable 2, Connector A, Port B
#define DIO96_2A_PIOC 0x0A // Cable 2, Connector A, Port C
#define DIO96_2A_CONT 0x0B // Cable 2, Connector A, Port control

#define DIO96_2B_PIOA 0x0C // Cable 2, Connector B, Port A
#define DIO96_2B_PIOB 0x0D // Cable 2, Connector B, Port B
#define DIO96_2B_PIOC 0x0E // Cable 2, Connector B, Port C
#define DIO96_2B_CONT 0x0F // Cable 2, Connector B, Port control

// Use the above hardware definitions to define connections to external devices

// counters
#define COUNTER_DEV DAS16_DEV // reward counter on PIO12 of Compuboard
#define COUNTER_PORT DAS16_PIOC // counters use port C
#define COUNTER1_BIT 0x10
#define COUNTER2_BIT 0x20

// touch bar
#define BAR_DEV DAS16_DEV
#define BAR_PORT DAS16_PIOA
#define BAR_BIT 0x1
#define bar_touch (DEVinp(BAR_DEV,BAR_PORT) & BAR_BIT)

// AlphaLab event codes
#define ALAB_DEV DIO96_DEV // AlphaLab device
#define ALAB_BYTE0 DIO96_1B_PIOA // low order byte
#define ALAB_BYTE1 DIO96_1B_PIOB // high order byte
#define ALAB_HSHAKE DIO96_1B_PIOC // handshaking byte
#define ALAB_READY 0x1 // ready bit (input)
#define ALAB_STROBE 0x10 // strobe bit (output)
#define ALAB_STROBE_NOT 0xFE00 // inverse of strobe bit (hi nibble)
#define ALAB_CTRL DIO96_1B_CONT // control byte
#define alphalab_init 0x81 // initialization byte (A,B,C_hi=out)
#define alphalab_ready (ALAB_READY & DEVinp(ALAB_DEV,ALAB_HSHAKE))
#define alphalab_strobe_on DEVoutp(ALAB_DEV,ALAB_HSHAKE,ALAB_STROBE)
#define alphalab_strobe_off DEVoutp(ALAB_DEV,ALAB_HSHAKE,ALAB_STROBE_NOT)

// Both reward devices on same port
#define REWARD_DEV DAS16_DEV
#define REWARD_PORT DAS16_PIOB
#define REWARD1_OFFSET 0x0;
#define REWARD2_OFFSET 0x10;

// SitePlayer Web support (requires #include webinc.h)
// SitePlayer uses standard COM2
// #define USE_WEB TRUE // FALSE to disable web support
#define WEB_BAUD_DIVISOR BAUD9600 // 9600
#define WEB_COM_PORT COM2 // COM2
#define WEB_COM_BITS N81 // N81
#define WEB_IRQ 3 // IRQ




NIMH CORTEX was written by a team of dedicated researchers for the NIMH Laboratory of Neuropsychology.
Questions or problems regarding this web site should be directed to CortexSite@salk.edu.