MATLAB Answers

User surface on piano

9 views (last 30 days)
Alina Panzel
Alina Panzel on 28 Oct 2019
Commented: Alina Panzel on 28 Dec 2020 at 11:05
Hey there!
I am new to Matlab and stuck with a problem: I have plotted a piano keyboard with notes for an experimental set up. (Will post it below) now I would like to set up
a user surface where the participants can mouse click onto the keyboard keys as a response which is then saved in a datafile. I'm not sure if we are aloud to use appdeveloper and as far as I understood it it would rather work with an uploaded picture. I've also considered ginput (and sort out the areas afterwards) but that would leave me with a lot of unnecessary data. How do I set up a interactive grids onto each of my keyboard keys?
Thank you so much for anyone whos answering!!
Bildschirmfoto 2019-10-28 um 17.16.56.png

  0 Comments

Sign in to comment.

Answers (3)

Thomas Satterly
Thomas Satterly on 28 Oct 2019
Matlab has button click callbacks for graphical types. There's always multiple ways to approach a problem, but this is the route I'd take:
function testPiano
% Create a figure and axis
fh = figure;
ax = axes;
% Make a button click response for the axis
ax.ButtonDownFcn = @(src, event) mouseClickCallback(src, event);
ax.Color = [0.8 0.8 0.8]; % Make the axis light grey
% Make two rectangular keys out of patches
% Using the "UserData" property to store what key it is here. You could
% also pass extra arguments throught the callback (e.g., a third input)
whiteKey = patch([0 0.5 0.5 0 0], [0 0 1 1 0], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'C1');
blackKey = patch([0.25 0.75 0.75 0.25 0.25], [0.35 0.35 1 1 0.35], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'C#1');
% Make the keys respond to mouse click
whiteKey.ButtonDownFcn = @(src, event) mouseClickCallback(src, event);
blackKey.ButtonDownFcn = @(src, event) mouseClickCallback(src, event);
% Probably want this to make sure the X and Y axis have the same step size
ax.DataAspectRatio = [1 1 1];
function mouseClickCallback(src, event)
% Single callback function, but switching behavior based on the
% source of the callback
switch class(src)
case {'matlab.graphics.primitive.Patch'}
% Patch clicked, so let's respond with the key value we
% stored
fprintf('Clicked key %s\n', src.UserData);
case {'matlab.graphics.axis.Axes'}
% Axis clicked, so let's respond with the (X, Y) position
% that was clicked
fprintf('Clicked axis at point %0.3f, %0.3f\n', src.CurrentPoint(1, 1), src.CurrentPoint(1, 2));
otherwise
fprintf('Unknown object of type ''%s'' clicked\n', class(src));
end
end
end
This demo code uses callbacks from patches and axes to make some kind of unique response. If you're going the "grid" route, you'll have to supply your own logic for detecting which key you're actually over using the mouse position data. As a side note, if you don't want something to intercept mouse click events (like text labels), just turn the "HitTest" property of the object to "off".

  4 Comments

Show 1 older comment
Alina Panzel
Alina Panzel on 29 Oct 2019
Just to check the vectors are supposed to be on top of every key so I'll have to put new vector values in and add one white key for every white key and so on..
'patch(X,Y,C) adds the filled two-dimensional patch to the current axes. The elements of X and Y specify the vertices of a polygon. If X and Y are matrices, MATLAB draws one polygon per column. C determines the color of the patch. It can be a single ColorSpec, one color per face, or one color per vertex (see "Remarks"). If C is a 1-by-3 vector, it is assumed to be an RGB triplet, specifying a color directly.'
Is it C ? and if so, why would it be 0.25 on x and 0.35 on y in the black key, wouldnt it be the same?
Thomas Satterly
Thomas Satterly on 29 Oct 2019
Regarding a X and Y being 5 elements instead of 4 - that's because I made a mistake (must have been thinking in closing the loop myself). The first four points are all that's needed.
The 0.25 on X and 0.35 on Y was just to offset the rectangle to the right and move the bottom edge up to look more like where a black key should be - but purely made up numbers. You'll probably want to make a couple loops to draw each white and black key. One more note: the draw order will stack the last created patch on top, but you can use "uistack" to force the draw order of a graphics object after it's created, but if you draw all white keys first, then all black keys, you won't have to adjust the order.
Alina Panzel
Alina Panzel on 29 Oct 2019
Perfect than I've done everything right I've created new 4 object vectors and created every white and then all the black keys :) thank you so much!

Sign in to comment.


Alina Panzel
Alina Panzel on 30 Oct 2019
Dear Thomas,
Unfortunatly I have another problem, I managed to make everythign work for every key in the keyboard but when I started moving on to saving it in the text file it started writing itself over instead of adding data into the textfile. Also, I had to put the fileID etc into the function in the bottom otherwise it did not find it although I tried to make it global. That leaves me wondering if there is an element in the function below that interefering with the data collection ? What should I be looking for?
Looking forward to hearing from you!
Alina

  1 Comment

