Theme music - Trying to get duration & keys to work together - matlab

I am writing a MATLAB function playing a song (Game of Thrones theme).
I have the correct keystroke and duration. I am trying to have two (treble and bass) playing at once and for each keystroke to be the correct duration. For some reason, I cannot get the duration to work with the keystroke (they all are playing at duration=1) or for the two sets to play together. Any ideas?
function xx = key2note(X, keynum, dur)
% KEY2NOTE Produce a sinusoidal waveform corresponding to a given piano key number
% usage: xx = key2note (X, keynum, dur)
% xx = the output sinusoidal waveform
% X = complex amplitude for the sinusoid, X = A*exp(j*phi).
% keynum = the piano keyboard number of the desired note
% dur = the duration (in seconds) of the output note
fs = 8000;
tt = (1/fs):(1/fs):dur;
freq = 440*(2^((keynum-49)/12)); %<=============== fill in this line
xx = real(X*exp(j*2*pi*freq*tt)); %<=============== fill in this line
end
‍‍‍‍‍‍ ‍‍
t = 0.17;
%treble - 40 is middle C
throne.keys = [47 40 43 45, 47 40 43 45, 47 40 43 45, 47 40 43 45, 47 40 44 45, 47 40 44 45, 47 40 44 45, 47 40 44 45, 47, 40, 40 45 47, 40 43 45, 42 47 36 40];
throne.dur = [ 1 1 0.5 0.5, 1 1 0.5 0.5, 1 1 0.5 0.5, 1 1 0.5 0.5, 1 1 0.5 0.5, 1 1 0.5 0.5, 1 1 0.5 0.5, 1 1 0.5 0.5, 4, 4, 1 1 2 1, 2 1 1, 1 1 1 1];
%bass
throne.keys2 = [ 21, 21 25 28 31 28 25, 21 25 28 31 28 25];
throne.dur2 = [ 32, 1/3 1/3 1/3 1/3 1/3 1/3, 1/3 1/3 1/3 1/3 1/3 1/3];
throne.durations = 0.2 * ones(1,length(throne.dur));
fs = 8000; % 11025 Hz also works
f = 329.62;
xx = zeros(1, sum(throne.dur)*fs + length(throne.keys));
n1 = 1;
for kk = 1:length(throne.keys)
keynum = throne.keys(kk);
tone = key2note(1,keynum,0.25); %amplitude 1, keynum, 0.38s % <------- Fill in this line
n2 = n1 + length(tone) - 1;
xx(n1:n2) = tone; %<------- Insert the note
n1 = n2 + 1;
end
fs = 8000;
%xt = zeros(1, sum(tdur)*fs + length(keyst));
%xb = zeros(1, sum(bdur)*fs + 1);
%xa = zeros(1, sum(adur)*fs+1);
%xd = zeros(1, sum(ddur)*fs+1);
%xx = xt + xb + xa + xd
xx = xt + xb;
soundsc(xx,fs)

