Computational Memory Lab None
Home
Research
Episodic Memory
Spatial Cognition
Recognition Memory
Resources
Publications
Experiments
Programming
Word Pools
Lab People
My Account
Resources
Subject Database
Administration
Related Sites
Penn Psychology
Penn Perception
Institute for Research in Cognitive Science
Center for Cognitive Neuroscience
Institute for Neurological Sciences
Login
Login Now
Experiment Programming Library Documentation

Experiment Programming Library  for Linux

Programmed by: Josh Jacobs

(based on work by Daniil Utin & Roger Khazan)

 VERSION 1.0

Table of Contents

1. INTRODUCTION

This document provides an overview of the development kit for creating computerized experiments. Every experiment consists of two stages: examination and scoring. In a computerized version both appear as two separate modules. The examination module consists of audio/visual tests given to a subject. Responses are stored as raw text (for example, digitized voice). The scoring part assists in interpretation of this data (e.g., correctness, inter-response time, etc.). The results are then recorded in a cumulative data file, according to a pre-specified format suitable to further analysis (e.g. statistical, graphical, etc.).

Designing experiments that include video and audio components can be simplified with the use of high level routines developed for this purpose. The knowledge of C and at least a very light exposure to C++ are necessary.

See Appendices I and II to learn how to set up an experiment.

Development kit consists of several C++ development libraries:

  • Initialization Routines to aid runtime variable initialization.
  • WordPool A collection of routines that allow one to open, shuffle, and query a set of words, called "word pool".
  • Svga256 X windows video routines, such as initialization of video hardware and displaying and clearing of the text.  (Its name should really be something like XVideo but is Svga256 for backwards compatibility.)
  • SB16 Audio routines, such as sound card initialization recording and playback of digital sound.
  • Segmentation Voice segmentation.
  • Timer Precise timing routines.

2. LIBRARY FUNCTIONS

2.1 Initialization

Initialization library allows runtime initialization of variable values which are obtained from experiment's config.ini file.

Here are a few guidelines on how to use this library: Before programming an experiment edit a config.h file containing all variables to be initialized at runtime. The advantage of initializing certain variables at runtime is that an experiment doesn't need to be recompiled every time values of these variables change. Runtime variables also increase an experiment's flexibility and make it easier and faster to use. A good example of runtime variable is the number of trials in the experiment. After config.h is finished, run the makeini utility; it will take care of generating the config.ini file and all necessary C++ code to parse that file at runtime. Don't forget to declare runtime variables (along with the rest of the variables) in the main experiment file by including the extern.h file produced by makeini, or the experiment won't compile.

Use function load_config() in the main experiment to invoke runtime initialization. It should return OK in case of success.

if (load_config()!=OK) return(-1);


2.2 Word Pool

Word Pool library provides means of operating with collections of words (e.g., a set of 18 groups of words called blocks, each consisting of four words). The set (pool) can be loaded, shuffled, searched and saved.

