#ifndef neuromat_eeg_frame_buffer_H #define neuromat_eeg_frame_buffer_H /* Buffer for readin consecutive dataframes from plain text NeuroMat EEG datasets. */ /* Last edited on 2023-10-21 21:47:52 by stolfi */ #define _GNU_SOURCE #include #include #include typedef struct neuromat_eeg_frame_buffer_t { int32_t size; /* Max number of frames in buffer. */ int32_t nc; /* Total number of channels. */ double **val; /* Cyclic queue of frame pointers. */ int32_t it_ini; /* Index of first frame in buffer. */ int32_t it_fin; /* Index of last frame in buffer. */ } neuromat_eeg_frame_buffer_t; /* A buffer that stores up to {size} consecutive data frames from a text NeuroMat EEG dataset, each with {nc} data channels (including electrodes, triggers, reference, and other event marker channels). A frame with time index {it} in the range {it_ini..it_fin} is stored into {val[it % size][0..nc]}; other frames are not stored. Entries of {val} that are not used in this way are either {NULL} or point to an unused data frame record. The index {it_ini} is always non-negative, and {it_fin} is {it_ini-1} iff the buffer is empty, at least {it_ini} if it has at least one frame loaded. */ neuromat_eeg_frame_buffer_t *neuromat_eeg_frame_buffer_new(int32_t size, int32_t nc); /* Allocate EEG data buffer, with space for up to {size} frames of {nc} channels each. */ void neuromat_eeg_frame_buffer_free(neuromat_eeg_frame_buffer_t *buf); /* De-allocates all the internal subsidiary records of {buf}, and the record {*buf} itself. */ typedef int32_t neuromat_eeg_frame_buffer_read_proc_t(int32_t nc, double val[]); /* Type of a client procedure that reads a frame from somewhere and stores it into {val[0..nc-1]}. Should return 1 if success, 0 if there are no more frames. */ int32_t neuromat_eeg_frame_buffer_get_frame ( neuromat_eeg_frame_buffer_t *buf, int32_t it, neuromat_eeg_frame_buffer_read_proc_t read_frame, bool_t mirror ); /* Tries to ensure that the requested data frame {it} is in {buf}. Namely, if {it} is already in {buf.it_ini..buf.it_fin}, does nothing. If {it} is greater than {buf.it_fin}, reads one or more frames from {rd} using the client-given {read_frame} procedure, stores them into their places in the buffer, and increments {buf.it_fin}; until {buf.it_fin} is equal to {it}. Allocates frame storage as needed. If necessary, reuses ("flushes") one or more buffer frame slots, starting at index {buf.it_ini} and incrementing the latter. If it succeeds, returns a non-negative index {itb = (it%buf.size)} such that {buf.val[itb]} points to the frame with index {it} in the whole dataset. Fails, returning {-1}, if the file contains no frames, or if {mirror} is false and {it} is outside the range of frame indices in the file, namely {0..nt-1} for a file with {nt} frames. If {mirror} is true and the file is non-empty, any index {it} outside the range {0..nt-1} is mapped back into that range by folding about the extremes. Namely, if {it < 0} is negative, replaces it internally by its absolute value, thus implicitly mirroring the file about frame 0. Likewise, if {it >= nt}, replaces {it} internally by {2*(nt-1) - it}, thus implicitly mirroring the file about frame {nt-1}. These mirrorings are repeated as often as neccessary to get {it} in the range {0..nt-1}. */ void neuromat_eeg_frame_buffer_find_next_pulse ( neuromat_eeg_frame_buffer_t *buf, neuromat_eeg_frame_buffer_read_proc_t *read_frame, int32_t it_start, int32_t ns, int32_t ichs[], int32_t *it_iniP, int32_t *it_finP, int32_t *sP ); /* Looks for the first and last frame of the next pulse in certain channels of the input, starting with frame {it_start}. Assumes that some frames are in {buf}, reads more frames with {read_frame} as needed. The frame {it_start} must not have been flushed from buffer {buf} yet. Specifically, the procedure assumes that the pulses of interest are in channels {ichs[0..ns-1]}. It looks for the first frame at or after frame {it_start} where one of those channels is positive, and then for the last frame where that same channel is positive, extending possibly to the end of the dataset. If it succeeds, returns in {*it_iniP,*it_finP} the first and last frames of the pulse. Also returns in {*sP} the the index in {0..ns-1} such that the the pulse was in channel {ichs[*sP]}. If all channels {chs[0..ns-1]} are zero in all frames at or after frame {it_start}, the procedure returns {INT32_MIN} in {*it_fx_iniP}. In that case {*it_fx_finP,*sP} are undefined. */ void neuromat_eeg_frame_buffer_find_next_pulse_start ( neuromat_eeg_frame_buffer_t *buf, neuromat_eeg_frame_buffer_read_proc_t *read_frame, int32_t it_start, int32_t ns, int32_t ichs[], int32_t *itP, int32_t *sP ); /* Finds the next up-transition in one or more channels of the input, starting at or after frame {it_start}. Assumes that some frames are in {buf}, reads more frames with {read_frame} as needed. The frame {it_start} must not have been flushed from buffer {buf} yet. Specifically, the procedure assumes that the pulses of interest are in channels {ichs[0..ns-1]}. It looks for the first frame, beginning with {it_start}, where one of those channels is positive. If successful, returns in {*itP} the index of that frame, and in {*sP} the channel index in {0..ns-1} among the specified -- that is, an integer such that the pulse occurred in channel {ichs[*sP]}. If there are two or more pulses starting at the same time, returns the one with smallest {*sP}. Returns {INT32_MIN} in {*itP} if the dataset ends before finding the required pulse. In that case, {*sP} will be undefined. */ void neuromat_eeg_frame_buffer_find_pulse_end ( neuromat_eeg_frame_buffer_t *buf, neuromat_eeg_frame_buffer_read_proc_t *read_frame, int32_t it_start, int32_t ic, int32_t *itP ); /* Finds the frame at or after frame {it_start} where the current pulse in channel {ic} ends. Returns its index in {*itP}. Assumes that some frames are in {buf}, reads more frames with {read_frame} as needed. The frame {it_start} must not have been flushed from buffer {buf} yet. Specifically, the procedure requires that channel {ic} of frame {t_start} is positive. The procedure then looks for the first frame after that where that channel is zero. If successful, returns in {*itP} the index pf the *previous* frame (the last one where the channel was nonzero). If the dataset ends before finding that frame, it returns in {*itP} the index of the last existing frame. */ /* TWO-PHASE EXPERIMENT RUNS The following procedures are intended to locate experimental runs consisting of a "fixation" phase followed by a "stimulus" phase. They assume that the start of each phase is indicated by the rising edge of a pulse in some marker channel. For now, the procedures assume that the start-of-fixation pulse occcurs in only one marker channel, while the start-of-stimulus pulse may occur in one of several channels, depending on the run type. */ void neuromat_eeg_frame_buffer_get_next_pulse_pair ( neuromat_eeg_frame_buffer_t *buf, neuromat_eeg_frame_buffer_read_proc_t *read_frame, int32_t it_start, int32_t ns, int32_t ichs[], int32_t nt_fx_default, bool_t verbose, char *chname[], double fsmp, int32_t *it_fx_iniP, int32_t *it_fx_finP, int32_t *it_st_iniP, int32_t *it_st_finP, int32_t *s_stP ); /* Looks for the next experimental run in the input file, defined by a start-of-fixation pulse (possibly missing) followed by a start-of-stimulus pulse, starting at or after frame {it_start}. Assumes that some frames are in {buf}; reads more with {read_frame} if needed. The frame {it_start} must not have been flushed from {buf} yet. Specifically, the procedure assumes that the valid start-of-stimulus pulses are in channels {ichs[0..ns-2]}, and that the start-of-fixation pulses are in channel {ic_fx = ichs[ns-1]}. It looks for the first frame, beginning with {it_start}, where channel {ic_fx} is positive, and considers that the first frame of the start-of-fixation pulse. Then looks for the first frame after that where some channel {ic_st = ichs[s_st]} becomes positive, for some {s_st} in {0..ns-2}; and considers that the first frame of the start-of-stimulus pulse. If it succeeds, returns in {*it_fx_iniP,*it_fx_finP} the first and last frames of the start-of-fixation pulse, and in {*it_st_iniP,*it_st_finP} the first and last frames of the start-of-stimulus pulse. Also returns in {*s_stP} the the stimulus type in {0..ns-2}. Reports the pulse to {stderr}, assuming {chname[o..buf->nc-1]} are the channel names and {fsmp} is the sampling rate (in Hz). If there are no more runs in the input, returns {INT32_MIN} in {*it_fx_iniP}. In that case {*it_fx_finP,*it_st_iniP,*it_st_finP,*s_stP} are undefined. If the first pulse found after {it_start} is a start-of-stimulus pulse, tries to get around the situation by assuming the the lost start-of-fixation pulse came {nt_fx_default} frames before the start-of-stimulus pulse (or at the first frame, if there is not enough frames for that). Note that, as a result, {*it_fx_ini_P} and possibly {*it_fx_finP} may be negative. If two or more start-of-fixation pulses are found with no intervening start-of-stimulus pulse, ignores all but the last one. */ void neuromat_eeg_frame_buffer_get_next_fixation_pulse ( neuromat_eeg_frame_buffer_t *buf, neuromat_eeg_frame_buffer_read_proc_t *read_frame, int32_t it_start, int32_t ns, int32_t ichs[], int32_t nt_fx_default, bool_t verbose, char *chname[], double fsmp, int32_t *it_fx_iniP, int32_t *it_fx_finP ); /* Looks for the next start-of-fixation pulse in the input file, faking one if it is missing, starting at or after frame {it_start}. Assumes that some frames are in {buf}; reads more with {read_frame} if needed. The frame {it_start} must not have been flushed from {buf} yet. Specifically, the procedure assumes that the valid start-of-stimulus pulses are in channels {ichs[0..ns-2]}, and that the start-of-fixation pulses are in channel {ic_fx = ichs[ns-1]}. It looks for the first frame, beginning with {it_start}, where channel {ic_fx} is positive, and considers that the first frame of the start-of-fixation pulse. If it succeeds, returns in {*it_fx_iniP,*it_fx_finP} the first and last frames of the start-of-fixation pulse. Reports the pulse to {stderr}, assuming {chname[o..buf->nc-1]} are the channel names and {fsmp} is the sampling rate (in Hz). If there are no more runs in the input, returns {INT32_MIN} in {*it_fx_iniP}. In that case {*it_fx_finP} is undefined. If the first pulse found after {it_start} is a start-of-stimulus pulse, tries to get around the situation by assuming the the start-of-fixation pulse came {nt_fx_default} frames before the start-of-stimulus pulse. Note that this may result in a negative value of {*it_fx_iniP} and maybe also {*it_fx_finP}. */ void neuromat_eeg_frame_buffer_get_next_stimulus_pulse ( neuromat_eeg_frame_buffer_t *buf, neuromat_eeg_frame_buffer_read_proc_t *read_frame, int32_t it_start, int32_t ns, int32_t ichs[], bool_t verbose, char *chname[], double fsmp, int32_t *it_st_iniP, int32_t *it_st_finP, int32_t *s_stP ); /* Looks for the next start-of-stimulus pulse in the input file, faking one if it is missing, starting at or after frame {it_start}. Assumes that some frames are in {buf}; reads more with {read_frame} if needed. The frame {it_start} must not have been flushed from {buf} yet. Specifically, the procedure assumes that the valid start-of-stimulus pulses are in channels {ichs[0..ns-2]}, and that the start-of-fixation pulses are in channel {ic_fx = ichs[ns-1]}. It looks for the first frame, beginning with {it_start}, where any channel {ichs[s_st]} is positive, for any {s_st} in {0..ns-2}, and considers that the first frame of the start-of-stimulus pulse. If it succeeds, returns in {*it_st_iniP,*it_st_finP} the first and last frames of the start-of-stimulus pulse, and in {*s_stP} the phase {s_st}. Reports the pulse to {stderr}, assuming {chname[o..buf->nc-1]} are the channel names and {fsmp} is the sampling rate (in Hz). If reaches the end-of-file before finding such a frame, returns {INT32_MIN} in {*it_st_iniP}. In that case {*it_st_finP} and {*s_stP} are undefined. If the first pulse in channels {ichs[0..ns-1]} found after {it_start} is a start-of-fixation pulse, rather than a start-of-stimulus, also returns {INT32_MIN} in {*it_st_iniP}. */ #endif