I commented some never used lines out - you might want to uncomment them if they were used for something else later. Also I felt so free as to make t a global speed control.
t = 0.17; % this variable was never used.
%I assumed you wanted to control the overall speed with it and felt so free to adjust the durations with it.
%treble - 40 is middle C
throne.keys = [47 40 43 45, 47 40 43 45, 47 40 43 45, 47 40 43 45, 47 40 44 45, 47 40 44 45, 47 40 44 45, 47 40 44 45, 47, 40, 40 45 47, 40 43 45, 42 47 36 40];
throne.dur = t*[ 1 1 0.5 0.5, 1 1 0.5 0.5, 1 1 0.5 0.5, 1 1 0.5 0.5, 1 1 0.5 0.5, 1 1 0.5 0.5, 1 1 0.5 0.5, 1 1 0.5 0.5, 4, 4, 1 1 2 1, 2 1 1, 1 1 1 1];
%bass
throne.keys2 = [ 21, 21 25 28 31 28 25, 21 25 28 31 28 25];
throne.dur2 = t*[ 32, 1/3 1/3 1/3 1/3 1/3 1/3, 1/3 1/3 1/3 1/3 1/3 1/3];
%throne.durations = 0.2 * ones(1,length(throne.dur)); % this was never used - no clue what you intended to do with it
fs = 8000; % 11025 Hz also works
%f = 329.62; % is also never used
%treble loop
xt = zeros(1, ceil(sum(throne.dur)*fs + length(throne.keys)));
n1 = 1;
for kk = 1:length(throne.keys)
tone = key2note(1,throne.keys(kk),throne.dur(kk)); % You used here the constant length of 0.25 instead of taking the duration value from throne.dur
n2 = n1 + length(tone) - 1;
xt(n1:n2) = tone; %<------- Insert the note
n1 = n2 + 1;
end
%Bass loop
xb = zeros(1, ceil(sum(throne.dur2)*fs + length(throne.keys2)));
n1 = 1;
for kk = 1:length(throne.keys2)
tone2 = key2note(1,throne.keys2(kk),throne.dur2(kk));
n2 = n1 + length(tone2) - 1;
xb(n1:n2) = tone2; %<------- Insert the note
n1 = n2 + 1;
end
xb(length(xb)+1:length(xt)) = 0; %bass voice has a much shorter duration currently - padding to same size as xt with 0's at the end.
%If you add more voices, you have to pad them all to the length of the longest one.
%xt = zeros(1, sum(tdur)*fs + length(keyst));
%xb = zeros(1, sum(bdur)*fs + 1);
%xa = zeros(1, sum(adur)*fs+1);
%xd = zeros(1, sum(ddur)*fs+1);
%xx = xt + xb + xa + xd
xx = xt + xb;
soundsc(xx,fs)
Of course you could just download the theme song somewhere and use [y,Fs] = audioread(filename) to convert it. But that wouldn't even remotely be as cool as what you are doing.

Related

Data arrangement in vector