It is possible to operate on several independent words at the same time. At the beginning of the program an instance of the word pool should be declared in the following way: POOL mypool;. The pool library consists of the following functions:

  • get_pool

  • Usage: mypool.get_pool(filename, number_of_blocks, size);
    [HEADER: long get_pool(char *fname, long nblocks = 0, long size=1);]

    Creates a pool containing words from a specified word file with specified number of blocks of the specified size. For example, the function call mypool.get_pool("wordpool.txt", 18, 4) would get 72 words from the file "wordpool.txt" as 18 groups of 4 words. The default size of the block is 1, so if it is not specified like in mypool.get_pool("wordpool.txt", 72) a linear word pool is created, i.e. each block consists of only one word. If the second argument (number of blocks) is 0, the entire file is used. In order to get a uniform sampling of words in a word pool it is advisable to load an entire pool, shuffle it (see below), and then work with the desired number of words. This way words from the entire spectrum of the dictionary are obtained.

  • get_str

  • Usage #1: mypool.get_str (block_number, item_number);
    [HEADER #1: char *get_str(long block, long item);]

    Usage #2: mypool.get_str (item_number);
    [HEADER #2: char *get_str(long nitem);]

    Returns pointer to a specified word in a word pool. block gives a block number a word should be taken from while item specifies a zero base offset form the firs word of that block. For example: char *word;

    word = list.get_str(9, 2);

    set word pointer to point to the third word in tenth block of a list pool. It is also possible to use this function in linear mode, i.e. word = list.get_str(0, 38) will yield the same result assuming that one block consists of four words.
    If no block number is given, the overloaded version #2 will be used, assuming absolute indexing, ignoring the block structure. I.e., list.get_str(38) is equivalent to list.get_str(0,38).

  • shuffle_items

  • Usage: mypool.shuffle_items(number_of_items, starting_item);
    [HEADER: void shuffle_items(long nitems, long from=0);]

    The usual thing to do after the pool is loaded is to shuffle all the words so the random grouping is created. The function shuffle_items is designed for this purpose, as in mypool.shuffle_items(72), which shuffles the first (since from is equal to 0) 72 words. When the program is executed, this function will shuffle the items the same way each time. To generate a random shuffle, you must call rnd.set_seed(nRandom) and pass it a randomly generated number.

  • shuffle

  • Usage: mypool.shuffle(number_of_blocks, starting_block);
    [HEADER: void shuffle(long nblocks, long from_block=0);]

    This function shuffles entire blocks of the pool preserving individual block structure. That is only blocks, not words within them are shuffled. The function call mypool.shuffle(18) shuffles the first 18 blocks of the pool. As in shuffle_item function, there is an optional second argument that specifies from which block to start shuffling. In general, this routine is convenient for shuffling the study list between the successive presentations. When the program is executed, this function will shuffle the items the same way each time. To generate a random shuffle, you must call rnd.set_seed(nRandom) and pass it a randomly generated number.

  • save_list

  • Usage: mypool.save_list ("list.txt", &pool, starting_block, number_of_blocks);
    [HEADER: int save_list(char *fname, POOL *pool, long from_block, long nblocks);]

    Sometimes it is convenient to save a (shuffled) list presented to a subject in order to have it available for the scoring part. The function save_list makes this possible. It saves specified part of pool in a file fname as another pool.. For example, save_list("list.txt", &pool, 0, 18) saves the first 18 blocks of pool in "list.txt". The advantage of saving them in the pool format is that they can be easily retrieved and operated on as a pool in the scoring part of the experiment, e.g.: POOL list;

    list.get_pool(list.txt", 18, 4);

  • length

  • Usage: mypool.length();
    [HEADER: long length();]

    This function returns the number of blocks in the pool. Continuing our previous example, mypool.length() returns 18.

  • search

  • Usage: mypool.search("word_being_searched_for", starting_point);
    [HEADER: long search(char *str, long from=0);]

    This functions can be used to search for a word in a pool. For example, i = pool.search("table", 7) would search for the word "table" starting from the 8th word in the pool. The returned value specifies index (zero based) of the word table in the pool. Finding which block and item i is can be done in a following way: block = i / blocksize; item = item % blocksize. search returns -1 if word is not present in the pool.
    NOTE: search only attempts a partial match. I.e., the string "BLUE" would match perfectly with the pool word "BLUEJAY". This function is therefore primarily of use in scoring data, as in DOS PARSE, where one wants to complete a word that the user has partially entered. Ideally, however, search is recommended be used in conjunction with searchexact (see below for description) as in:

    index = searchexact(thestring); // Attempt a perfect match first
    if(index==-1) index=search(thestring); // attempt a partial match
    if(index==-1) cerr << "no match"; // the word just ain't in there
  • searchexact

  • Usage: mypool.searchexact("word_being_searched_for", starting_point);
    [HEADER: long searchexact(char *str, long from=0);]

    Like search (see above for description), this functions can be used to search for a word in a pool. For example, i = pool.search("table", 7) would search for the word "table" starting from the 8th word in the pool. The returned value specifies index (zero based) of the word table in the pool. Finding which block and item i is can be done in a following way: block = i / blocksize; item = item % blocksize. search returns -1 if word is not present in the pool.
    searchexact, as contrasted with search, requires that the string match the whole word in the pool in order for the search to be successful.


2.3 SB16 Audio

The following environment variables affect EPL sound output:

  • SDL_AUDIODRIVER
  • This should always be set to "dsp".
  • EPL_MICBOOST
  • On some sound cards the mic input level is too low. If this is the case, EPL_MICBOOST may be used. EPL will multiply all sound samples by this number before any processing or storage.

This library consists of a number of high level functions which allow recording and playback of digital sound through Sound Blaster 16 sound card. To start, an object of class SB16 should be declared (SB16 audio;). There are two functions that allow recording of digital sound. The first one:

  • int record_voice(char *fname) starts recording sound to a voice file fname on the hard disk. It works on the background. This means that statements after this function call are executed while sound is being recorded. Function void stop_voice(void) should be called to stop recording. The timer functions can be used to control the time spent on recording. Here is a typical example of recording voice file:
  • SB16 audio; // declare an instance of class SB16
    TIMER timer; // declare an instance of class TIMER
    char fname[] = "myvoice.voc";
    ...
    // start the timer
    timer.start();
    // start recording in a file specified by fname
    audio.record_voice(fname);
    // wait while timer has not reached X milliseconds
    while (timer.get() < X);
    // stop recording upon exiting from the while loop
    audio.stop_voice();
    The function record_voice should be used to record voice continuously for a certain period of time (e.g. free recall).
  • record_voice_start

  • Usage: record_voice_start("my_voice.voc", number_of_milliseconds_to_record)
    [HEADER: long record_voice_start(char *fname, long length)]
    or
    Usage: record_voice_start("my_voice.voc", number_of_milliseconds_to_record, recording_beginning_function)
    [HEADER: long record_voice_start(char *fname, long length, void (*)())]
    or
    Usage: record_voice_start("my_voice.voc", number_of_milliseconds_to_record, recording_threshold)
    [HEADER: long record_voice_start(char *fname, long length, short)]
    or
    Usage: record_voice_start("my_voice.voc", number_of_milliseconds_to_record, recording_beginning_function, recording_threshold)
    [HEADER: long record_voice_start(char *fname, long length, void (*)(), short)]

    This function records sound into a voice file for a specified number of milliseconds. This functions starts saving sound from microphone only after subject begins speaking. It will just wait until subject produces a sound that exceeds the given recording level threshold.   The sound level threshold can be set by using a version of this function that lets you specify it (see the declarations above). The default value (200) is used if the function is called without specifying it.  The sound level threshold should be set by the programmer to customize the epl to the specific environment that the experiment is being done in (ie. the characteristics of the sound card and microphone, and mixer settings).  Valid values for the sound threshold range from 0 - (2^16-1).  The programmer would probably want to make this value a run-time modifyable variable via the config.ini file so that it is not necessary to recompile the experiment to change this threshold.

    This function also can be passed a function that it will call when recording begins.  The function that the function is passed in should be a function that returns a void and takes no parameters.  This can be used, for instance, to have the program flash "Recording" on the screen when the recording actually begins.  In order to use this function, the programmer will probably have to pass in a wrapper function which encapsulates the functionality that is wanted inside of a function that returns void and takes no parameters.

    Since the amount of recording time is specified as a second argument, no function to stop recording is necessary. A typical example of using this function is presented below:

    SB16 audio; // declare an instance of class SB16
    char fname[] = "myvoice.voc";
    ...
    // start recording in a file specified by fname for 2 seconds
    audio.record_voice_start(fname, 2000);
    This function is useful for recording short responses, one at a time. It is appropriate for measuring the correctness of the responses, as opposed to something like interresponse time.

    This function returns the length of time (in milliseconds) that the program had to wait while there was silence before it began recording sound.
     

  • play_voice

  • Usage: audio.play_voice("my_voice.voc");
    [HEADER: int play_voice(char *fname)]

    This function plays the specified voice file.
     

  • beep

  • Usage: beep(freq,msec);
    [HEADER: void beep(int freq, int msec)]

    Where freq is the desired frequency in Hz, and msec is an amount of time in milliseconds.

    Function beep produces a freq Hz tone (beep) of msec millisecond duration.
     


2.4 Video

This library contains a collection of functions which allow displaying text in high resolution video mode. At the beginning an object of class Svga256 should be instantiated: Svga256 video() and then video mode should be initialized by calling initialize_video().

Default width and height can be changed by specifying different values in the first and second arguments to Svga256 instantiation: Svga256 video(3,4) will create set up 3x4 screen.

Note: Try using default 2x2 width and height. It will make the program less confusing (especially for others).

  • initialize_video

  • Usage: video.initialize_video();
    [HEADER: void initialize_video(void)]

    This function initializes graphics hardware and switches screen to the graphics mode. Call this function after the text based part of the experiment (like asking for a patient's name or number, etc.) is completed.

  • close_video

  • Usage: video.close_video();
    [HEADER: void close_video(void)]

    Call this function to switch back to text mode. Usually, it is a function to call at the end of experiment before exiting. Calling it before initialize_video() makes no sense.
     
     

  • set_pointsize

  • Usage: video.set_pointsize(size);
    [HEADER: void set_pointsize(int size)]

    This function allows you to set the text size that will be used by the EPL.  The value of size that it is passed should be the point size (according to X window's idea of point sizes) that you want all text to be.
     

  • set_textsize

  • Usage: video.set_textsize(size);
    [HEADER: void set_textsize(int size)]

    Note:  This function is used for backwards compatibility with the old version of the EPL.  For new experiments, use the video.set_pointsize function.  set_textsize sets the text size according an unusual metric used in the dos version of EPL.
     
     

  • write_text

  • Usage: video.write_text("My Text", x-position, y-position, horiz-justification, vertical- justification);
    [HEADER: void write_text(char *s, double x, double y, int h = 0, int v = 0)]

    This function writes a text string *s at a location specified by x and y on the screen. For example, video.write_text("Hello, Screen!!!", -.5, .5) will write the specified string in a left upper quarter of the screen. The text is drawn in a white color. Optional arguments h and v allow to alter (h)orizontal and (v)ertical justifications of the text around x and y. The possible values for h are: 0 (LEFT_TEXT), 1 (CENTER_TEXT), and 2 (RIGHT_TEXT). The possible values for v are: 0 (BOTTOM_TEXT), 1 (CENTER_TEXT), 2(TOP_TEXT). By default, text is left justified (0) horizontally, and centered (1) vertically.
     
     

  • clear_text

  • Usage: video.clear_text("My Text", x-position, y-position, horiz-justification, vertical- justification);
    [HEADER: void clear_text(char *s, double x, double y, int h = 0, int v = 0);]

    This function erases a text string *s at a location specified by x and y on the screen. For example, video.clear_text("Hello, Screen!!!", -.5, .5) will erase the specified string in a left upper quarter of the screen. Use this function to undo the results of previous write_text call.

  • clear_screen

  • Usage: video.clear_screen( );
    [HEADER: void clear_screen()]

    This function clears the screen with the current background color. Note: Since this function takes lots of resources and is comparativelyslow; do not use it excessively (don't call it instead of clear_text(), for example).
     
     

  • change_fore

  • Usage: video.change_fore(char *);
    [HEADER: void change_fore(char *color_name)]

    This function changes the foreground color used by all video functions to the color specified in color_name.  For a list of valid colors and their names, refer to the file /usr/lib/X11/rgb.txt.  The default foreground color is white.
     
     

  • change_back

  • Usage: video.change_back(char *);
    [HEADER: void change_back(char *color_name)]

    This function changes the background color used by all video functions to the color specified in color_name.  For a list of valid colors and their names, refer to the file /usr/lib/X11/rgb.txt.  The default background color is black.
     

  • show_file

  • Usage: video.show_file("my_file");
    [HEADER: void show_file(char *filename)]

    Use this function to display a text file on a screen which has been previously initialized by the initialize_video() function. The user may scroll up and down using the arrow keys, page up and page down, and space (same as page down). When the bottom of the file is in view the user may press enter to finish viewing. The user will see context-sensitive instructions at the bottom of the screen. Long lines will be word-wrapped.

    Temporary problem: You MUST have an empty line at the end of the file. This will be fixed.

    Example: video.show_file("txt\intro.txt") will display the file intro.txt form the directory txt (naturally, directory and file should exist before this call is made).

  • show_xpm_file

  • Usage: video.show_xpm_file("my_file", x, y);
    [HEADER: void show_xpm_file(char *filename, double x, double y)]

    where filename is a string containing path (optional) and file name of 256-color bitmap file. x and y are horizontal and vertical position of the .bmp file respectively.

    NOTE:  This function is outdated and is only included here for backwards compatibility.  Please use load_xpm and display_xpm and display_xpm_center below.

    Function show_xpm_file displays xpm files  with left upper corner of the bitmap being in (x, y) coordinate.

    Example:

    Svga256 video();
    
    void main(void)
    {
      video.initialize_video();
      video.show_xpm_file("apple.xpm",-1, 1); // Show apple.xpm file in left upper corner of the screen
    }
  • video.init_xpms(int maxnum)
  • video.load_xpm(char *filename)
  • video.load_xpm(char *filename, int num)
  • video.display_xpm(int num, double x, double y)

  •  

    The preferred way of displaying graphics in the EPL is to use this suite of functions.  By separating the process of loading the bitmap from that of displaying it, it will increase performance by allowing the X server to cache the images internally.  In order to use these functions, at the beginning of the program (after the initialize_video call, but before any real work is done) call initxpms with the maximum number of images that you plan on having the program display throughout its execution.  Then, load each image that you plan on having the program display throughout its execution with the load_xpm function.  From this point on each image can be referred to by its index within the EPL's internal list of images.  You can have the EPL automatically assign each image an index (by just calling load_xpm with an image name and nothing else), or you can specify the exact index for an image (by specifying the image index in the call to load_xpm.).  Then, to display an image, call display_xpm with the index of the image that you want to display, and the coordinates that you want the image displayed at.  This method is faster than using the show_xpm_file function because after all the images are loaded with the load_xpm functions, they will be much faster to access than if they had to be loaded from disk each time.   Here is an example of how the code should be used:
     
     

      Video vid();
      vid.init_xpms(10); //maximum of 10 xpms allowed
      vid.load_xpm("image0xpm");  //puts image0.xpm in index 0
      vid.load_xpm("image1xpm");  //puts image1xpm in index 1
      vid.load_xpm("image2.xpm");  //puts image2.xpm in index 2
      vid.load_xpm("image3.xpm");   //puts image3.xpm in index 3
      vid.load_xpm("image1-new.xpm",1);   //puts image1-new.xpm in index 1, replacing image1.xpm
      vid.display_xpm(1,0,0);  //displays image1-new.xpm at position 0,0
      vid.display_xpm(2,1,1);  //displays image2.xpm at position 1,1

  • video.display_xpm_center(int num, double x, double y)
  • Most likely you'll want to use this function instead of display_xpm. The reason is that this function centers the bitmap around the (x,y) coordinates given, rather than using them as the upper-left corner.

  • kbclear()

  • Usage: video.kbclear ( );

    This function clears the keyboard buffer. This is a very useful way of preventing the program from going on by itself when a subject holds down a key too long inadvertantly filling the buffer.

  • wait_CTRL_pressed( )
  • wait_CTRL_depressed( )
  • get_user_response(int yes_left)

  •  

    Function wait_CTRL_pressed( ) waits for the user to press one of the two CONTROL keys on the bottom of the keyboard. This function returns which control key was pressed (1=right; 0=left). The function wait_CTRL_depressed( ) waits for the user to release one of the two CONTROL keys. This function returns nothing. To get a return value, use the function get_user_response(int yes_left) to get a yes or no response using the control keys. If yes_left=0, the function will return 1 if the right CTRL key is pressed and 0 if the left CTRL key is pressed. If yes_left=1, the function will return 0 if the right CTRL key is pressed and 1 if the left CTRL key is pressed. To be certain that the buffer is kept clear, it is usually best to use get_user_response, followed by wait_CTRL_depressed(int yes_left) and then kbclear() to clear the buffer.

    video.kbclear(); // clear any previous keypresses
    int nInput = video.get_user_response(1); // (1 is LEFT, 0 is RIGHT)

    Here is a typical example of using video library:

    // Instantiate video object
    Svga256 video(SVGA1024x768x256);
    ?
    // Set up video hardware and switch to graphics mode
    video.initialize_video();
    // Present introduction
    video.show_file("txt\intro.txt");
    // Clear screen
    video.clear_screen();
    // Write something on the screen
    video.write_text("Hello, screen!!!", -.5, .5);
    // Erase that something.
    video.clear_text("Hello, screen!!!", -.5, .5);
    // Switch back to text mode
    video.close_video();
  • close_video

  • Usage:  video.close_video();

    This function closes the window created for the video object returns the program to text mode.  This function should be called at the end of an experiment.
     
     

  • get_key()

  • Usage: Video.get_key();
    [HEADER: char get_key()]

    This function should be used by the user to get a character from teh keyboard.  This function serves the same function as the normal C getch function under the EPL X windows implementation.  get_key waits until a key is hit (if one is not already present in the input buffer before it is called) and then returns the character value of the key that is pressed.
     
     

  • get_keyevent()

  • Usage: Video.get_keyevent(XKeyEvent *)
    [HEADER: void get_keyevent(&xkeyevent)]

    This function serves the same purpose as get_key above, except that it places a copy of the actual XKeyEvent object into the XKeyEvent pointer that is passed to it (rather than simply extracting the character value as get_key does).  This can be used if you want to determine the values of the modifier keys (alt, shift, control....) at the time that the key is pressed.

  • xkbhit()

  • Usage: Video.xkbhit()
    [header int xkbhit()]

    This function checks the event queue and  determines if it contains a keyboard event that can be obtained via either get_key or get_keyevent above.  This allows the program to do background processing while waiting for the user to hit a key, as in the following example:

    while (!vid.xkbhit()){
        //do background work here....
        ;
        }

  • kbclear()

  • Usage: Video.kbclear();

    This function flushes the keyboard input buffer of the program.  This function is if you want to have the program only count key presses after a certain point, so that those presses before this function call are ignored.
     

  • mouse_show();

  • Usage:Video.mouse_show();

    This function makes the mouse pointer visible on the screen in preparation for having the user use the pointer for part of the experiment.
     

  • mouse_hide();

  • Usage: Video.mouse_hide();

    This function hides the mouse pointer.  This function should be called after a mouse_show function call in order to put the EPL in the state that it had previous to the mouse pointer being shown.
     

  • mouse_get_event(xpos,ypos,buttons);

  • Usage: Video.mouse_get_event(double *xpos, double *ypos, int *buttons);

    This function is called by the program to get the current state of the mouse pointer.  The pointers that are passed in will contain the correct values corresponding to the currentt status of the mouse: xpos will contain the x coordinate of the mouse pointer, ypos will contain the y coordinate of the y pointer, and buttons will contain a constant that corresponds to the status of the buttons on the mouse that are pressed (see below table).
     
     
    Constant Name Meaning
    MOUSE_EVENT_LRBUTTON Both (left and right, on a three button mouse) buttons pressed
    MOUSE_EVENT_LBUTTON Left mouse button pressed
    MOUSE_EVENT_RBUTTTON Right button presssed
    MOUSE_EVENT_NONE No buttons pressed


2.5 Voice Segmentation

The words in recorded voice files are usually identified during scoring and their timing is recorded for further processing. Parse, the voice segmentation program, is rather large standalone function written to semi-automate and improve a laborious task of manual word segmentation. It is declared in the following way:

  • parse_file

  • Usage: parse_file(fname, pname, data, datasize, parsename);
    [HEADER: int parse_file(char *fname, char *pname, DATA *data, int data_size,char *parsename);]

    where fname is a voice file name containing words to segment, pname is a pool file name containing words that can occur in fname voice file, *data is a pointer to an array of data structures containing information on words found in a voice file, and data_size is a number of elements in data.  parsename is the filename that the parse program will write the parse file (the parsefile is not always necessary, as its contents is duplicated in the data parameter.)

    A brief example should clarify everything:

    DATA wdata[100];
    int i=0;
    wparse_file("my_exp/free_1.voc", "my_exp/free_1.txt", wdata, 100, "/tmp/parse.tmp");  //parsefilename doesn't matter, so it is stored to /tmp
    
    while (wdata[i].pool_index>=0)
            cout << "Pool Index: " << wdata[i].pool_index << "Interresponse Time: " <<
            wdata[i].interresponse << "Word started at: " << wdata[i].voice_start ;
    In above example, wparse_file was given voice file free_1.voc in my_exp directory to segment, free_1.txt word pool in the same directory containing all words that could be in free_1.voc and a wdata array of 100 "blank" data structures of type DATA which could accommodate information on up to 100 words. Upon return, data structure wdata is filled with sequential information on segmented words. Each entry in DATA data structure has the following fields:
    typedef struct
    {
    int pool_index; // Index of the word in a word pool
    long interresponse; // Interresponse time between beginnings of two adjacent words
    long voice_start; // Offset of the word from the beginning of the voice file
    } DATA;
    Now, suppose subject was given five words to memorize from a word pool free_1.txt:

    (0)DUTY, (1)ACCORD, (2)PLACE, (3)REVENGE, (4)MUTE

    Subject recalled the following words that were recorded in a file free_1.voc:

    DUTY, MUTE, ACCORD

    After the call to parse_file the pool_index of wdata structure should be equal to the following values (assuming that scoring was done correctly):

    wdata[0].pool_index 0 Index of DUTY in free_1.txt
    wdata[1].pool_index 4 Index of MUTE in free_1.txt
    wdata[2].pool_index 1 Index of ACCORD in free_1.txt
    wdata[3].pool_index Negative number No more words left in feee_1.voc
    wdata[0].interresponse should be equal to number of milliseconds elapsed from the beginning of the recall to the beginning of the first word.
    wdata[1].interresponse should be equal to number of milliseconds elapsed from the beginning of the first word to the beginning of the second word, etc.
    voicestart field of wdata stands for the number of time units elapsed from the beginning of the voice file to the beginning of the particular word. It has very few applications.
     

    Instructions for the parse program:

    When the program starts, it will try to select what it thinks is the first  word.  If it finds a word, it will highlight the waveform that it thinks it  is.  If not, you will have to what you think is the waveform yourself.  You can scroll through the waveform using either the horizontal scroll bar on the bottom of the screen, or using the left ( [ ) or right ( ] ) brackets.  You can select a waveform with the mouse (hold down the button to start the selection and move the mouse to the end of it.  The start and and of a selection can be changed easily via the arrow keys.  The left and right arrow will move what is selected as the onset of the word, if alt or control is held down while the arrow is pressed, the onset will move even more.  If shift is held while the arrow is pressed, you will change the offset of the word selection instead of the onset.

    Once you have a selection, you can press the spacebar to have it play the sound you selected.  Typically, after having it play the sound, you will change the onset/offset so that it more closely matches the word.  Once you have a word selected, you can type in the word.  the parse program will display the letters that you typed in the bottom of the screen. It will only let you type in letters that fit words that are present in the word pool (so as to prevent spelling errors).  Once the correct word is entered, press enter to have the program mark it down and go to the next word.  If the actual word that is spoken by the user is not in the word list (in other words, an intrusion), press '!' (shift 1) and a dialog box will pop up that will let you enter the word.  Type the word in this dialog box and hit enter to have the program register it.  Once you enter in a word, it will try to find the next word in the file.  The program will play all of the sound from the end of the current word to the end of what it thinks is the next word.  In this way, you can hear if the program made a mistake in parsing the sound file and missed a word.  If the program cannot any next word, you will have to do it manually in the same manner as before.  This same basic pattern contines, and once you reach the end of the program, press the "quit" button to quit.

    You can press the help button to have the program give you a small help box that will give you a short reminder of how the program works.  You can press the up and down arrow to zoom in and out of the file.  This helps in parsing large data files.  You can also zoom in and out using the slider at the bottom of the screen.

    The parameters that the program uses in parsing the sound file for words are set at defaults inside the program, but they can be changed by changing the parse.ini file.  There is a sample version of this file in the parse source code directory.  In order to use this file to change the parsing parameters, copy this file to the directory where the experiment that you will be doing parsing is, and then edit the file so that the parameters that it contains are what you would like to use.  There is more information available on how the word parsing algorithm works and how to change the parameters in the source code for the parsing program.

    Parse Key Bindings

    keystroke function
    alphabetic character
    to identify the currently selected word. Parse will attempt to complete the word from the word pool
    [
    scroll the view left
    ]
    scroll the view right
    -
    delete the word previous to the current marker
    SPACE
    play the current selection
    BACKSPACE or DELETE
    shorten the current selection
    TAB
    move the selection to the next word (here, parse tries to figure out when the next word starts. Be very cautious using TAB: always go back and see whether or not parse skipped words, especially if they were recorded softly
    !
    Use this to enter an intrusion. You will be prompted to type in the intrusion word. Be very careful in typing in the intrusion word precisely.
    ENTER
    accepts the currently entered word and binds it to the currently selected onset marker
    up-arrow
    zoom in
    down-arrow
    zoom out
    left-arrow
    move the word selection later in time (see modifier keys below)
    right-arrow
    move the word selection earlier in time (see modifier keys below)
    modifier keyfunction
    SHIFT
    move the selection offset (else move the selection onset)
    CONTROL
    move the selection faster
    ALT or META
    move the selection even faster


2.6 Timer

A timer library provides two exact timing routines .  The first one, timer.start [HEADER: void start(void)], starts the timer, the second, timer.get [HEADER: usigned long get(void)] returns the time, in milliseconds, expired from the call to start(). A second call to start() resets the timer:

#define ESCAPE 27 // Escape key code
#define DELAY 1000 // Delay in ms.

// Instantiate timer object.
TIMER timer;
int i;

// Do this 10 times.
for (i = 0; i < 10; i++)
{
        // Start timer
        timer.start();
        // Wait for DELAY second to expire.
        while (timer.get()<DELAY)
        if (video.xkbhit()) // Check if a key on a keyboard was pressed
        if (video.get_key() == ESCAPE) // Key was pressed, was it Escape?
        {
                printf("\nAborting countdown?");// Escape was pressed
                exit(1); // Exit without waiting
        }
        // Report that number of seconds expired
        // since entering into for loop
        printf("\n%i ms. expired.", (i + 1)*DELAY);
}
  • delay

  • Usage: void delay(int time);

    This function is a simple wrapper around the TIMER class that is useful for having the program stall execution for time milliseconds.  The program simply sits in a while loop until the specified time is elapsed.
     

  • SysTimer::get()

  • Usage: unsigned long get();
    This function is a wrapper around the unix gettimeofday system call.  This is useful because it returns the number of milliseconds elapsed since an absolute point rather than the simple TIMER class which returns the time elapsed since the initial start call.  This can be useful because it lets you avoid timer skew (when the program is off by a fraction of a millisecond on each get() call and these errors accumulate into something significant.)  In order to use this function, create a SysTimer object, then call that object's get method.
    2.7 Synchronization Object The synchronization protocol works as follows:
    • At the beginning of every testing session, send a "start train" (10 pulses with a 10-ms width and 10-ms IPI) (start_train())
    • Every so often (something like every 10 seconds or so), send a single 10-ms pulse (pulse()). You might do this before each word list is presented or at regular intervals or whatever. Using irregular intervals can be useful for debugging purposes, but this is up to you. The main thing is to get at least two pulses per session in order to align the EEG record with the experimental testing machine clock.
    • Before exitting the session, send an "exit train" (5 pulses, 10-ms width and 10-ms IPI).
    Finally, make sure that the synch routines won't throw off the timing of your experiment. The start and exit trains are not used in aligning the clocks, but can be very useful in figuring out when a testing session happened.
    • Constructor #1: ParallelPortEEGSync(char *ename,SysTimer *passedtimer)
      This function initialises the synch object and opens the synch file, in the "append" mode. The filename is by default synch.txt. ename denotes the name of the experiment, and will be included on every line of the synch. log file. The timer you pass into passedtimer should be the same as the one you use to write out experimental event time stamps. NOTE: if you write synch pulses to the same file repeatedly, the timestamps will be appended, not overwritten.
    • Constructor #2: ParallelPortEEGSynch(char *sname,char *ename,SysTimer *passedtimer)
      This constructor works just like the other one, but you can pass it the specific name you want for the synch. log file in sname. You might, for instance, want to name these separately for each subject or each subject/session.
    • void start_train(char *comment)
      This function sends a train of 10 pulses, to mark the beginning of a testing session. It marks the event with a timestamp into the synch. log file. You may optionally provide a comment to add additional information to the log file text line.
    • void exit_train(char *comment)
      This function sends a train of 5 pulses, to mark the end of a testing session. It marks the event with a timestamp into the synch. log file. You may optionally provide a comment to add additional information to the log file text line.
    • void pulse(char *comment)
      This function sends a single 10-ms pulse. Use this a few times to make sure you can align the EEG and experimental clocks. It also writes a line to the synch. log file. You may optionally provide a comment to add additional information to the log file text line.
    • Destructor: ~ParallelPortEEGSynch()
      This destructor closes the synch. log file. Therefore, please use it so that you don't lose any timestamps.
       

    How to start. Creating new project and setting up environment.
    • Unzip and untar epl.tar.gz, this should give you a parse directory and a lib directories
    • Run 'make' and then 'install' in order to compile and install the library under /usr/local/lib/ and /usr/local/include/.
    • Make sure that you include the file "epl.h" that you just untarred somewhere in the code for the experiment that you want to create.
    • To use the load_config function, copy makeini from the lib directory into the directory where you want to run your experiment, then create a config.h file there and run makeini on this file.  See the load_config function for more information.
    • In order to compile your experiment, you need to make sure that you include all of the libraries that epl requires. The compile line to use is:

    • g++ experiment_name.cc -o experiment_name -L/usr/X11R6/lib -lX11 -lXpm -lpthread -lepl

      You should change experiment_name to whatever the actual name of the files in your experiment are. The system libraries that the EPL needs to be linked with to work are the X11, Xpm and pthreads.

    • Notes:
      • In order to ensure that audio works correctly, make sure that the sound card is configured correctly under linux (make sure you have the most recent drivers as well.)  Also ensure that all users have access to the sound card.  This can be done by running the following commands as root:

      • chmod a+r /dev/dsp
        chmod a+w /dev/dsp

        Note that this does present a potential security problem....

      • In order for the parse_file function to find the parse program correctly, make sure that the parse binary itself (that was compiled when you actually ran make inside the parse directory) is either in the same directory as the experiment itself, or is in the path (for either of these purposes, it may be helpful to use links).
      • When recording sound, change the sound card mixer settings (you can use xmixer or aumix, for instance) so that the "recording monitor" setting is turned down.  If it is too high, whenever you are recording sound, the sound will be played through the computer's speakers.
        • There have been some problems with the linux sound drivers under kernel versions < 2.0.36 (specifically, earlier kernel versions do not give you access to the correct sound card mixer settings).   It is recomended that you use a kernel at least as new as this.


    Tips for porting experiments to Linux EPL from DOS EPL

    As one may expect, there are a number of differences that must be taken into account when trying to move an experimetn that was programmed under the DOS EPL to the Linux EPL.  These differences stem from both differences between the actual implementation of the EPL on these platforms (the API is not completely compatible), and lower-level differences between DOS and Linux programming.  Here is a summary of some of the things that you should keep track of when porting an experiment to the Linux EPL.

    • User Input:  Under the DOS EPL it was acceptable to use the standard C calls (getch(), getchar(), etc...) for getting input from the user.  Under the Linux EPL these calls will not work at all since the experiments run in an X environment.  The only way to receive input from the user (short of doing all kind of X event loop hacking) is to use the EPL function calls for this purpose.
      • get_key() should be used to retrieve a single character from the user instead of any low-level C calls (like getchar).
      • xkbhit() should be used to test whether a key has already been pressed by the user (instead of kbhit()).
      • kbclear() should be used to clear the keyboard buffer.
      • The part of the API involving the mouse is completely different under Linux as it was under DOS.  Read the documentation above for more information.
    • Video:  There are a number of changes made to the way that the video calls function in the Linux EPL.
      • set_pointsize(int) should be used to select the desired text size rather than set_textsize(int).  set_pointsize lets the programmer specify the exact pointsize of the text that they wish to display.  The metric used to size text by set_textsize was completely arbitrary.  (note: set_textsize(int) is still supported by the EPL for backwards compatibility)
      • Color specification:
        • Screen Foreground: change_fore(colorname) should be used instead of setcolor.
        • Screen background:  Previously, in order to erase the screen and fill it with a certain color, the programmer would use a call to clear_screen(colorname).  The new EPl works slightly differently.
          • change_back(colorname) should be used to select the background color.  Any subsequent calls to clear_screen (or clear_text) will use this background color in performing their operation.  clear_screen does not take any parameters (unlike the dos version) and gets its background color from the value specified by the change_back call.
        • In calls to change_back and change_fore the correct way to specify the color that you want is to use the X windows colorname.  (The dos version had its own color naming scheme.)  See the documentation for these functions above for more information.
    • Sound:
      • In order to produce beeps, the beep function should be used, rather than the soundon/soundoff functions that were often used previously.
    • Misc.
      • Ensure that in filename strings unix style forward slashes ( / ) are used instead of the dos backslash ( \\  ).