Convert Frequency to Note in matlab - matlab

So I created a MATLAB program to detect the frequencies present in a piano recording. Now I need to convert these detected frequencies to their corresponding piano note.
I know there's a theory about using A4 (440Hz) as the reference note and deriving the rest based on that. But I'm looking for something like a look up table, where I can directly call the respective piano notes by looking at the look up table. I'm not quite sure how to go on about it though and so would like some suggestions on what I could do... Thanx

From here (just the first Google hit, nothing special), you can see that all the frequencies of all notes in the equal-tempered scale are related through
f = f0 · 2^(i/12)
with f0 = A4 = 440Hz, and i the number of half-steps away from the base note. This allows you to make a lookup table (LUT) for all the notes.
The only thing you need to put a bit of work in are:
find the entry in the LUT closest to your frequencies
map those entries to named notes
Define what the range of your source piano is (yes, ranges can differ).
Today's your lucky day (I'm bored and procrastinating :), so here's a fairly complete implementation, for notes from C0 to E8♭:
% Your frequencies (can be of any size, must be in Hz)
V = [17 450 4000];
% Generate the lookup table
LUT = 440 * (2^(1/12)).^(-57:42);
% The names of all those notes
noteNames = {...
'AN' 'AN♯/BN♭' 'BN' 'CN' 'CN♯/DN♭' 'DN' 'DN♯/EN♭' 'EN' 'FN' 'FN♯/GN♭' 'GN' 'GN♯/AN♭'};
allNotes = [];
for ii = 0:8
allNotes = [allNotes regexprep(noteNames, 'N', num2str(ii))]; end %#ok<AGROW>
allNotes = allNotes(4:end-4);
% Indices into the lookup table
[~,I] = min(abs(bsxfun(#minus, V(:), LUT)), [], 2);
% The named noted corresponding to your frequencies
allNotes(I)

You don't need a lookup table neither do you have to limit yourself to a specific range: (Javascript)
function getNoteFromFrequency(frequency) {
var noteOrder = 'G# A A# B C C# D D# E F F# G'.split(" ");
var n = Math.round(49 + 12 * Math.log(frequency / 440) / Math.log(2));
var note = noteOrder[n % noteOrder.length];
var index = Math.ceil((n - 3) / noteOrder.length);
return note + index;
}

Related

Reverse-calculating original data from a known moving average

I'm trying to estimate the (unknown) original datapoints that went into calculating a (known) moving average. However, I do know some of the original datapoints, and I'm not sure how to use that information.
I am using the method given in the answers here: https://stats.stackexchange.com/questions/67907/extract-data-points-from-moving-average, but in MATLAB (my code below). This method works quite well for large numbers of data points (>1000), but less well with fewer data points, as you'd expect.
window = 3;
datapoints = 150;
data = 3*rand(1,datapoints)+50;
moving_averages = [];
for i = window:size(data,2)
moving_averages(i) = mean(data(i+1-window:i));
end
length = size(moving_averages,2)+(window-1);
a = (tril(ones(length,length),window-1) - tril(ones(length,length),-1))/window;
a = a(1:length-(window-1),:);
ai = pinv(a);
daily = mtimes(ai,moving_averages');
x = 1:size(data,2);
figure(1)
hold on
plot(x,data,'Color','b');
plot(x(window:end),moving_averages(window:end),'Linewidth',2,'Color','r');
plot(x,daily(window:end),'Color','g');
hold off
axis([0 size(x,2) min(daily(window:end))-1 max(daily(window:end))+1])
legend('original data','moving average','back-calculated')
Now, say I know a smattering of the original data points. I'm having trouble figuring how might I use that information to more accurately calculate the rest. Thank you for any assistance.
You should be able to calculate the original data exactly if you at any time can exactly determine one window's worth of data, i.e. in this case n-1 samples in a window of length n. (In your case) if you know A,B and (A+B+C)/3, you can solve now and know C. Now when you have (B+C+D)/3 (your moving average) you can exactly solve for D. Rinse and repeat. This logic works going backwards too.
Here is an example with the same idea:
% the actual vector of values
a = cumsum(rand(150,1) - 0.5);
% compute moving average
win = 3; % sliding window length
idx = hankel(1:win, win:numel(a));
m = mean(a(idx));
% coefficient matrix: m(i) = sum(a(i:i+win-1))/win
A = repmat([ones(1,win) zeros(1,numel(a)-win)], numel(a)-win+1, 1);
for i=2:size(A,1)
A(i,:) = circshift(A(i-1,:), [0 1]);
end
A = A / win;
% solve linear system
%x = A \ m(:);
x = pinv(A) * m(:);
% plot and compare
subplot(211), plot(1:numel(a),a, 1:numel(m),m)
legend({'original','moving average'})
title(sprintf('length = %d, window = %d',numel(a),win))
subplot(212), plot(1:numel(a),a, 1:numel(a),x)
legend({'original','reconstructed'})
title(sprintf('error = %f',norm(x(:)-a(:))))
You can see the reconstruction error is very small, even using the data sizes in your example (150 samples with a 3-samples moving average).

Selecting corresponding values from another loop to use in subsequent loop

I am an Earth Scientist, and interested in the macro scale morphometric parameters in the river basins. I am building a simple graded Stream Gradient index from a excel sheet. I have wrote a simple code which follows:
data = 'SL.xlsx';
headers = xlsread(data);
lat = headers (:,1);
long = headers (:,2);
elevation = headers (:,3);
numberofelement = numel (lat);
% To calculate the intermideate distacne between two points
for i=2:numberofelement
intdistance (1)=0;
intdistance (i) = sqrt((lat (i)-lat (i-1))^2+ (long (i)-long (i-1))^2);
end
% Cumulative distacne in km
cumdist (1)=0
for j=2:numberofelement
cumdist(j)=cumdist(j-1)+intdistance (j);
cumdistkm (j)= cumdist(j)/1000;
end
% Average SL index (or graded river gradient
gradedslindex = (elevation (1)- elevation (numberofelement))/log(cumdistkm(numberofelement))
I am unable to to do the next steps. The next steps includes few calculations:
The hypothetical data looks like
Cumdist Elevation
0.25 500
2.1 480
4.2 470
6.8 450
7.5 430
8.2 420
9.1 410
10.1 400
1) In the cumdistkm variable I have to segment for every 5 km. If there is no 5 km value, I have to select the nearest lower value for cumdist. So for this data the 4.2 and 9.1 have to taken.
2) Then the calculation part would be elevation(last)-elevation(first)(for that particular reach) and divide by In(cumdistkm(last))-In(cumdist(first))(for the same row index).
I am unable to select these parameters for regular interval. A small hint is will be very helpful.
Thank you.
You can use the cumsum function to find cumdist
cumdist_km = [0; cumsum(intdistance)/1000]; %calculate cumdist in km
I would make a function and stick it in a file findIndicesBelowIncrement.m. There might be a slicker way to do this, but this should be quite fast and works (as I understand what you're asking for).
function indices = findIndicesBelowIncrement(cumdist_km, km_increment)
% indices = findIndicesForBelowIncrement(cumdist_km, km_increment)
% cumdist_km : sorted array of distances
% km_increment: we will find index of cumdist_km for each
% multiple of increment such that distance is
% at or below the multiple of the increment
indices = NaN(ceil(cumdist_km(end) / 5), 1); % we know endpoint, lets init smart
j = 1;
for i=2:length(cumdist_km) % iterate through array
if(cumdist_km(i) > j * km_increment) % we have just passed increment multiple
indices(j) = i - 1; % previous index is what we want
while(cumdist_km(i) > j * km_increment) % handle case value skips ahead
% note: well end up with a NaN in indices for every skip
j = j + 1 % normal is 1 increment. skip is > 1
end
end
end
indices(end) = length(cumdist_km);
Then back in your main script you could do:
five_km_indices = findIndicesBelowIncrement(cumdist_km, 5);
dist_5kminc = cumdist_km(five_km_indices);
elev_5kminc = elevations(five_km_indices);
Then do whatever calculations on dist_5kminc and elev_5kminc.

Regarding loop structure in Matlab for an iterative procedure

I'm trying to code a loop in Matlab that iteratively solves for an optimal vector s of zeros and ones. This is my code
N = 150;
s = ones(N,1);
for i = 1:N
if s(i) == 0
i = i + 1;
else
i = i;
end
select = s;
HI = (item_c' * (weights.*s)) * (1/(weights'*s));
s(i) = 0;
CI = (item_c' * (weights.*s)) * (1/(weights'*s));
standarderror_afterex = sqrt(var(CI - CM));
standarderror_priorex = sqrt(var(HI - CM));
ratio = (standarderror_afterex - standarderror_priorex)/(abs(mean(weights.*s) - weights'*select));
ratios(i) = ratio;
s(i) = 1;
end
[M,I] = min(ratios);
s(I) = 0;
This code sets the element to zero in s, which has the lowest ratio. But I need this procedure to start all over again, using the new s with one zero, to find the ratios and exclude the element in s that has the lowest ratio. I need that over and over until no ratios are negative.
Do I need another loop, or do I miss something?
I hope that my question is clear enough, just tell me if you need me to explain more.
Thank you in advance, for helping out a newbie programmer.
Edit
I think that I need to add some form of while loop as well. But I can't see how to structure this. This is the flow that I want
With all items included (s(i) = 1 for all i), calculate HI, CI and the standard errors and list the ratios, exclude item i (s(I) = 0) which corresponds to the lowest negative ratio.
With the new s, including all ones but one zero, calculate HI, CI and the standard errors and list the ratios, exclude item i, which corresponds to the lowest negative ratio.
With the new s, now including all ones but two zeros, repeat the process.
Do this until there is no negative element in ratios to exclude.
Hope that it got more clear now.
Ok. I want to go through a few things before I list my code. These are just how I would try to do it. Not necessarily the best way, or fastest way even (though I'd think it'd be pretty quick). I tried to keep the structure as you had in your code, so you could follow it nicely (even though I'd probably meld all the calculations down into a single function or line).
Some features that I'm using in my code:
bsxfun: Learn this! It is amazing how it works and can speed up code, and makes some things easier.
v = rand(n,1);
A = rand(n,4);
% The two lines below compute the same value:
W = bsxfun(#(x,y)x.*y,v,A);
W_= repmat(v,1,4).*A;
bsxfun dot multiplies the v vector with each column of A.
Both W and W_ are matrices the same size as A, but the first will be much faster (usually).
Precalculating dropouts: I made select a matrix, where before it was a vector. This allows me to then form a variable included using logical constructs. The ~(eye(N)) produces an identity matrix and negates it. By logically "and"ing it with select, then the $i$th column is now select, with the $i$th element dropped out.
You were explicitly calculating weights'*s as the denominator in each for-loop. By using the above matrix to calculate this, we can now do a sum(W), where the W is essentially weights.*s in each column.
Take advantage of column-wise operations: the var() and the sqrt() functions are both coded to work along the columns of a matrix, outputting the action for a matrix in the form of a row vector.
Ok. the full thing. Any questions let me know:
% Start with everything selected:
select = true(N);
stop = false; % Stopping flag:
while (~stop)
% Each column leaves a variable out...
included = ~eye(N) & select;
% This calculates the weights with leave-one-out:
W = bsxfun(#(x,y)x.*y,weights,included);
% You can comment out the line below, if you'd like...
W_= repmat(weights,1,N).*included; % This is the same as previous line.
% This calculates the weights before dropping the variables:
V = bsxfun(#(x,y)x.*y,weights,select);
% There's different syntax, depending on whether item_c is a
% vector or a matrix...
if(isvector(item_c))
HI = (item_c' * V)./(sum(V));
CI = (item_c' * W)./(sum(W));
else
% For example: item_c is a matrix...
% We have to use bsxfun() again
HI = bsxfun(#rdivide, (item_c' * V),sum(V));
CI = bsxfun(#rdivide, (item_c' * W),sum(W));
end
standarderror_afterex = sqrt(var(bsxfun(#minus,HI,CM)));
standarderror_priorex = sqrt(var(bsxfun(#minus,CI,CM)));
% or:
%
% standarderror_afterex = sqrt(var(HI - repmat(CM,1,size(HI,2))));
% standarderror_priorex = sqrt(var(CI - repmat(CM,1,size(CI,2))));
ratios = (standarderror_afterex - standarderror_priorex)./(abs(mean(W) - sum(V)));
% Identify the negative ratios:
negratios = ratios < 0;
if ~any(negratios)
% Drop out of the while-loop:
stop = true;
else
% Find the most negative ratio:
neginds = find(negratios);
[mn, mnind] = min(ratios(negratios));
% Drop out the most negative one...
select(neginds(mnind),:) = false;
end
end % end while(~stop)
% Your output:
s = select(:,1);
If for some reason it doesn't work, please let me know.

??? Index exceeds matrix dimensions PSD Proplem

Hey guys I am trying to find the Power Spectral Density of a .wav signal i recorded which is essentially a sine immersed in noise. The function that i have written is supposed to take records all of 1024 points in length and use it to find the Gxx of the signal by finding Gxx per record and then adding them and dividing them by the number of records better explained in the algorithm below:
a. Step through the wav file and extract the first record length (e.g. 1 to 1024 points). (Note that the record length is your new “N”, hence the frequency spacing changes in accordance with this, NOT the total length of the wav file).
b. Perform the normal PSD function on this record.
c. Store this vector.
d. Extract the next 1024 points in the wav file (e.g. 1025:2048) and perform PSD on this record.
e. Add this to the previously stored record and continue through steps c to e until you reach the end of your wav file or the total number of records you want. (Remember that total records*record length must be less than the total length of the wavfile!)
f. Divide the PSD by the number of averages (or number of records).
This is your averaged PSD
The function I created is as follows:
%Function to plot PSD
function[f1, GxxAv] = HW3_A_Fn_811003472_RCT(x,fs,NumRec)
Gxx = 0;
GxxAv = 0;
N = 1024;
df = fs/N;
f1 = 0:df:fs/2;
dt = 1/fs;
T = N*dt;
q = 0;
e = 1;
for i = 1:NumRec;
for r = (1+q):(N*e);
L = x(1+q:N*e);
M = length(L);
Xm = fft(L).*dt;
aXm = abs(Xm);
Gxx(1)=(1/T).*(aXm(1).*aXm(1));
for k = 2:(M/2);
Gxx(k) = (2/T) *(aXm(k).*(aXm(k)));
%Gxx = Gxx + Gxx1(k);
end
Gxx((M/2)+1)= (1/T)*(aXm((M/2)+1)).*(aXm((M/2)+1));
q = q+1024;
e = e+1;
%Gxx = Gxx + Gxx1((M/2)+1);
end
GxxAv = GxxAv + Gxx;
%Gxx = Gxx + Gxx1;
end
GxxAv = GxxAv/NumRec;
And the code I used to call this function is as follows:
[x,fs] = wavread('F:\Final\sem1Y3\Acoustics\sinenoise5s.wav');
[f1,GxxAv] = HW3_A_Fn_811003472_RCT(x,fs,100); %where 100 is the number of records to generated
plot(f1,GxxAv)
xlabel ('Frequency / Hz', 'fontsize', 18)
ylabel ('Amplitude Squared per Frequency / WU^2/Hz', 'fontsize', 18)
title ('Plot of the single sided PSD, using Averaging', 'fontsize', 18)
grid on
When Trying to plot this graph the following error was observed:
??? Index exceeds matrix dimensions.
Error in ==> HW3_A_Fn_811003472_RCT at 19
L = x(1+q:N*e);
Error in ==> HW3_A_3_811003472_RCT at 3
[f1,GxxAv] = HW3_A_Fn_811003472_RCT(x,fs,100); %where 100 is the number of records to generated
I am not sure how to fix it and i have tried many different methods but still i get this error. I am not too familiar with Matlab but all I really want to do for line 19 is to go like:
x(1:1024), x(1025:2048), x(2049:3072), x(3072:4096)...etc to 100 records
Any ideas??? Thanks
This is obviously homework, so I am not gonna do your work for you. But there quite some things wrong with your code. Start by fixing all of those first:
Use more appropriate function names, homework123 is not a good name to describe what the function does.
Use more appropriate variable names. More standard in this context would be nfft instead of N and n_average instead of NumRec. I don't care about the exact thing you use, but it should describe exactly what the variable does.
Your error message clearly hints that you are trying to index x in some illegal way. Start with making a loop that just prints the right indices (1..1024, 1025..2048, ...) and make sure it follows your instruction E. Only when this works as expected add the rest of the code.
you use a triple-nested for-loop. You only need a single for-loop or while-loop to solve this problem.

Matlab fourier descriptors what's wrong?

I am using Gonzalez frdescp function to get Fourier descriptors of a boundary. I use this code, and I get two totally different sets of numbers describing two identical but different in scale shapes.
So what is wrong?
im = imread('c:\classes\a1.png');
im = im2bw(im);
b = bwboundaries(im);
f = frdescp(b{1}); // fourier descriptors for the boundary of the first object ( my pic only contains one object anyway )
// Normalization
f = f(2:20); // getting the first 20 & deleting the dc component
f = abs(f) ;
f = f/f(1);
Why do I get different descriptors for identical - but different in scale - two circles?
The problem is that the frdescp code (I used this code, that should be the same as referred by you) is written also in order to center the Fourier descriptors.
If you want to describe your shape in a correct way, it is mandatory to mantain some descriptors that are symmetric with respect to the one representing the DC component.
The following image summarize the concept:
In order to solve your problem (and others like yours), I wrote the following two functions:
function descriptors = fourierdescriptor( boundary )
%I assume that the boundary is a N x 2 matrix
%Also, N must be an even number
np = size(boundary, 1);
s = boundary(:, 1) + i*boundary(:, 2);
descriptors = fft(s);
descriptors = [descriptors((1+(np/2)):end); descriptors(1:np/2)];
end
function significativedescriptors = getsignificativedescriptors( alldescriptors, num )
%num is the number of significative descriptors (in your example, is was 20)
%In the following, I assume that num and size(alldescriptors,1) are even numbers
dim = size(alldescriptors, 1);
if num >= dim
significativedescriptors = alldescriptors;
else
a = (dim/2 - num/2) + 1;
b = dim/2 + num/2;
significativedescriptors = alldescriptors(a : b);
end
end
Know, you can use the above functions as follows:
im = imread('test.jpg');
im = im2bw(im);
b = bwboundaries(im);
b = b{1};
%force the number of boundary points to be even
if mod(size(b,1), 2) ~= 0
b = [b; b(end, :)];
end
%define the number of significative descriptors I want to extract (it must be even)
numdescr = 20;
%Now, you can extract all fourier descriptors...
f = fourierdescriptor(b);
%...and get only the most significative:
f_sign = getsignificativedescriptors(f, numdescr);
I just went through the same problem with you.
According to this link, if you want invariant to scaling, make the comparison ratio-like, for example by dividing every Fourier coefficient by the DC-coefficient. f*1 = f1/f[0], f*[2]/f[0], and so on. Thus, you need to use the DC-coefficient where the f(1) in your code is not the actual DC-coefficient after your step "f = f(2:20); % getting the first 20 & deleting the dc component". I think the problem can be solved by keeping the value of the DC-coefficient first, the code after adjusted should be like follows:
% Normalization
DC = f(1);
f = f(2:20); % getting the first 20 & deleting the dc component
f = abs(f) ; % use magnitudes to be invariant to translation & rotation
f = f/DC; % divide the Fourier coefficients by the DC-coefficient to be invariant to scale