I have the following vector:
Here is the code to produce this vector:
A = [11 115 167 44 51 5 6];
B = [100 1 1 87];
C = [2000 625];
D = [81 623 45 48 6 14 429 456 94];
E = [89];
F = [44 846 998 2035 498 4 68 4 1 89];
G = {A,B,C,D,E,F};
[max_val, idx] = max(cellfun(#numel, G)); % Find max sizes of vectors
% Create vector with zeros filling open matrix space
LeftIndented = zeros(idx,max_val);
for k = 1:numel(G), LeftIndented(k,1:numel(G{k})) = G{k}; end
I would like to have a vector with:
Data to the right (zeros to the left)
Centered data (surrounded with zeros)
(Notice that if data cannot be exactly centered, it is ok if it is off by one vector space to the left)
How can I achieve this?
You can pad each vector with zeros:
A = [11 115 167 44 51 5 6];
B = [100 1 1 87];
C = [2000 625];
D = [81 623 45 48 6 14 429 456 94];
E = [89];
F = [44 846 998 2035 498 4 68 4 1 89];
G = {A,B,C,D,E,F};
[max_val, idx] = max(cellfun(#numel, G)); % Find max sizes of vectors
% Create vector with zeros filling open matrix space
LeftIndented = zeros(idx,max_val);
Centered = zeros(idx,max_val);
RightAligned = zeros(idx,max_val);
for k = 1:numel(G)
LeftIndented(k,1:numel(G{k})) = G{k};
l = length(G{k});
padding = max_val - l;
leftPadding = floor(padding / 2);
Centered(k, :) = [zeros(1, leftPadding), G{k}, zeros(1, padding - leftPadding)];
RightAligned(k, :) = [zeros(1, padding), G{k}];
end
This is equivalent to
A = [11 115 167 44 51 5 6];
B = [100 1 1 87];
C = [2000 625];
D = [81 623 45 48 6 14 429 456 94];
E = [89];
F = [44 846 998 2035 498 4 68 4 1 89];
G = {A,B,C,D,E,F};
[max_val, idx] = max(cellfun(#numel, G)); % Find max sizes of vectors
% Create vector with zeros filling open matrix space
LeftIndented = zeros(idx,max_val);
Centered = zeros(idx,max_val);
RightAligned = zeros(idx,max_val);
for k = 1:numel(G)
LeftIndented(k,1:numel(G{k})) = G{k};
l = length(G{k});
padding = max_val - l;
leftPadding = floor(padding / 2);
Centered(k, 1 + leftPadding:leftPadding + l) = G{k};
RightAligned(k, 1 + padding:end) = G{k};
end
but in the second code the values of the vectors are written into the correct position in a zero vector.
A solution using strjust:
A = [11 115 167 44 51 5 6];
B = [100 1 1 87];
C = [2000 625];
D = [81 623 45 48 6 14 429 456 94];
E = [89];
F = [44 846 998 2035 498 4 68 4 1 89];
G = {A,B,C,D,E,F};
data = [G{:}];
N = cellfun(#numel, G);
M = max(N);
idx = char((N.' >= (1:M))+32);
Le = strjust(idx, 'left');
Ri = strjust(idx, 'right');
Ce = strjust(idx, 'center');
LeftAdjusted = zeros(M, N);
RightAdjusted = zeros(M, N);
Centered = zeros(M, N);
LeftAdjusted(Le.' ~= ' ') = data;
RightAdjusted(Ri.' ~= ' ') = data;
Centered(Ce.' ~= ' ') = data;
LeftAdjusted = LeftAdjusted.';
RightAdjusted = RightAdjusted.';
Centered = Centered.';

How to get a vector from the elements along a fixed dimension in a 3D array?

I have a 3D array in MATLAB like this:
val(:,:,1) =
1.1461 2.3993
2.3993 15.4036
val(:,:,2) =
1.0041 0.8106
0.8106 10.6503
val(:,:,3) =
1.0001 0.9895
0.9895 3.0384
val(:,:,4) =
1.0024 0.9936
0.9936 2.0169
It's a 2 x 2 x 600 array. I want the second element of each of the 600 "matrices". Is there a simple way to extract these in MATLAB?
The output I would desire is like this:
output = [ 2.3993; 0.8106; 0.9895; 0.9936 ];
My attempt so far has been the following:
val(1, 2, :)
But, this returns:
ans(:,:,1) =
2.3993
ans(:,:,2) =
0.8106
ans(:,:,3) =
0.9895
ans(:,:,4) =
0.9936
I need these values as a vector.
Your approach accessing val(1, 2, :) is correct. Nevertheless, the output produced has a size of 1 x 1 x 4. What you want to do is to remove the (unnecessary) dimensions of length 1. That is what the squeeze function is for.
Having a 3-dimensional array val like yours and fixed indices x, y for the first and second dimensions, we just surround your command with squeeze:
% 3-dimensional array
val = reshape(1:36, 3, 3, 4)
% Squeezed output for fixed x, y in dimensions 1 and 2
x = 1;
y = 2;
out3d = squeeze(val(x, y, :))
The output is the following:
val =
ans(:,:,1) =
1 4 7
2 5 8
3 6 9
ans(:,:,2) =
10 13 16
11 14 17
12 15 18
ans(:,:,3) =
19 22 25
20 23 26
21 24 27
ans(:,:,4) =
28 31 34
29 32 35
30 33 36
out3d =
4
13
22
31
This works for arbitrary dimensions and number of indices.
A 4-dimensional array with two fixed indices will produce a 2-dimensional output:
% 4-dimensional array
val = reshape(1:108, 3, 3, 4, 3)
% Squeezed output for fixed x, y in dimensions 1, 2
x = 1;
y = 2;
out4d = squeeze(val(x, y, :, :))
Output:
val = (omitted here)
out4d =
4 40 76
13 49 85
22 58 94
31 67 103
A 4-dimensional array with three fixed indices will again produce a 1-dimensional output:
% 4-dimensional array
val = reshape(1:108, 3, 3, 4, 3)
% Squeezed output for fixed x, y, z in dimensions 1, 2, 3
x = 1;
y = 2;
z = 1;
out4d = squeeze(val(x, y, z, :))
Output:
val = (omitted here)
out4d =
4
40
76
Hope that helps!

Error of Averaging a Large Vector in MATLAB

How do you accurately take the average of large sets of integers in MATLAB?
I have two large vectors (2672x4008 in dimensions) I am dealing with, each the result of pixels in an image. Hence, the resulting vector is filled with values 0 to 256, all integers. My problem is that I want an accurate value of the average intensity of these grey-scale images. To do this, I used the line
meanvalue = mean(I(:))
This yielded a value of meanvalue = 155.9335 in the output line of MATLAB.
Next, I added 20 to each value of the vector, as below (this should raise the intensity of the overall image, if I am understanding correctly).
Ipt = I + 20;
I then took the mean value of this new vector, Ipt
meanvaluept = mean(Ipt(:))
and matlab spat out a value of meanvaluept = 175.8916. I'm no math wizard, but I know enough to know that 175.8916 - 20 ≠ 155.9335.
Any help would be appreciated, either mathematically (how to increase the precision of MATLAB), or procedurally (there is some built-in function of MATLAB which will find the intensity).
Since you are referring to "grey-scale images", and you have integers in the range 0-255 (the 256 you mention must be a typo), my guess is that your I is of type uint8.
In this case, MATLAB uses saturated addition, in which results larger than 255 are clamped to 255. The effect you describe is caused by this saturated addition.
Here is an example:
>> I = uint8(randi(255,1000,1000));
>> mean( I(:)+20 )
ans =
147.1954
>> mean(I(:)) + 20
ans =
148.0151
The solution is to convert to doubles first:
>> mean( double(I(:)) + 20 )
ans =
148.0151
Have you checked the image datatype?
It's true that if your the mean of image I is
meanvalue = mean(I(:)) = 155.9335
and you added 20 to each pixels
Ipt = I + 20
you supposed to have
meanept = mean(Ipt(:)) = meanvalue + 20 = 175.9335
But, don't forget that image's datatype is uint8, which limit the pixels value to 0-255. It means if you added 20 to a pixel and its value is greater than 255, its value will set to 255, and the same if you substract some value and it's lower than 0.
Maybe, some of your pixels restricted to 255 when normally you'll have more than 255.
For example:
I have vector X in double
X = [1 1 1; ...
1 1 1; ...
1 1 240];
The mean of X is
mean(X(:)) = 27.5556
since
( 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 240)/9 = 27.5556
If I added 20 to each pixels
X20 = X + 20
= [(1 + 20) (1 + 20) (1 + 20); ...
(1 + 20) (1 + 20) (1 + 20); ...
(1 + 20) (1 + 20) (240 + 20)];
= [21 21 21; ...
21 21 21; ...
21 21 255];
Notice that X20(3,3) is 255, not 260. It cause
meanX20 = mean(X20(:)) = 47
but if I change X's datatype to double
X_double = double(X)
and added 20 to each pixels
X20_double = X_double + 20
= [(1 + 20) (1 + 20) (1 + 20); ...
(1 + 20) (1 + 20) (1 + 20); ...
(1 + 20) (1 + 20) (240 + 20)];
= [21 21 21; ...
21 21 21; ...
21 21 260];
and the average of X20_double is
X20_double_mean = mean(X20_double(:)) = 47.5556
See the difference?
The double X20's mean is 47.5556 and the uint8 X20's mean is 47.
I hope this will help :)
There is a very important note in your question :
Suppose I = [2 3;4 9]
meanvalue = mean(I(:)) = 4.5
When you add 20 with I , You will have :
Ipt = I + 20;
Ipt = [22 23;24 29]
so you add 20 to all elements in I, therefore your mean will increase 20 value.

Construct a matrix from a set of coordinates

I have a set of coordinates:
x y
65 17
66 17
67 18
68 18
24 26
25 26
26 27
27 27
34 35
35 35
I want to construct a 70-by-70 matrix A such that A(67,18) = A(68,18) = A(24,26) = ... =A(35,35) = 1, otherwise A(:,:) = 0.
Is there any quick way to do this?
A=sparse(x,y,1,70,70)
If you don't want a sparse matrix, convert it:
A=full(A)
I recommend using sparse as shown in Daniel's answer, but here is a simple sub2ind based alternative:
n = 70;
A = zeros(n);
A(sub2ind([n,n],x,y))=1;
Or, you can use linear indexing:
A = zeros(70);
A(x+70*(y-1))=1;
you can try this:
coords = ...
[65 17;
66 17;
67 18;
68 18;
24 26;
25 26;
26 27;
27 27;
34 35;
35 35];
%// creating the result array
result = zeros(70,70);
%// loop through the entrys
for i = 1 : size(coords,1)
result(coords(i,1), coords(i,2)) = 1;
end
UPDATE
you can solve it without an loop too:
coords = ...
[65 17;
66 17;
67 18;
68 18;
24 26;
25 26;
26 27;
27 27;
34 35;
35 35];
%// creating the result array
result = zeros(70,70);
%// splitting the coords into a `x` and a `y` vector and save them into a cell
indexes = mat2cell(coords, size(coords, 1), ones(1, size(coords, 2)));
%// setting the ones by the indexes cell
result(sub2ind(size(result), indexes{:})) = 1;
Here's another way using accumarray:
result = accumarray([x(:) y(:)], 1, [70 70]); %// full matrix
The third input argument, [70 70], specifies matrix size.
If you prefer sparse result, use a sixth input argument (called issparse in the documentation) as follows:
result = accumarray([x(:) y(:)], 1, [70 70], [], 0, true); %// sparse matrix
Either of the above statements accumulates coincident values. That is, if there is a repeated coordinate you will get a value 2 at that entry. If you want to keep value 1 in those cases, change the fourth input argument:
result = accumarray([x(:) y(:)], 1, [70 70], #(x) 1, 0); %// force 1, full matrix
This could of course be combined with the issparse flag set to true:
result = accumarray([x(:) y(:)], 1, [70 70], #(x) 1, 0, true); %// force 1, sparse matrix

How do I add lines connecting corresponding boxes, that I've drawn on a plot? (MATLAB)

I have a matrix (Data) which looks like this:
(start) (stop) (strand) (gene number)
[ 1 29 1 1]
[ 32 38 1 1]
[ 44 60 1 1]
[ 66 70 0 2]
[ 75 80 0 2]
[ 81 88 0 3]
[ 99 102 0 3]
[ 111 160 0 3]
[ 166 170 1 4]
[ 171 188 1 4]
which I have plotted onto a graph using the first two columns as X positions, and a set Y position. This is the code I have up till now:
if nargin<4, strands = 0; end;
if nargin<3, height = 0.1; end;
if nargin<2, y = 2.1; end;
for k=1:size(cds,1),
xc = [cds(k,1) cds(k,2) cds(k,2) cds(k,1)];
if strands,
if cds(k,3), % minus strand
yc = [y y y-height/2 y-height/2];
c = 'r';
else % plus strand
yc = [y+height/2 y+height/2 y y];
c = 'b';
end
else
yc = [y+height/2 y+height/2 y-height/2 y-height/2];
c = 'b';
end
h(k) = patch(xc,yc,c);
end
What I'm trying to do is add lines underneath each 'box' which corresponds to the gene number (4th collumn of the data matrix). How would I go about doing this with the plot function?
It's not clear from your question how you want the lines to indicate the gene numbers, I assume you want different colors for each type. Here is how I would do it:
cds = [
1 29 1 1
32 38 1 1
44 60 1 1
66 70 0 2
75 80 0 2
81 88 0 3
99 102 0 3
111 160 0 3
166 170 1 4
171 188 1 4
];
strands = 0;
height = 0.1;
y = 2.1;
[g,gIdx,gNum] = unique(cds(:,4));
clr = 'gcmykrb';
for k=1:size(cds,1),
xc = [cds(k,1) cds(k,2) cds(k,2) cds(k,1)];
if strands,
if cds(k,3), % minus strand
yc = [y y y-height/2 y-height/2];
c = 'r';
else % plus strand
yc = [y+height/2 y+height/2 y y];
c = 'b';
end
else
yc = [y+height/2 y+height/2 y-height/2 y-height/2];
c = 'b';
end
h(k) = patch(xc,yc,c);
hLine(k) = line([cds(k,1) cds(k,2)], [y-3*height/4 y-3*height/4], ...
'LineWidth',5, 'Color',clr(gNum(k)));
end
legend(hLine(gIdx), num2str(g), 'Orientation','horizontal')