Clear Filters
Clear Filters

Why is there no matching of timing data when trying to match participant timing data (separate file) with note onset data (one file)?

1 view (last 30 days)
Hello!
I want to analyze synchronization data. I have the timing of note onsets in seconds of a 30s long audio file in an xlsx file. I also have the timestamps of a participant's taps in relation to a stimuli in a separate xlsx file. The experiment is thus: the participant will listen to a 30s long audio stimulus where a C4 note is played at different rhythms. There will be 6 different audio files - a file will be randomly selected from the list for the participant to synchronize to by tapping along. This will happen 24 times as each stimuli has to be selected 4 times.
In my participant's data file there are several columns of interest to me. The most important being the column "keyTap.rt" as this is the column that contains the timestamps of each trial. Each row of this column will contain all the tap timestamps of one trial. Another column is the "Rhythmsequence" column, which designates what audio file was playing at the time of the trial. Hence, each row of this column tells me what audio file was playing at that particular trial. Rows of keyTap.rt and rows of Rhythmsequence have to match up. They also have to match up to the data in the note onsets file (RPTiming.xls), which has a column that called "Rhythmsequence" which says what audiofile and another column called "Notetiming" which has the timing of the note onset. So I have a multi-level matching task I have to perform prior to analysis.
So the problem is that I don't think I am matching the data properly.
As a result, I am unable to generate the circular histogram plots that I need.
Here is my code:
% Load participant data
participant_data = readtable('945386.xlsx');
keyTap_rt = participant_data.keyTap_rt;
Rhythmsequence = participant_data.Rhythmsequence;
trials_thisN = participant_data.trials_thisN;
% Display Rhythmsequence for debugging
disp('Rhythmsequence:');
disp(Rhythmsequence);
% Load note onset data
note_onset_data = readtable('RPTiming.xls');
note_onset_Rhythmsequence = note_onset_data.Rhythmsequence;
note_onset_Notetiming = note_onset_data.Notetiming;
% Define durations for different audio stimuli
file_duration_data = { ...
'44_neutral_eighths.mp3', 33; ...
'44_prosody_eighths.mp3', 33; ...
'44_reverse_eighths.mp3', 33; ...
'78_neutral_eighths.mp3', 30; ...
'78_prosody_eighths.mp3', 30; ...
'78_reverse_eighths.mp3', 30 ...
};
% Initialize duration_map
duration_map = containers.Map();
% Populate duration_map
for i = 1:size(file_duration_data, 1)
filename = file_duration_data{i, 1};
duration = file_duration_data{i, 2};
duration_map(filename) = duration;
end
% Initialize matched data arrays
matched_keyTap_rt = [];
matched_Rhythmsequence = [];
matched_Notetiming = [];
% Store results for each trial
trial_results = struct('filename', {}, 'circular_mean', {}, 'circular_variance', {});
% Initialize a map to store results per audio file
audiofile_results = containers.Map('KeyType', 'char', 'ValueType', 'any');
% Define the list of relevant audio files
relevant_files = {'44_neutral_eighths.mp3', '44_prosody_eighths.mp3', '44_reverse_eighths.mp3', '78_neutral_eighths.mp3', '78_prosody_eighths.mp3', '78_reverse_eighths.mp3'};
% Iterate through each trial in the participant data from rows 7 to 30
for i = 7:30
% Get the full filename from the Rhythmsequence
full_filename = Rhythmsequence{i};
% Check if full_filename or keyTap_rt is empty
if isempty(full_filename) || isempty(keyTap_rt{i})
warning('Empty full_filename or keyTap_rt at index %d', i);
continue;
end
% Debugging statement to check the full_filename
disp('Full filename:');
disp(full_filename);
% Remove 'resources/' prefix if it exists
filename_without_prefix = strrep(full_filename, 'resources/', '');
% Debugging statement to check the filename without the prefix
disp('Filename without prefix:');
disp(filename_without_prefix);
% Check if the filename without prefix is in the list of relevant files
if ~ismember(filename_without_prefix, relevant_files)
continue; % Skip this trial if the file is not relevant
end
% Check if the filename without prefix is present in the duration map
if isKey(duration_map, filename_without_prefix)
% Retrieve duration from duration_map
duration_of_audio_stimulus = duration_map(filename_without_prefix);
% Debugging statement to check the duration
disp('Duration of audio stimulus:');
disp(duration_of_audio_stimulus);
else
warning('Duration not found for audio file: %s', filename_without_prefix);
continue; % Skip this trial if duration is not found
end
% Find the index of the matching Rhythmsequence in the note onset data
matching_idx = find(strcmp(note_onset_Rhythmsequence, full_filename));
% Match keyTap.rt with corresponding Notetiming based on Rhythmsequence
for j = 1:numel(matching_idx)
% Extract note onset timings for the current Rhythmsequence
note_onset_times = note_onset_Notetiming{matching_idx(j)};
% Convert note onset timings to numeric array
note_onset_times = str2double(strsplit(note_onset_times, ','));
% Check if the trial index is within the range of available keyTap_rt values
if i <= numel(keyTap_rt)
% Iterate over each note onset and find the closest tapping event
for k = 1:numel(note_onset_times)
% Find the index of the closest tapping timestamp to the current note onset timestamp
[~, closest_idx] = min(abs(keyTap_rt{i} - note_onset_times(k)));
% Match participant's tapping timestamp with note onset timestamp
matched_keyTap_rt = [matched_keyTap_rt; keyTap_rt{i}(closest_idx)];
matched_Rhythmsequence = [matched_Rhythmsequence; full_filename];
% For the first note, set the timestamp to 0.0
if k == 1
matched_Notetiming = [matched_Notetiming; 0.0];
else
matched_Notetiming = [matched_Notetiming; note_onset_times(k)];
end
end
% Convert timestamps to angles for current trial
angles = mod(2*pi * matched_keyTap_rt / duration_of_audio_stimulus, 2*pi);
% Calculate circular mean and variance for current trial
trial_circular_mean = circ_mean(angles);
trial_circular_variance = circ_var(angles);
% Perform Rayleigh test for circular mean
p_mean = circ_rtest(angles);
% Perform Rayleigh test for circular variance
p_variance = circ_rtest(angles);
% Store results for current trial
trial_results(end+1).filename = full_filename;
trial_results(end).circular_mean = trial_circular_mean;
trial_results(end).circular_variance = trial_circular_variance;
% Retrieve or initialize results for the current audio file
if isKey(audiofile_results, filename_without_prefix)
results = audiofile_results(filename_without_prefix);
else
results = struct('means', [], 'variances', []);
end
% Append current trial results to the audio file results
results.means(end+1) = trial_circular_mean;
results.variances(end+1) = trial_circular_variance;
% Update the audio file results in the map
audiofile_results(filename_without_prefix) = results;
end
end
end
Full filename:
resources/44_prosody_eighths.mp3
Index of matching Rhythmsequence:
2
Index of matching Rhythmsequence:
No matching Rhythmsequence found for 44_neutral_eighths.mp3
Full filename:
resources/44_prosody_eighths.mp3
Index of matching Rhythmsequence:
2
Index of matching Rhythmsequence:
No matching Rhythmsequence found for 44_prosody_eighths.mp3
Full filename:
resources/44_prosody_eighths.mp3
Index of matching Rhythmsequence:
2
Index of matching Rhythmsequence:
No matching Rhythmsequence found for 44_reverse_eighths.mp3
Full filename:
resources/44_prosody_eighths.mp3
Index of matching Rhythmsequence:
2
Index of matching Rhythmsequence:
No matching Rhythmsequence found for 78_neutral_eighths.mp3
Full filename:
resources/44_prosody_eighths.mp3
Index of matching Rhythmsequence:
2
Index of matching Rhythmsequence:
No matching Rhythmsequence found for 78_prosody_eighths.mp3
Full filename:
resources/44_prosody_eighths.mp3
Index of matching Rhythmsequence:
2
Index of matching Rhythmsequence:
No matching Rhythmsequence found for 78_reverse_eighths.mp3
>>
So what I expect and actually did manage to get is an overall comparison of synchronization data across 24 trials, as well as the comparison of trials of specific audio files. I end up getting something like this
Summary for all trials combined: Circular Mean: 2.109612 radians Circular Variance: 0.058917
Audio File: 78_neutral_eighths.mp3 Trial 1 - Circular Mean: 2.918635 radians, Circular Variance: 0.059712 Trial 2 - Circular Mean: 2.918385 radians, Circular Variance: 0.060746 Trial 3 - Circular Mean: 2.918424 radians, Circular Variance: 0.060585 Trial 4 - Circular Mean: 2.918635 radians, Circular Variance: 0.059712
Trial Number: 24 Filename: resources/44_prosody_eighths.mp3 Circular Mean: 2.109612 radians Circular Variance: 0.058917
And so I want this on a circular histogram for each audio file (I didn't include the plotting section), showing the tapping data in different colors (e.g. for audio file n we have tapping data for trial n1 in green, trial n2, in blue, etc.)
The plots are unfortunately not generating and I suspect it's cause there is an issue with the index? Is there a way to fix this?
  2 Comments
Andrea
Andrea on 4 Jun 2024
Hello! I have read through the link, thanks so much! I also included the files. I still find myself hitting a wall - I think I might be having a problem with properly extracting the data from the participant file because I end up generating a variable called matched_keyTap_rt which is filled with just commas rather than the timestamps.

Sign in to comment.

Answers (1)

Divyajyoti Nayak
Divyajyoti Nayak on 29 Aug 2024
Edited: Divyajyoti Nayak on 2 Sep 2024
Hey Andrea,
Like you mentioned, the issue is in fact in the data extraction from the participant data file. In the data file, the rows of the ‘keyMaprt’ column contains an “array” of numbers enclosed in square brackets but when this is read by MATLAB, it will be considered as an array of characters and not numeric data. That’s the reason the ‘matched_keyTap_rt’ variable gets populated with commas and square brackets.
To fix this issue, before finding the index of closest tapping timestep you should convert the character array to an array of doubles. You can make use of the ‘erase’ and ‘split’ functions to do so. Here are the documentation links for them:
Here’s your code that I edited to fix the issue:
if i <= numel(keyTap_rt)
% Iterate over each note onset and find the closest tapping event
for k = 1:numel(note_onset_times)
%Remove square brackets from character array
keyTap_rt_arr = erase(keyTap_rt{i}, {'[',']'});
%Split the array with comma delimiter
keyTap_rt_arr = split(keyTap_rt_arr,',');
%Convert the string array into double array
keyTap_rt_arr = str2double(keyTap_rt_arr);
% Find the index of the closest tapping timestamp to the current note onset timestamp
[~, closest_idx] = min(abs(keyTap_rt_arr - note_onset_times(k)));
% Match participant's tapping timestamp with note onset timestamp
matched_keyTap_rt = [matched_keyTap_rt; keyTap_rt_arr(closest_idx)];
matched_Rhythmsequence = [matched_Rhythmsequence; full_filename];
% For the first note, set the timestamp to 0.0
if k == 1
matched_Notetiming = [matched_Notetiming; 0.0];
else
matched_Notetiming = [matched_Notetiming; note_onset_times(k)];
end
end
end

Categories

Find more on Audio I/O and Waveform Generation in Help Center and File Exchange

Products


Release

R2024a

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!