writecell writes empty value for the first row in a for-loop

I have this .mlapp from App Designer, and I'm using it to loop through some audiofiles and then record what the listener heard. The listener types what they hear in the sentence into an edit text input (app.response), and then presses "Enter" to record the answer and advance to the next stimulus. When I look at the excel file at the end, it shows the recorded response, but it's all offset by 1 row and the last response is never recorded. It's like somehow the waitfor isn't working right, and it's saving the response before it collects the information from the edit text.
I feel like this should be really straighforward, but it's taken me hours now to figure out - so I've come to the community. What am I missing? I feel like it's something to do with the "waitfor" and setting the current character, while changing the current character by typing out a response? I can't get it right though.
function BeginButtonPushed(app, event)
%set default character on keyboard to 0 (so we can advance by
%pressing Enter later)
app.UIFigure.CurrentCharacter = char(0);
%access stimuli
task = 'Type_babble_1'; %Set task name
stim_dir=[cd '\stimuli']; %set directory for stimuli
FileList = dir(fullfile(stim_dir,'*.wav')); %create list of stimuli
%loop that controls stimulus playback
for i=1:length(FileList)
app.Counter.Value = length(FileList)+1-i; %adding a number for the counter
%load the signal
signalpath = [stim_dir '\' FileList(play_order(i)).name];
[fnames,fs]=audioread(signalpath);
%present stimulus
duration = length(fnames)/fs;
sound(fnames,fs);
pause(duration)
%wait to collect response
app.UIFigure.CurrentCharacter = char(0);
waitfor(app.UIFigure,'CurrentCharacter', char(13))
app.UIFigure.CurrentCharacter = char(0);
%collect trial information and write it to the file.
sentence_code=FileList(play_order(i)).name;
output = [cellstr(app.ID.Value) cellstr(task) cellstr(sentence_code) cellstr(app.response.Value)];
disp(output)
writecell(output,[app.ID.Value '-' task '-data.xlsx'],"Sheet",1,'WriteMode','append');
end
fclose('all');
msgbox('You''re done!')
end

14 Commenti

I think it is because char(0) is NULL, set your CurrentCharacter property like below and try
app.UIFigure.CurrentCharacter = '';
Picture of the output I'm getting. The first sound (row 2) was "Michael", but it didn't record it until the next iteration. What was supposed to be the 3rd name never got recorded.
Maybe this as well: app.Counter.Value = length(FileList)+1-i
You didn't show how do you obtain the app.ID.Value.
Debugging is also helpful and can help you track the issue.
When setting CurrentCharacter to "" instead of char(0)
"Error setting property 'CurrentCharacter' of class 'Figure':
Value must be a char."
I've gone in through the debugger and it works when I step through it manually, which makes me feel like it's an issue with this waitfor and triggering vs when it pulls data.
app.ID.Value is recording correctly on each row- but it's just another edit text element that doesn't change during the course of the experiment.
I see now, it is impossible for CurrentCharacter to have no value.
Just an idea to test, I don't want to make an app now to test things. Maybe the second line appends the NULL to the edit field which is being currently edited.
waitfor(app.UIFigure,'CurrentCharacter', char(13))
% app.UIFigure.CurrentCharacter = char(0);
app.UIFigure.CurrentCharacter = '+';
Unfortunately, that didn't work either. I also tried placing the currentCharacter reset before the waitfor, but it didn't work either.
If possible, a minimal reproducible example would be great.
It doesn't make any sense to me that Response is shifted by a single row.
I would suggest to inspect ASCII values of app.response.Value, maybe it contains some leftovers of 0, 10, 13?
double(app.response.Value)
dpb
dpb il 20 Set 2023
Modificato: dpb il 20 Set 2023
waitfor doc says the object "can be the child of a Figure object created with the figure or uifigure function, or it can be the child of a container in a Figure object."
It doesn't say anything about it being the UIFigure object itself.
It appears the content of the edit text control isn't actually updated when your code is checking it -- I don't know if a drawnow would cause a pending internal callback to be executed or not; I've never tried to mess at such a low level with gui inputs. If that doesn't solve it, you'll have to figure out just what it does take to get the content of the edit field actually updated/refreshed internally to the content on the screen before reading it. I'd wonder about stuffing that (0) into the current key buffer immediately, certainly.
I'd wager (at least a cup of coffee, if not the house) that if you preset the content of the response text edit field to some initial dummy value you'll see that come up in the first cell that would essentially prove the hypothesis.
ADDENDUM: Further rambling wonderings/musings...
a. Wonder if stuffing the char(0) is generating the first CR internally, maybe, before the user even gets to the edit field?
b. Continuation of the first --the object response you're wanting to wait on would be in the text edit field itself; the way it's coded, anywhere while the figure has focus would be good enough.
c. Use the <value changed> callback function for the edit text box itself instead -- it is only called when the user either hits <return> or switches focus away (and in use you can't prevent the user from doing so in which case the "13" will not necessarily ever happen. I'd have to dig into the overal functionality to see how to structure it to use it instead of messing with the current, but I think it's not the right way (as you're finding in trying to get it to work as desired).
d. A <value changing> callback function instead would give you access every keystroke as well as for the <return> for change of focus; just ignore anything until the magic end...
Not the issue, but
signalpath = [stim_dir '\' FileList(play_order(i)).name];
should be
signalpath=fullfile(FileList(play_order(i)).folder,FileList(play_order(i)).name);
You did well in using fullfile() earlier, but fell into the string construction trap later...
re: Addendum - I filled in the char with char(65) (uppercase A), and it didn't save an "A" to the array - it logs a 0 x 0 char in the cell prior to writing the first cell.
I've attached a minimum reproducible example of the app. You just need a "stimuli" folder in the same directory with some short audio files in it (tried to upload, but .wav file isn't allowed here) and a "babble" file (another audio file - any one will do, just make sure the name matches - it's hard-coded in the program) and it should run.
Thanks!
Thanks for the fullfile improvement suggestion!
dpb
dpb il 20 Set 2023
Modificato: dpb il 20 Set 2023
Did you try inserting a drawnow first after the waitfor to see if that would update the field?
Stuffing a character into the buffer isn't the same as initializing the edit field content first so I'm not sure you actually did initialize the content of the edit field. If it were a default property on initialization and then still showed up as empty it would indicate somewhere your code wiped that field out before the loop which would then also coincide with getting the content the next pass.
One possible kludge -- test the length of the returned field -- if it is zero-length, then just continue the loop -- although you would then probably have to execute the loop N+1 times instead of N.
I still think the real solution will be to use the callback function for the text field object itself instead; this just seems excessively complex besides the issue of what if the user doesn't follow directions and leaves the control instead of hitting <return>.
dpb
dpb il 20 Set 2023
Modificato: dpb il 20 Set 2023
OBTW, you can attach zip file that would have needed data...I, for one, would have to do quite a bit of work to get any wav files handy; I simply don't use them in anything I ever do so there aren't any just laying around where I know where they are...although I know there's a whole repository of stuff MS loads, I have no idea about where. Make it simple...
A minimal example would just loop picking up user response; wouldn't even have to play anything...
Here's the zip thanks for the suggestion. I'm worried I'm not experienced enough to reduce the code by much more without breaking it as you suggested. I will try it and will post if I'm successful.
Thank you for the helpful suggestions.

Accedi per commentare.

 Risposta accettata

@Shae Morgan: I think @dpb is exactly right with his comments - the root cause of the problem is that the edit box Value is not updated yet when you try to use it to write to file. In my tests, drawnow doesn't seem to fix it, and I found that presetting a non-empty string in the edit box does indeed cause the string to be written to file on the first iteration (as dpb predicted and contrary to what you observed, strangely).
I also agree with dpb that using the ValueChangedFcn of the edit box is the way to go, rather than relying on the uifigure's CurrentCharacter. The attached app has the structure necessary to do the job as you originally intended/imagined. In my tests, it correctly writes all the information to file.
(Note that I removed the masking/level logic because it wasn't running with the wav files of random data I created for testing, and because that stuff is not relevant to the problem at hand, so you'll have to reintroduce that logic in the right place - in the play_current_sound function, I imagine.)

9 Commenti

"... and I found that presetting a non-empty string in the edit box does indeed cause the string to be written to file on the first iteration (as dpb predicted and contrary to what you observed, strangely)."
@Voss, @Shae Morgan didn't actually preload the text box in his test; he just stuffed a single "A" in the character buffer, not the same thing at all.
That makes sense. I must've misunderstood @Shae Morgan's statement.
dpb
dpb il 20 Set 2023
Modificato: dpb il 20 Set 2023
@Voss: nicely done! The obvious to simply move all the "done" stuff for each pass into the callback function; I was still thinking about how to signal to the "Begin" button callback when the other callback event occurred...then again, I'm really not a GUI designer/coder; my thing was to compute stuff and if a UI is needed, always had a cadre around who did that sort of stuff...
@Shae Morgan: Follow up -- what's going on here and why yours acts the way it does---
Your code waits for a CR from the figure; assuming the user does follow instructions, then that does signify he completed the answer for the given soundtrack and the callback is initiated. BUT, that callback only signifies that the character has been entered and while the edit text control does show the input content on the screen, the internal callback to actually update the control .Value field doesn't happen with every keystroke but only when the user is done. That also is signified by the CR character (or leaving focus) but that callback occurs only after the form character update callback you used and callbacks are generally not interruptible so while the form displays one thing, for a little while until the control callback is executed, the display and the control data are out of synch.
That's why you were always one step behind--and if you ran it in debug mode to see what was happening, then the callback got interrupted and the .Value field got updated and you thought it should work. Easy thing to conclude; the test @Voss did of loading the edit text with a default value and it getting retrieved the first time illustrates conclusively (although that it was a blank field initially also did because that was what your initial value was, just harder to tell where it came from).
Thank you all for the comments and answers. This makes sense, I'm just used to javascript/html where if the text exists, then it's rendered and something you can capture. Appreciate the help and info!
Can I get some clarification on one more thing just so I can understand? Why does pressing enter ("return") trigger the valueChanged function? Does it move the focus from the text input, which triggers that the user is "done" typing? alternatively, one could use the valuechanging function and monitor for a char(13) response - but that seems unnecessary given the current solution. Am I on the right track?
dpb
dpb il 21 Set 2023
Modificato: dpb il 21 Set 2023
"...Why does pressing enter ("return") trigger the valueChanged function?"
It's how the UI was designed to operate. Nota bene a change of focus by mouse click elsewhere also triggers it.
I do not believe <RETURN> changes focus, but it may, I simply haven't used it enough to know otomh. The uieditfield doc doesn't actually say what the behavior is; you'll have to see what happens.
If you were to use the valueChanging callback, it would generate a callback every keypress of the user which is way more overhead than needed, but essentially the same as your original excepting it is then constrained to be a keystroke actually in the control whereas your original would be anything anywhere in the app figure and the user changing focus other than by the <RETURN> key would not generate the callback for that but would continue to fire for whatever keys did press. That's not the way you intend for the app to be used, but you can't prevent the user from not following instructions precisely either by accident or by trying to break it.
dpb
dpb il 21 Set 2023
Modificato: dpb il 21 Set 2023
"... I'm ... used to javascript/html where if the text exists, then it's rendered and something you can capture."
Capturing from the screen buffer would be something different; you were/are returning the internal state of the control .Value property, that's a different thing than the display buffer. As the ValueChanging callback function describes, there's a background ValueChangingData object that is the buffer holding the user text as being type that you can access/query from the ValueChanging callback function that will look like what's on the screen...but the actual control .Value property itself isn't updated until the user either hits <return> or switches focus away from the control, and then only when the ValueChanged callback executes which is, as noted above, after the figure keystroke callback has completed.

Accedi per commentare.

Più risposte (0)

Categorie

Scopri di più su Creating, Deleting, and Querying Graphics Objects in Centro assistenza e File Exchange

Prodotti

Release

R2022b

Richiesto:

il 20 Set 2023

Modificato:

dpb
il 21 Set 2023

Community Treasure Hunt

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

Start Hunting!

Translated by