Thomas Satterly
Thomas Satterly on 30 Oct 2019
Could you post your code? Without seeing the code, I would ensure that you are only using fopen once, or if that is not possible, make sure you are using the permission 'a+' to append to the existing file rather than overwriting it.

Sign in to comment.


Alina Panzel
Alina Panzel on 5 Nov 2019
% Create a figure and axis
fh = figure;
ax = axes;
% creates a button click response for the axis
ax.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
%
ax.Color = [0.8 0.8 0.8]; % Makes the axis light grey
% Make two rectangular keys out of patches
% Using "UserData" to store what key it is .
% extra arguments throught the 'callback' (e.g., a third input)? Look up
% again
whiteKey1 = patch([0 1 1 0], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'C1');
whiteKey2 = patch([1 2 2 1], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'D1');
whiteKey3 = patch([2 3 3 2], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'E1');
whiteKey4 = patch([3 4 4 3], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'F1');
whiteKey5 = patch([4 5 5 4], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'G1');
whiteKey6 = patch([5 6 6 5], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'A1');
whiteKey7 = patch([6 7 7 6], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'H1');
whiteKey8 = patch([7 8 8 7], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'C2');
whiteKey9 = patch([8 9 9 8], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'D2');
%111is white,000is black
blackKey1 = patch([0.6 1.4 1.4 0.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'C#1');
blackKey2 = patch([1.6 2.4 2.4 1.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'D#1');
blackKey3 = patch([3.6 4.4 4.4 3.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'F#1');
blackKey4 = patch([4.6 5.4 5.4 4.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'G#1');
blackKey5 = patch([5.6 6.4 6.4 5.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'A#1');
blackKey6 = patch([7.6 8.4 8.4 7.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'C#2');
%same here, putting it in seperate names to avoid overwriting
% creates letters
text(0.4,1.5,'C')
text(1.4,1.5,'D')
text(2.4,1.5,'E')
text(3.4,1.5,'F')
text(4.4,1.5,'G')
text(5.4,1.5,'A')
text(6.4,1.5,'H')
text(7.4,1.5,'C')
text(8.4,1.5,'D')
%creates smaller, white letters on black keys
text(0.85,4,'C#','Color','white','FontSize',9)
text(1.85,4,'D#','Color','white','FontSize',9)
text(3.85,4,'F#','Color','white','FontSize',9)
text(4.85,4,'G#','Color','white','FontSize',9)
text(5.85,4,'A#','Color','white','FontSize',9)
text(7.85,4,'C#','Color','white','FontSize',9)
text(1.5,0,'Now click the matching key on the piano')
% Makes the keys respond to mouse click
whiteKey1.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey2.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey3.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey4.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey5.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey6.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey7.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey8.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey9.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
blackKey1.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
blackKey2.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
blackKey3.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
blackKey4.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
blackKey5.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
blackKey6.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
% makes sure the X and Y axis have the same step size; not sure if needed
% seen somewhere
ax.DataAspectRatio = [1 1 1];
function ClickresponseCallback(src, event)
% exp_directory = "/Users/alinapanzel/Documents/MATLAB/result.txt";
% fileID = fopen(exp_directory'w');
% callback function, but switching behavior based on the source of the callback
%Mathworks
switch class(src)
case {'matlab.graphics.primitive.Patch'}
%this is from a mathworks community member
% Patch clicked, so let's respond with the key value we
% stored fprintf writes data into a textfile
%first input is a string of what will be displayed in the command window
% %d = decimal notation
% \n = new line \t = tab space \
% second input assigns decimal(%d) in the string of value
%this is from a mathworks community member
exp_directory = "/Users/alinapanzel/Documents/MATLAB/result.txt";
fileID = fopen(exp_directory,'w','a');
fprintf(fileID, 'Clicked key\n\n');
fprintf(fileID,'%f %f\n','a+', src.UserData);
fclose(fileID);
type result.txt
%fprintf('Clicked key %s\n', src.UserData);
%fprintf(fileID 'Clicked key %s\n', src.UserData);
%case {'matlab.graphics.axis.Axes'}
% Axis clicked, so let's respond with the (X, Y) position
% that was clicked
%this is from a mathworks community member
%fprintf('Clicked axis at point %0.3f, %0.3f\n', src.CurrentPoint(1, 1), src.CurrentPoint(1, 2));
%otherwise
%fprintf('Unknown object of type ''%s'' clicked\n', class(src));
end
end
It saves the first click and adds a second but afterwards it just starts writing itself over and over :/
Thank you so much for your help so far, you've teached me a lot already :)

  2 Comments

Thomas Satterly
Thomas Satterly on 5 Nov 2019
You're pretty close, but file IO can be difficult to manage. Your current fopen statement has both 'w' and 'a' permissions on it - that's not really how you specify permissions, it should just be a single input. You could fix it by just using fopen(fileName, 'a') so that it only ever appends to the file if it already exists. Also, on your print statement, you should be using the format specification '%s' to signify that you are printing a string, not a number. When you have '%f' as a format spec, Matlab is converting over each character in your string to their number, which I don't think is what you want.
What I'd propose is moving towards a file ID that stays open for the duration of the script. This way, you won't be constantly opening and closing (which is generally a bit safer), and it will open you up to some more capabilities of Matlab.
First, in order to share a variable across multiple locations within a script, you can either use global variables or persistent variables hidden inside a function. Don't use globals, they are bad practice. Hiding a variable as a persistent variable inside of a function takes a little effort, but is much safer (see the getFileID() function).
Next comes how to close the file when you're done. You can take advantage of a figure's CloseRequestFcn callback (similar to a ButtonDownFcn callback) that executes when the figure is closed. Inside of this function, the file ID is retrived, closed, and then the figure itself deleted so that it actually closes.
These changes are reflected in the code below, along with a few more notes on persistent variables
% Create a figure and axis
fh = figure;
ax = axes;
% creates a button click response for the axis
ax.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
%
ax.Color = [0.8 0.8 0.8]; % Makes the axis light grey
% Make two rectangular keys out of patches
% Using "UserData" to store what key it is .
% extra arguments throught the 'callback' (e.g., a third input)? Look up
% again
whiteKey1 = patch([0 1 1 0], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'C1');
whiteKey2 = patch([1 2 2 1], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'D1');
whiteKey3 = patch([2 3 3 2], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'E1');
whiteKey4 = patch([3 4 4 3], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'F1');
whiteKey5 = patch([4 5 5 4], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'G1');
whiteKey6 = patch([5 6 6 5], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'A1');
whiteKey7 = patch([6 7 7 6], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'H1');
whiteKey8 = patch([7 8 8 7], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'C2');
whiteKey9 = patch([8 9 9 8], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'D2');
%111is white,000is black
blackKey1 = patch([0.6 1.4 1.4 0.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'C#1');
blackKey2 = patch([1.6 2.4 2.4 1.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'D#1');
blackKey3 = patch([3.6 4.4 4.4 3.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'F#1');
blackKey4 = patch([4.6 5.4 5.4 4.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'G#1');
blackKey5 = patch([5.6 6.4 6.4 5.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'A#1');
blackKey6 = patch([7.6 8.4 8.4 7.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'C#2');
%same here, putting it in seperate names to avoid overwriting
% creates letters
text(0.4,1.5,'C')
text(1.4,1.5,'D')
text(2.4,1.5,'E')
text(3.4,1.5,'F')
text(4.4,1.5,'G')
text(5.4,1.5,'A')
text(6.4,1.5,'H')
text(7.4,1.5,'C')
text(8.4,1.5,'D')
%creates smaller, white letters on black keys
text(0.85,4,'C#','Color','white','FontSize',9)
text(1.85,4,'D#','Color','white','FontSize',9)
text(3.85,4,'F#','Color','white','FontSize',9)
text(4.85,4,'G#','Color','white','FontSize',9)
text(5.85,4,'A#','Color','white','FontSize',9)
text(7.85,4,'C#','Color','white','FontSize',9)
text(1.5,0,'Now click the matching key on the piano')
% Makes the keys respond to mouse click
whiteKey1.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey2.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey3.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey4.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey5.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey6.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey7.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey8.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey9.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
blackKey1.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
blackKey2.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
blackKey3.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
blackKey4.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
blackKey5.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
blackKey6.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
% makes sure the X and Y axis have the same step size; not sure if needed
% seen somewhere
ax.DataAspectRatio = [1 1 1];
% Setup the close request callback so the file can be closed correctly
fh.CloseRequestFcn = @(src, event) closeKeyboard(src, event);
function ClickresponseCallback(src, event)
% exp_directory = "/Users/alinapanzel/Documents/MATLAB/result.txt";
% fileID = fopen(exp_directory'w');
% callback function, but switching behavior based on the source of the callback
%Mathworks
switch class(src)
case {'matlab.graphics.primitive.Patch'}
%this is from a mathworks community member
% Patch clicked, so let's respond with the key value we
% stored fprintf writes data into a textfile
%first input is a string of what will be displayed in the command window
% %d = decimal notation
% \n = new line \t = tab space \
% second input assigns decimal(%d) in the string of value
%this is from a mathworks community member
fileID = getFileID();
fprintf(fileID, 'Clicked key\n\n');
fprintf(fileID,'%s\n', src.UserData);
type('results.txt');
%fprintf('Clicked key %s\n', src.UserData);
%fprintf(fileID 'Clicked key %s\n', src.UserData);
%case {'matlab.graphics.axis.Axes'}
% Axis clicked, so let's respond with the (X, Y) position
% that was clicked
%this is from a mathworks community member
%fprintf('Clicked axis at point %0.3f, %0.3f\n', src.CurrentPoint(1, 1), src.CurrentPoint(1, 2));
%otherwise
%fprintf('Unknown object of type ''%s'' clicked\n', class(src));
end
end
function closeKeyboard(src, event)
% Close the file when the figure is closed so we save the data
try
fileID = getFileID();
fclose(fileID);
delete(src); % Delete the source figure once close operations have finished
catch err
% If anything goes wrong, report it, and delete the figure
warning('Failed to close out properly!')
disp(err);
delete(src)
end
end
function outFID = getFileID()
% This is a helper function to pass around the file ID through the
% script. Using this instead of globals because globals tend to be
% messy. In a more robust system, the whole keyboard should
% probably be made as a class, but for a simple demo, this will do
% just fine
fullFilePath = 'results.txt';
% Make the file identifier persistent so we don't lose it
persistent fid;
% One caveat to persistent variables is that they stick around
% until 'clear all' is used. If this script is run multiple times
% in a row without a 'clear all', the file ID will be leftover from
% a previous run and will now be invalid because 'fclose' was
% called, but it will not be empty. To test for this, we try to
% print an empty string to the file. If there is an error returned
% that matches an invalid file identifier, then it's time to open
% up a new file
doOpenFlag = false;
if isempty(fid)
doOpenFlag = true;
else
try
fprintf(fid, '');
catch err
if strcmp(err.identifier, 'MATLAB:badfid_mx')
% If we can't print to the file because it's invalid,
% that means that the persistent variable is leftover
% from a previous sessions that has ended. Go ahead and
% get rid of it and open a new file
doOpenFlag = true;
else
disp(err);
error('File ID is invalid for an unkown reason');
end
end
end
if doOpenFlag
% Only open the file if the identifier is empty
fprintf('Opening file ''%s''\n', fullFilePath);
fid = fopen(fullFilePath, 'w+');
end
% Cannot return persistent variables directly from a function, so
% pass it out through a dummy variable
outFID = fid;
end
Alina Panzel
Alina Panzel on 28 Dec 2020 at 11:05
Dear Thomas,
I just realised that I forgot to thank you! So here is a big big thank you for your help and attached youll find the final outcome. This wouldn't have been possible without your help and of course I gave you credit in my assignment. Thank you so much your help I learned a lot from it. Merry Christmas and a happy (better) next year!
% ------------------------------------------------%
clc;
clear all;
clear main;
%--------------------------------------------------%
%////////////// THE PERFECT PITCH EXPERIMENT \\\\\\\\\\\\\\\%
% ___________________Table of Contents_______________________
% 1.0 The main function: main()
% 2.0 The textfile function: write_txtfile
% 3.0 The Startscreen function: blank()
% 4.0 The Endscreen function: blank2()
% 5.0 The patch drawing funtion: draw()
% 6.0 The click info function: draw_check(pressed)
% 7.0 The Callback function: ClickresponseCallback(src,event)
% 8.0 The audioplayer funtion: play_sound(F)
%___________________________________________________________
% To provide a better overview, 1.0-8.0 are not only used within this
% script, but also in the journal. Since the script uses
% various data setting and retrieving elements, thus making the 'red line' more complex to follow,
% it can be useful to read the journal alongside whilst using the provided section numbers.
%--------------------------------------------------%
% These are all the used frequencies of the keyboard, using a
% structure arrays to implement them in different functions depending on the
% needed context used in example the .f for the frequencies in [8.0] or the
% .name for the strcmp in [6.2] and [2.8]
% [0.0] structure arrays
fs = 1e4; % sampling frequency
t = 1:1/fs:5; % time signal
f1.f = 264; % note frequency in Hz (middle C)
f1.name = 'C1';
f2.f = 297; % " (middle D)
f2.name = 'D1';
f3.f = 330; % " (middle E)
f3.name = 'E1';
f4.f = 352; % " (middle F)
f4.name = 'F1';
f5.f = 396; % " (middle G)
f5.name = 'G1';
f6.f = 440; % " (middle A)
f6.name = 'A1';
f7.f = 495; % " (middle H)
f7.name = 'H1';
f8.f = 528; % " (high C)
f8.name = 'C1';
f9.f = 280; % " (C#)
f9.name = 'C#1';
f10.f = 314;% " (D#)
f10.name = 'D#1';
f11.f = 370;% " (F#)
f11.name = 'F#1';
f12.f = 415;% " (G#)
f12.name = 'G#';
f13.f = 467;% " (A#)
f13.name = 'A#1';
f14.f = 555;% " (C2#)
f14.name = 'C#2';
f15.f = 588;% " (high D)
f15.name = 'D2';
values = [f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 f13 f14 f15];
% values = array of all the used frequencies, as displayed above
values_rand = values(randperm(length(values)));
% [0.01] values_rand creates a randomized order of all tones
% randperm is a function that allows a randomized order using every element
% in each procedure
% unlike randi, which solely uses every element once
% Tip: Trials will be going on for 15 times, therefore it is handy to
% comment out i.e. 12 frequencies
%------------------------------------------------%
setappdata(gcf,'values_rand',values_rand);
% sets information for 'values_rand' with the input of values rand above
% [0.1]setappdata stores information, which can be retrieved using the matching
% getappdata function, used similarly as global functions, but 'saver' and for GUIs, needed when
% using Callback functions as done in later context
% gcf stands for the current figure handle
% the syntax of set/getappdata is as followed:
% setappdata(obj,name,val)
% val = getappdata(obj,name)
setappdata(gcf,'tones',values_rand);
%sets appdata for 'tones' a variable later used within the text file
%function [2.0]
main();
% initiates the main function
%------------------------------------------------%
%1.0 - 1.16
function main()
% 1.0 main function which calls all the necessary functions
values_rand = getappdata(gcf,'values_rand');
% 1.1 retrieves information of the randomized values_rand from
% setappdata in [0.1]
if size(values_rand)>0
% 1.2 If the size of values_rand aka the randomized frequencies is
% greater than 0, initiate/ follow the if loop
blank();
% 1.3 blank() [3.0] displays the first start display with user
% instructions 'Listen Carefully'
pause(2);
% 1.4 A pause to give the user time to read 'Listen Carefully'
play_sound(values_rand(1).f);
% 1.5 Initiates sound function play_sound [8.0], taking the first frequency from values_rand
% using the structure array .f since the frequency is used
setappdata(gcf,'current',values_rand(1).name);
% 1.6 this sets and stores the value of the new variable
% 'current' which hols information of the first frequency in the
% randomly created list of values_rand, also the .name is used,
% since a string compare in [6.2] is performed
% Current & pressed [6.5] are the variables used for the click color
% feedback in [6.2]
values_rand = values_rand(2:end);
% 1.7 This step changes values_rand, thus after this point the
% second frquency is 'picked' until the end
% This step allows the loop to move along the list of frequencies
% in values_rand until the every element was used, than the value
% would be <0 therefore initiating the else loop
setappdata(gcf,'values_rand',values_rand);
% 1.8 this sets up the setappdata for the 'new' values rand that
% has just been assigned in [1.7]
pause(3)
% 1.9 adds another Pause after the sound was played
draw();
% 1.10 initiated the draw function which draws the interactive piano
% patches, since I'm using buttondownfnc in combination with a
% callbackfunction there is no need to 'call' the
% callbackfunction [7.0] as they interact naturally, yet the
% Clickcallback function executes the draw_check function and
% gives it the Userdata input, than referred to as pressed.
else
tones = getappdata(gcf,'tones');
% 1.11 this retrieves the information of 'tones', set up in [0.1]
% and used in [2.6] for the data collection. Tones & guesses are
% the variables set up for the textfile function
tones = arrayfun(@(x) x.name,tones,'UniformOutput',false);
% 1.12 This function was created for [2.0] since the keys
% were printed as frequencies, yet the matlab syntax didn't allow
% me to use the .name structure array, thus I had to implement it in this function to
% be able to use the boolean in [2.8] and fprintf in [2.9]. It
% was sourced from the matlab forum
guesses = getappdata(gcf,'guesses');
% 1.13 This function retrieves information from the setappdata in
% [6.3] since the guessed key is not a known factor from the
% beginning, the data had to be set up in the draw check
% function
filename = 'test.txt';
% 1.14 This defines the filename, thus collected data will be
% found in test.txt!
write_textfile(filename,tones,guesses);
% 1.15 Initiates the function that writes the textfile, given inputs
% filename [1.14], tones [1.12] and guesses [6.3,1.13], defined in [2.0]
blank_2()
% 1.16 Initiated the function, creating the end screen [3.0]
end
end
%2.0- 2.9
function write_textfile(filename,tones,guesses)
% 2.0 This function writes the text file and was sourced from a Fitzle
% Production
Title_1 = 'The Perfect Pitch Experiment';
% 2.1 This line will create the headline of the text file
Label_1 = 'Played ';
Label_2 = 'Clicked';
Label_3 = 'Correct';
% 2.2 Label 1-2 will create the headlines of the seperate columns
fid = fopen(filename,'w+t');
% 2.3 fid = fopens creates and openes a textfile, 'w+t' will allow to
% read and write within the textfile
if fid < 0
% This if case is made to display an error, therefore if the value of
% fid is less than zero it will ..
fprintf('error opening file\n');
% 2.4 move on to fprintf which will print the error message within
% the command window
return;
% If the error message appeared this retrun function allows the
% function to go back to the If case
end
fprintf(fid,'%s\n\n\n', Title_1);
% 2.5 fprintf is not only used for printing intp the command video but
% also for printing into text file. Its syntax is fprintf(fileID,formatSpec,A1,...,An)
% therefore fid was previously defined to create or open, read or
% write in a textfile, the format specification for the title is a
% string therefor %s and to make it a propper headline three 'enter's
% away from the rest (\n\n\n)
fprintf(fid,'%s %s %s\n\n',Label_1,Label_2,Label_3);
% Same for the column heads, yet there are 3 lables therefore three
% strings %s and spaces inbetween them to make it neater. Also 2
% spaces downwards to create some space between headline and data
for i = 1:length(tones)
% 2.6 this for loop makes sure that only as many clicks will be saved as
% tones were played
tone = tones(i);
% therefore tone(the value that is later saved) is defined as 1 at
%a time until the end of all tones
tone = strcat(tone," ");
% 2.7 Since my keys are not the same length i.e. C1 = 2 spaces, C# =
% 3 spaces, I needed to find a solution to still have them look
% neat in a column with a trick: strcat concatenates rings horizontally
% therefore I 'glued' spaces to my keys (using "" instead of '' since '' printed it each letter at a time)
tone = extractBetween(tone,1,10);
% and afterwards extracted spaces again so every row would have
% exactly 10 spaces for both tones and guesses
guess = guesses(i);
guess = strcat(guess," ");
guess = extractBetween(guess,1,10);
% same procedure with the guesses
correct = string(strcmp(tone,guess));
% 2.8 This is a neat tip sourced from a youtube video, a so-called
% Boolean which simply states if the comparison is true aka euqal
% or false aka not equal. Also, string and stringcompare are used
% since my input is not a number but a string (tones, guesses)
% Since this is the last column I didnt use the
% strcat/extractbetween command
fprintf(fid,'%s%s%s\n',tone,guess,correct);
% 2.9 this function saves the tone, guess and boolean data into a textfile
% Since I have used the strcat and extractbetween technique there
% is no need to put spaces between the strings as in the lable
% example
end
fclose(fid);
% This closes the file at the end
end
%3.0- 3.2
function blank()
% 3.0 This function creates the start screen syntax can be seen in []
patch([0 9 9 0], [1 1 7 7],[0 0.4470 0.7410])
% 3.1 It is patched the same way as the keyboard notes [] just larger
text(3.3,4,'Listen carefully','Color','white','FontSize',16)
% 3.2 This creates the text on top of the large patch that was created
% before
end
%4.0- 4.2
function blank_2()
% 4.0 This function creates the end screen, Syntax of Patch creating can be
% seen in []
patch([0 9 9 0], [1 1 7 7],[0.9290 0.6940 0.1250])
% 4.1 this creates the large patch
text(2.5,4,'Thank you for participating','Color','white','FontSize',16)
% 4.2 this creates the Thank you text at the end
end
%5.0- 5.3
function draw()
% 5.0 This function draws the patches for the pino keys
%draws Whitekeys patches
whiteKey1 = patch([0 1 1 0], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'C1');
whiteKey2 = patch([1 2 2 1], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'D1');
whiteKey3 = patch([2 3 3 2], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'E1');
whiteKey4 = patch([3 4 4 3], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'F1');
whiteKey5 = patch([4 5 5 4], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'G1');
whiteKey6 = patch([5 6 6 5], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'A1');
whiteKey7 = patch([6 7 7 6], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'H1');
whiteKey8 = patch([7 8 8 7], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'C2');
whiteKey9 = patch([8 9 9 8], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'D2');
% 5.1 Syntax :name = patch([x1 x2 x3 x4], [y1 y2 y3 y4], [RGB Triplet for Colors],
%Edgecolor [RGB], 'Userdata' as information handle for a later stage [7.3],
%'Name of the key')
%[111] is white,[000] is black, [0.1 0.1 0.1] slightly lighter black
% This function draws the black keys, same structure as above,
blackKey1 = patch([0.6 1.4 1.4 0.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'C#1');
blackKey2 = patch([1.6 2.4 2.4 1.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'D#1');
blackKey3 = patch([3.6 4.4 4.4 3.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'F#1');
blackKey4 = patch([4.6 5.4 5.4 4.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'G#1');
blackKey5 = patch([5.6 6.4 6.4 5.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'A#1');
blackKey6 = patch([7.6 8.4 8.4 7.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'C#2');
% creates letters of Key names
% has to be following in order to be on 'top' of the keys
text(0.4,1.5,'C')
text(1.4,1.5,'D')
text(2.4,1.5,'E')
text(3.4,1.5,'F')
text(4.4,1.5,'G')
text(5.4,1.5,'A')
text(6.4,1.5,'H')
text(7.4,1.5,'C')
text(8.4,1.5,'D')
% 5.2 Syntax: Text (x,y,'Name')
%creates smaller, white letters on black keys
text(0.85,4,'C#','Color','white','FontSize',9)
text(1.85,4,'D#','Color','white','FontSize',9)
text(3.85,4,'F#','Color','white','FontSize',9)
text(4.85,4,'G#','Color','white','FontSize',9)
text(5.85,4,'A#','Color','white','FontSize',9)
text(7.85,4,'C#','Color','white','FontSize',9)
% Makes the keys respond to mouse click using buttondownfcn and a Callback
% function
whiteKey1.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey2.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey3.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey4.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey5.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey6.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey7.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey8.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
whiteKey9.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
blackKey1.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
blackKey2.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
blackKey3.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
blackKey4.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
blackKey5.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
blackKey6.ButtonDownFcn = @(src, event) ClickresponseCallback(src, event);
% 5.3 First a the callback function needs to be created that MATLABÆ executes
% when users left-click on the graphics object thus the ClickresponseCallback
% Than a function handle that references the callback function to the
% ButtonDownFcn property of the object needs to be assigned, in this case
% the src, event (event could also be a ~, not needed for syntax)
% Writes User instruction underneath the keyboard
text(1.5,0,'Now click the matching key on the piano')
end
%6.0- 6.7
function draw_check(pressed)
% 6.0 This function provides information of what key was clicked and gives direct color feedback
% using input information of Userdata from 8.0
% starting with creating patches again...
whiteKey1 = patch([0 1 1 0], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'C1');
whiteKey2 = patch([1 2 2 1], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'D1');
whiteKey3 = patch([2 3 3 2], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'E1');
whiteKey4 = patch([3 4 4 3], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'F1');
whiteKey5 = patch([4 5 5 4], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'G1');
whiteKey6 = patch([5 6 6 5], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'A1');
whiteKey7 = patch([6 7 7 6], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'H1');
whiteKey8 = patch([7 8 8 7], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'C2');
whiteKey9 = patch([8 9 9 8], [1 1 7 7], [1 1 1], 'EdgeColor', [0 0 0], 'UserData', 'D2');
current = getappdata(gcf,'current');
% 6.1 This retrieves the information of the setappdata in [1.6] the value
% of current is the played frequency
if strcmp(current,pressed)
% 6.2 strcmp compares strings in their content equality so if they are the
% same..
col = [0.4660 0.6740 0.1880];
% ..a nice green will be displayed
else
%Otherwise in the else case
col = [0.6350 0.0780 0.1840];
% .. a nice red will be displayed
end
guesses = getappdata(gcf,'guesses');
% 6.3 Guesses is the guessed cliked key after the tone, this variable is used
% in the write_textfile function, I separated guesses/tones for the frpintf
% and current/pressed for the draw_check red/green info for my own
% understanding in order to see directly where the function belongs to
if ~isempty(guesses)
% ~ is a negative operator, this function was given by matlab, the
% original solely stated that is guesses > 0, needed since guesses
% could be potentially empty at this stage, thus this case needs to be
% defined as well
setappdata(gcf,'guesses',[guesses string(pressed)]);
% 6.4 In this case, guesses is not empty, therefore the previous
% value of guesses is used and pressed is assigned to it
else
setappdata(gcf,'guesses',[string(pressed)]);
% Otherwise a new guesses is set up and pressed is assigned to it
% both caused syntay errors at first which is why they had to
% referred to as [string()] for matlab to process it adequately
end
switch pressed
% 6.5 This creates switch cases for each individual key in order to give a
% fitting color feedback. Thus, if a key is clicked the specific case is
% activated. 'col' is the color variable, hence when the strcmp() in [6.2]
% is equal col will be assigned to be green, otherwise col represents a red
% color
case 'C1'
whiteKey1 = patch([0 1 1 0], [1 1 7 7], col , 'EdgeColor', [0 0 0], 'UserData', 'C1');
case 'D1'
whiteKey2 = patch([1 2 2 1], [1 1 7 7], col, 'EdgeColor', [0 0 0], 'UserData', 'D1');
case 'E1'
whiteKey3 = patch([2 3 3 2], [1 1 7 7], col, 'EdgeColor', [0 0 0], 'UserData', 'E1');
case 'F1'
whiteKey4 = patch([3 4 4 3], [1 1 7 7], col, 'EdgeColor', [0 0 0], 'UserData', 'F1');
case 'G1'
whiteKey5 = patch([4 5 5 4], [1 1 7 7], col, 'EdgeColor', [0 0 0], 'UserData', 'G1');
case 'A1'
whiteKey6 = patch([5 6 6 5], [1 1 7 7], col, 'EdgeColor', [0 0 0], 'UserData', 'A1');
case 'H1'
whiteKey7 = patch([6 7 7 6], [1 1 7 7], col, 'EdgeColor', [0 0 0], 'UserData', 'H1');
case 'C2'
whiteKey8 = patch([7 8 8 7], [1 1 7 7], col, 'EdgeColor', [0 0 0], 'UserData', 'C2');
case 'D2'
whiteKey9 = patch([8 9 9 8], [1 1 7 7], col, 'EdgeColor', [0 0 0], 'UserData', 'D2');
end
%Same procedure with the black keys
blackKey1 = patch([0.6 1.4 1.4 0.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'C#1');
blackKey2 = patch([1.6 2.4 2.4 1.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'D#1');
blackKey3 = patch([3.6 4.4 4.4 3.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'F#1');
blackKey4 = patch([4.6 5.4 5.4 4.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'G#1');
blackKey5 = patch([5.6 6.4 6.4 5.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'A#1');
blackKey6 = patch([7.6 8.4 8.4 7.6], [3.5 3.5 7 7], [0.1 0.1 0.1], 'EdgeColor', [0 0 0], 'UserData', 'C#2');
switch pressed
case 'C#1'
blackKey1 = patch([0.6 1.4 1.4 0.6], [3.5 3.5 7 7], col, 'EdgeColor', [0 0 0], 'UserData', 'C#1');
case 'D#1'
blackKey2 = patch([1.6 2.4 2.4 1.6], [3.5 3.5 7 7], col, 'EdgeColor', [0 0 0], 'UserData', 'D#1');
case 'F#1'
blackKey3 = patch([3.6 4.4 4.4 3.6], [3.5 3.5 7 7], col, 'EdgeColor', [0 0 0], 'UserData', 'F#1');
case 'G#1'
blackKey4 = patch([4.6 5.4 5.4 4.6], [3.5 3.5 7 7], col, 'EdgeColor', [0 0 0], 'UserData', 'G#1');
case 'A#1'
blackKey5 = patch([5.6 6.4 6.4 5.6], [3.5 3.5 7 7], col, 'EdgeColor', [0 0 0], 'UserData', 'A#1');
case 'C#2'
blackKey6 = patch([7.6 8.4 8.4 7.6], [3.5 3.5 7 7], col, 'EdgeColor', [0 0 0], 'UserData', 'C#2');
end
% Writes User instruction underneath the keyboard
text(1.5,0,'Now click the matching key on the piano')
%creates letters on top
text(0.4,1.5,'C')
text(1.4,1.5,'D')
text(2.4,1.5,'E')
text(3.4,1.5,'F')
text(4.4,1.5,'G')
text(5.4,1.5,'A')
text(6.4,1.5,'H')
text(7.4,1.5,'C')
text(8.4,1.5,'D')
%creates smaller, white letters on black keys
text(0.85,4,'C#','Color','white','FontSize',9)
text(1.85,4,'D#','Color','white','FontSize',9)
text(3.85,4,'F#','Color','white','FontSize',9)
text(4.85,4,'G#','Color','white','FontSize',9)
text(5.85,4,'A#','Color','white','FontSize',9)
text(7.85,4,'C#','Color','white','FontSize',9)
pause(3);
% 6.6 initiates a pause at the function of the function
main();
% 6.7 initiates the main loop again
end
%7.0- 7.4
function ClickresponseCallback(src, event)
% 7.0 This function executes the callback and thus makes the keys interactive
% this function was taken from a mathworks forum and it was created
% by Thomas Satterly
switch class(src)
% 7.1 This creates a class which defines an object which encapsulates
% data, in this case the src which is used in the
% buttondownfunction of the keys []
case {'matlab.graphics.primitive.Patch'}
% 7.2 This is part of the class syntax and defines a the value in
% the {}. The Value is a property hat contains the numeric data stored in the object of the class
fprintf('Clicked key %s\n',src.UserData);
% 7.3 This fprintf function has the solemn purpose to display
% the clicked key in the Command Window, it could be
% outcommented but I find it useful to 'see' if my click
% was excecuted
draw_check(src.UserData);
% 7.4 This directs the flow to the draw_check function and
% gives the input of the defined object of the class (src) and the UserData [5.0], a 'handle'
% used to pass around data within a Callbackfunction this
% value is then assigned to as 'pressed' which is used for
% the color feedback in [6.2]
end
end
%8.0- 8.2
function play_sound(F)
% 8.0 This function plays the sounds using the audioplayer function, it was
% taken from a forums page and adjusted
fs = 1e4;
% sets sampling frequency
t = 1:1/fs:5;
% gives the time signal
y = sin(2*pi*F*t);
% 8.1 creates a sine wave; F is the frequency that, previously 'picked'
% from the random loop
player = audioplayer(y, fs);
% 8.2 create audio player object
play(player);
% plays the sound
pause(2)
% creates a pause
% -> the bigger the longer the tone
stop(player)
% stops the player
end
%------------------------------------------------%
% As aforementioned, collected and corrected data will be found in the
% test.txt file on used computer, Merry Christmas! *

Sign in to comment.

Community Treasure Hunt

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

Start Hunting!

Translated by