import { JsPsych , JsPsychPlugin , ParameterType , TrialType } from "jspsych" ; const info = < const > { name : "audio-button-response" , parameters : { /** The audio to be played. */ stimulus : { type : ParameterType . AUDIO , pretty_name : "Stimulus" , default : undefined , } , /** Array containing the label(s) for the button(s). */ choices : { type : ParameterType . STRING , pretty_name : "Choices" , default : undefined , array : true , } , /** The HTML for creating button. Can create own style. Use the "%choice%" string to indicate where the label from the choices parameter should be inserted. */ button_html : { type : ParameterType . HTML_STRING , pretty_name : "Button HTML" , default : '<button class="jspsych-btn">%choice%</button>' , array : true , } , /** Any content here will be displayed below the stimulus. */ prompt : { type : ParameterType . HTML_STRING , pretty_name : "Prompt" , default : null , } , /** The maximum duration to wait for a response. */ trial_duration : { type : ParameterType . INT , pretty_name : "Trial duration" , default : null , } , /** Vertical margin of button. */ margin_vertical : { type : ParameterType . STRING , pretty_name : "Margin vertical" , default : "0px" , } , /** Horizontal margin of button. */ margin_horizontal : { type : ParameterType . STRING , pretty_name : "Margin horizontal" , default : "8px" , } , /** If true, the trial will end when user makes a response. */ response_ends_trial : { type : ParameterType . BOOL , pretty_name : "Response ends trial" , default : true , } , /** If true, then the trial will end as soon as the audio file finishes playing. */ trial_ends_after_audio : { type : ParameterType . BOOL , pretty_name : "Trial ends after audio" , default : false , } , /**
         * If true, then responses are allowed while the audio is playing.
         * If false, then the audio must finish playing before a response is accepted.
         */ response_allowed_while_playing : { type : ParameterType . BOOL , pretty_name : "Response allowed while playing" , default : true , } , } , } ; type Info = typeof info ; author Kristin Diep
 *  see https class AudioButtonResponsePlugin implements JsPsychPlugin < Info > { static info = info ; private audio ; constructor ( private jsPsych ) { } trial ( display_element , trial , on_load ) { let trial_complete ; var context = this . jsPsych . pluginAPI . audioContext ( ) ; var response = { rt : null , button : null , } ; var startTime ; this . jsPsych . pluginAPI . getAudioBuffer ( trial . stimulus ) . then ( ( buffer ) => { if ( context !== null ) { this . audio = context . createBufferSource ( ) ; this . audio . buffer = buffer ; this . audio . connect ( context . destination ) ; } else { this . audio = buffer ; this . audio . currentTime = 0 ; } setupTrial ( ) ; } ) . catch ( ( err ) => { console . error ( `Failed to load audio file "${ trial . stimulus }". Try checking the file path. We recommend using the preload plugin to load audio files.` ) ; console . error ( err ) ; } ) ; const setupTrial = ( ) => { if ( trial . trial_ends_after_audio ) { this . audio . addEventListener ( "ended" , end_trial ) ; } if ( ! trial . response_allowed_while_playing && ! trial . trial_ends_after_audio ) { this . audio . addEventListener ( "ended" , enable_buttons ) ; } var buttons = [ ] ; if ( Array . isArray ( trial . button_html ) ) { if ( trial . button_html . length == trial . choices . length ) { buttons = trial . button_html ; } else { console . error ( "Error in audio-button-response plugin. The length of the button_html array does not equal the length of the choices array" ) ; } } else { for ( var i = 0 ; i < trial . choices . length ; i ++ ) { buttons . push ( trial . button_html ) ; } } var html = '<div id="jspsych-audio-button-response-btngroup">' ; for ( var i = 0 ; i < trial . choices . length ; i ++ ) { var str = buttons [ i ] . replace ( /%choice%/g , trial . choices [ i ] ) ; html += '<div class="jspsych-audio-button-response-button" style="cursor: pointer; display: inline-block; margin:' + trial . margin_vertical + " " + trial . margin_horizontal + '" id="jspsych-audio-button-response-button-' + i + '" data-choice="' + i + '">' + str + "</div>" ; } html += "</div>" ; if ( trial . prompt !== null ) { html += trial . prompt ; } display_element . innerHTML = html ; if ( trial . response_allowed_while_playing ) { enable_buttons ( ) ; } else { disable_buttons ( ) ; } startTime = performance . now ( ) ; if ( context !== null ) { startTime = context . currentTime ; this . audio . start ( startTime ) ; } else { this . audio . play ( ) ; } if ( trial . trial_duration !== null ) { this . jsPsych . pluginAPI . setTimeout ( ( ) => { end_trial ( ) ; } , trial . trial_duration ) ; } on_load ( ) ; } ; function after_response ( choice ) { var endTime = performance . now ( ) ; var rt = Math . round ( endTime - startTime ) ; if ( context !== null ) { endTime = context . currentTime ; rt = Math . round ( ( endTime - startTime ) * 1000 ) ; } response . button = parseInt ( choice ) ; response . rt = rt ; disable_buttons ( ) ; if ( trial . response_ends_trial ) { end_trial ( ) ; } } const end_trial = ( ) => { this . jsPsych . pluginAPI . clearAllTimeouts ( ) ; if ( context !== null ) { this . audio . stop ( ) ; } else { this . audio . pause ( ) ; } this . audio . removeEventListener ( "ended" , end_trial ) ; this . audio . removeEventListener ( "ended" , enable_buttons ) ; var trial_data = { rt : response . rt , stimulus : trial . stimulus , response : response . button , } ; display_element . innerHTML = "" ; this . jsPsych . finishTrial ( trial_data ) ; trial_complete ( ) ; } ; function button_response ( e ) { var choice = e . currentTarget . getAttribute ( "data-choice" ) ; after_response ( choice ) ; } function disable_buttons ( ) { var btns = document . querySelectorAll ( ".jspsych-audio-button-response-button" ) ; for ( var i = 0 ; i < btns . length ; i ++ ) { var btn_el = btns [ i ] . querySelector ( "button" ) ; if ( btn_el ) { btn_el . disabled = true ; } btns [ i ] . removeEventListener ( "click" , button_response ) ; } } function enable_buttons ( ) { var btns = document . querySelectorAll ( ".jspsych-audio-button-response-button" ) ; for ( var i = 0 ; i < btns . length ; i ++ ) { var btn_el = btns [ i ] . querySelector ( "button" ) ; if ( btn_el ) { btn_el . disabled = false ; } btns [ i ] . addEventListener ( "click" , button_response ) ; } } return new Promise ( ( resolve ) => { trial_complete = resolve ; } ) ; } simulate ( trial , simulation_mode , simulation_options , load_callback ) { if ( simulation_mode == "data-only" ) { load_callback ( ) ; this . simulate_data_only ( trial , simulation_options ) ; } if ( simulation_mode == "visual" ) { this . simulate_visual ( trial , simulation_options , load_callback ) ; } } private create_simulation_data ( trial , simulation_options ) { const default_data = { stimulus : trial . stimulus , rt : this . jsPsych . randomization . sampleExGaussian ( 500 , 50 , 1 / 150 , true ) , response : this . jsPsych . randomization . randomInt ( 0 , trial . choices . length - 1 ) , } ; const data = this . jsPsych . pluginAPI . mergeSimulationData ( default_data , simulation_options ) ; this . jsPsych . pluginAPI . ensureSimulationDataConsistency ( trial , data ) ; return data ; } private simulate_data_only ( trial , simulation_options ) { const data = this . create_simulation_data ( trial , simulation_options ) ; this . jsPsych . finishTrial ( data ) ; } private simulate_visual ( trial , simulation_options , load_callback ) { const data = this . create_simulation_data ( trial , simulation_options ) ; const display_element = this . jsPsych . getDisplayElement ( ) ; const respond = ( ) => { if ( data . rt !== null ) { this . jsPsych . pluginAPI . clickTarget ( display_element . querySelector ( `div[data-choice="${ data . response }"] button` ) , data . rt ) ; } } ; this . trial ( display_element , trial , ( ) => { load_callback ( ) ; if ( ! trial . response_allowed_while_playing ) { this . audio . addEventListener ( "ended" , respond ) ; } else { respond ( ) ; } } ) ; } } export default AudioButtonResponsePlugin ;