/* This is an excerpt from the code for the device for the Wickens and Martin-Emerson task. The emphasis is on how the device interacts with the EPIC system, so the many functions that handle various details are not shown in this sample. A run consists of a series of blocks, with the specified number of trials in each block. Each block begins with a period of time in which the tracking cursor does not move. */ #include "Wickens_device.h" #include "Geometry.h" #include "Coordinator.h" #include "Output_tee.h" #include "Epic_globals.h" #include "Human_processor.h" #include "Numeric_utilities.h" #include "Symbol_utilities.h" #include "Assert.h" #include "Device_exception.h" #include "Epic_standard_names.h" // defines standard constants, like "Color" as Color_c #include #include #include #include #include #include using namespace std; /* Symbol constants would normally be used, but literal strings are shown in this sample excerpt for clarity. E.g.: const Symbol Start_experiment_c("Start_experiment"); */ // experiment constants const long choice_task_delay_minimum_c = 5000; const long choice_task_delay_range_c = 2000; const long error_sampling_interval_c = 125; const long error_sampling_duration_c = 2000; const long cursor_update_interval_c = 20; // const int n_separations = 12; // const double separations[n_separations] = {0., 3.2, 6.4, 9.6, 12.8, 16., 19.2, 22.4, 25.6, 28.8, 32., 35.2}; const int n_separations = 5; const double separations[n_separations] = {3.2, 6.4, 12.8, 22.4, 35.2}; // display constants // tracking region origin is at 0, +10 const GU::Point tracking_target_location_c(0., 10.); const double degrees_per_pixel_c = .0584795; const double pixels_per_degree_c = 1. / degrees_per_pixel_c; // scaled rms error is rms error in VA units * pixels per degree-va // B1 response is left middle for left-arrow // B2 response is left index for right-arrow // define the global function that creates a specific device Device_processor * create_specific_device() { return new Wickens_device("Wickens device", Normal_out); } // The constructor simply saves the experiment condition specifications away. Wickens_device::Wickens_device(const string& id, Output_tee& ot) : Device_processor(id, ot), new_control_input(false), stimulus_number(0), condition_string("10 -1 Easy"), n_trials(0), stimulus_separation(0.),tracking_difficulty("Easy"), trial(0), sampling_error(false), cursor_movement_enabled(false) { parse_condition_string(); // set up the conditions } void Wickens_device::initialize() { // initialize the base object Device_processor::initialize(); separation_index = 0; tracking_index = 0; initialize_block(); device_out << "Conditions: " << condition_string << endl; // schedule the start event - provide some delay before putting up objects do_device_delay_event(200, "Start_experiment", nil_c); // schedule the start event for the first block do_device_delay_event(1000, "Start_block", nil_c); } // all events for the device first come here, then to the specific handling function. void Wickens_device::accept_event(Smart_Pointer event_ptr) { if(get_trace() && Trace_out) Trace_out << processor_info() << typeid(*event_ptr).name() << " received" << endl; // tell the message to handle itself with yourself! event_ptr->handle_self(this); } // handle device delay events void Wickens_device::handle_event(Smart_Pointer msg_ptr) { if(msg_ptr->type == "Start_experiment") { start_experiment(); } else if(msg_ptr->type == "Start_block") { start_block(); } if(msg_ptr->type == "Move_cursor") { update_cursor_position(); } else if(msg_ptr->type == "Start_choice_trial") { start_choice_trial(); } else if(msg_ptr->type == "Sample_error") { sample_error(); } else if(msg_ptr->type == "Stop_error_sampling") { stop_error_sampling(); } else if(msg_ptr->type == "Stop") { Coordinator::get_instance().stop(); } else throw Device_exception(this, string(" Unknown delay type: ") + (msg_ptr->type).str()); } /* Start the experiment by presenting the objects tracking origin - a one-degree circle tracking cursor - a cross-hairs choice stimulus circle - a 2 degree circle possibly displaced from tracking origin */ void Wickens_device::start_experiment() { target_location = tracking_target_location_c; cursor_location = tracking_target_location_c; human_ptr->make_visual_object_appear("Tracking_target", target_location, GU::Size(1., 1.)); human_ptr->set_visual_object_property("Tracking_target", "Color", Black_c); human_ptr->set_visual_object_property("Tracking_target", Shape_c, Empty_Rectangle_c); human_ptr->make_visual_object_appear("Tracking_cursor", cursor_location, GU::Size(1., 1.)); human_ptr->set_visual_object_property("Tracking_cursor", "Color", Red_c); human_ptr->set_visual_object_property("Tracking_cursor", Shape_c, Cross_Hairs_c); human_ptr->make_visual_object_appear("Stimulus_circle", stimulus_location, GU::Size(2., 2.)); human_ptr->set_visual_object_property("Stimulus_circle", "Color", Black_c); human_ptr->set_visual_object_property("Stimulus_circle", Shape_c, Empty_Circle_c); } // Start the block by positioning the choice circle and // starting to move the cursor and presenting the stimulus void Wickens_device::start_block() { // reposition the objects target_location = tracking_target_location_c; cursor_location = tracking_target_location_c; human_ptr->set_visual_object_location("Tracking_target", target_location); human_ptr->set_visual_object_location("Tracking_cursor", cursor_location); human_ptr->set_visual_object_location("Stimulus_circle", stimulus_location); cursor_movement_enabled = true; do_device_delay_event(1000, "Move_cursor", nil_c); do_device_delay_event(2000, "Start_choice_trial", nil_c); } // This function updates the cursor position using the last control input void Wickens_device::update_cursor_position() { /* not shown nere: calculate the new cursor location */ cursor_location = new_cursor_location; human_ptr->set_visual_object_location("Tracking_cursor", cursor_location); // compute and update the tracking error as a property of the tracking cursor Symbol distance(cartesian_distance(cursor_location, target_location)); human_ptr->set_visual_object_property("Tracking_cursor", Distance_c, distance); // schedule a new update do_device_delay_event(cursor_update_interval_c, "Move_cursor", nil_c); } // the joystick contains a location which is not the new location of the cursor, // but rather its vector is used to compute the displacement to be applied to the cursor void Wickens_device::handle_event(Smart_Pointer event_ptr) { // the control input movement vector is saved to be combined with the forcing function control_input = GU::Cartesian_vector(event_ptr->movement_vector); new_control_input = true; if(get_trace() && Trace_out) { if(event_ptr->target_name != nil_c) Trace_out << processor_info() << "Final Ply input to " << event_ptr->target_name << endl; } } void Wickens_device::start_choice_trial() { response_made = false; stimulus_name = concatenate_to_Symbol("ChoiceStimulus", ++stimulus_number); // flipped a coin to choose a stimulus choice_stimulus = static_cast(random_int(2)); choice_stimulus_onset = get_time(); human_ptr->make_visual_object_appear(stimulus_name, stimulus_location, GU::Size(1.5, 1.)); human_ptr->set_visual_object_property(stimulus_name, Color_c, Black_c); if(choice_stimulus == LEFT_ARROW) human_ptr->set_visual_object_property(stimulus_name, Shape_c, Left_Arrow_c); else human_ptr->set_visual_object_property(stimulus_name, Shape_c, Right_Arrow_c); // increment here in case response is never made trial++; // start sampling the tracking error sampling_error = true; sample_error(); // schedule a sampling cessation do_device_delay_event(error_sampling_duration_c, "Stop_error_sampling", nil_c); } void Wickens_device::handle_event(Smart_Pointer msg_ptr) { if(msg_ptr->key_name == "B1" && choice_stimulus == LEFT_ARROW || msg_ptr->key_name == "B2" && choice_stimulus == RIGHT_ARROW) { // a correct response - accumulate it long rt = get_time() - choice_stimulus_onset; current_rt.update(rt); if(get_trace() && Trace_out) Trace_out << processor_info() << "Trial " << trial << ": RT= " << rt << endl; } else throw Device_exception(this, string("Unrecognized keystroke: ") + msg_ptr->key_name.str()); response_made = true; // remove the stimulus human_ptr->make_visual_object_disappear(stimulus_name); }