package jass.generators;
/* calculates to and from
polar and rectangular coordinates
in the frequency domain.
@author Reynald Hoskinson (reynald@cs.ubc.ca)
*/
public class RectPolar
{
// speed of sound in meters per second.
static final float speedOfSound = 340.29f;
float headRadius = 0.3f; // meters
// need only 1dim array for phase?
// or calculate all of them at same time?
double [] phase;
double [] magnitude;
int windowSize;
public RectPolar(float headRadius, int windowSize) {
this.headRadius = headRadius;
this.windowSize = windowSize;
phase = new double[windowSize];
magnitude = new double[windowSize];
}
/*
The paper is a little unclear here.
First they say that the "complex part of the measured HRTF"
is prone to noise and other errors. Then they say they replace the
phase, but as far as I understand,
the phase is not the same as the complex part.
They replace the phase with a value that gives the correct interaural
time difference for a sphere
radius(mag + cos mag) (cos theta)/c
*/
public double calcPhase(double mag, double theta){
return headRadius*(mag + Math.cos(mag))*Math.cos(theta)/speedOfSound;
}
// given real and imaginary parts of complex number,
// caclulates their magnitude in polarMag
public void getPolarMag(double [] real, double [] imag,
double [] polarMag) {
for (int i = 0; i < real.length; i++) {
polarMag[i] = Math.sqrt(real[i]*real[i] + imag[i]*imag[i]);
}
}
// convert from polar coordinates, given in first two arrays,
// to rectangular, given in next two
public void getRect(double [] mag, double [] phase, double [] real,
double [] imag) {
for (int i = 0; i < mag.length; i++ ) {
real[i] = mag[i]*Math.cos(phase[i]);
imag[i] = mag[i]*Math.sin(phase[i]);
}
}
// multiply
public void multiplyRect(double [] a, double [] aI,
double [] b, double [] bI) {
// assume for now same length
// puts result into b
for (int i = 0; i < a.length; i++ ){
b[i] = a[i]*b[i] - aI[i]*bI[i];
bI[i] = aI[i]*b[i] + a[i]*bI[i];
}
}
/*
* given the fresh fft'd HRIR
* 1) converts it to polar coordinates
* 2) calculates new phase component based on spherical head
* 3) converts that back to rectangular
replaces input values
*/
public void constructHRTF(double [] realPart, double [] imagPart)
{
// get magnitude
getPolarMag(realPart, imagPart, magnitude);
// construct phase
for (int i =0; i < magnitude.length; i++) {
// phase[i] = Math.atan2(imagPart[i], realPart[i]);
phase[i] = calcPhase(magnitude[i], Math.atan2
(imagPart[i], realPart[i]));
}
// incidentally, why is it atan2(y,x), instead of (x, y)?
// the other way worked pretty well!
// convert back to rectangular coordinates.
getRect(magnitude, phase, realPart, imagPart);
}
// test out!
public static void main(String [] args) {
float headRadius = 0.3f;
int length = 64;
RectPolar rp = new RectPolar(headRadius, length);
double [] bleh = new double[length];
double [] bleh_img = new double[length];
for (int i = 0; i < length; i++) {
bleh[i] = i/5.0;
bleh_img[i] = 0;
}
System.out.println("original");
for (int i =0; i < length; i++) {
System.out.println(bleh[i] + " " + bleh_img[i]);
}
FFT fft = new FFT(6);
fft.doFFT(bleh, bleh_img, false);
System.out.println("after");
rp.constructHRTF(bleh, bleh_img);
fft.doFFT(bleh, bleh_img, true);
for (int i =0; i < length; i++) {
System.out.println(bleh[i] + " " + bleh_img[i]);
}
}
}