// class file for HRTF data // organized in terms of azimuth (naz) elevation (nel) time (nt) // right now as a 25x50x200 data structure, as organized in CIPIC /* % two arrays: hrtf_l and hrtf % indices naz, nel, nt (azimuth, elevation, time) % (25x50x200) 3 dimensional array % in matlab, azimuth angle corresponding to nax is the naz'th element % of the vector % azimuths = [-80 -65 -55 -45:5:45 55 65 80] % elevations range from -45 to +230.625 in steps of 5.625 % this angular increment divides the full circle into 64 equal parts, % but only 50 values are used % elevation angle corresponding to nel is the nel-th element of the % vector elevations = -45 + 5.625*(0:49) % temporal @author Reynald Hoskinson (reynald@cs.ubc.ca) */ package jass.generators; import java.io.*; /** class for using CIPIC-type HRTF files */ public class HRTF { boolean doSpatial = true; static final boolean DEBUG = false; // real double [][][] hrtf_l; double [][][] hrtf_r; // imaginary double [][][] hrtfI_l; double [][][] hrtfI_r; // working buffers double [] hrtfL; double [] hrtfR; double [] hrtfL_img; double [] hrtfR_img; // buffers for the separated right/left channels of original sample // imaginary ones needed for FFT double [] channelL; double [] channelL_img; double [] channelR; double [] channelR_img; int windowLength; // FFT window length used // formula used to interplolate/choose // between HRTF points. CIPIC_HRTF ucdpulse; FFT fft; float headRadius = 0.3f; // meters float gain = 1000; // for some reason, these HRTF's make everything _very_ quiet. // conversion between rectangular/ polar RectPolar rp = new RectPolar(headRadius, windowLength); /* takes the window size in samples and the filename of the HRTF data */ public HRTF (int windowlength, String filename) { windowLength = windowlength; if (DEBUG) { System.out.println("windowlength: " + windowLength); } // this is dependent on window size. // figure out the formula later! fft = new FFT(8); // put HRTF data here hrtf_l = new double[25][50][windowLength]; hrtf_r = new double[25][50][windowLength]; hrtfI_l = new double[25][50][windowLength]; hrtfI_r = new double[25][50][windowLength]; // intermediate buffers for separated signal channelL = new double[windowLength]; channelR = new double[windowLength]; channelL_img = new double[windowLength]; channelR_img = new double[windowLength]; // intermediate buffers for HRTF hrtfR = new double[windowLength]; hrtfR_img = new double[windowLength]; hrtfL = new double[windowLength]; hrtfL_img = new double[windowLength]; ucdpulse = new CIPIC_HRTF(); // zero imaginary portion for(int i = 0; i < channelL_img.length; i++) { channelL_img[0] = 0.0; channelR_img[0] = 0.0; } loadHRTFFromCIPIC(filename); } // sets a value of the HRTF. // r true: right. r false: left public void set(int naz, int nel, int nt, float val, boolean r) { if (r) { hrtf_r[naz][nel][nt] = val; } else { hrtf_l[naz][nel][nt] = val; } } // gets an array of the HRTF for specific azimuth index, elevation index public double[] getNL(int naz, int nel, boolean r) { if (r) { return hrtf_r[naz][nel]; } else { return hrtf_r[naz][nel]; } } /* * loads HRTF data from file */ public void loadHRTFFromCIPIC(String filename) { try { File file = new File(filename); fillHRTF(file); } catch(Exception e) { e.printStackTrace(); } } public void fillHRTF(File file) { try { Reader in = new FileReader(file); BufferedReader bf = new BufferedReader(in); String delimiter = " "; String thisLine; for (int i = 0; i < 25; i ++) { for (int j = 0; j < 50; j++) { thisLine = bf.readLine(); java.util.StringTokenizer st = new java.util.StringTokenizer(thisLine, delimiter); for (int k = 0; k < 256; k++ ) { hrtf_l[i][j][k] = Double.valueOf(st.nextToken()).doubleValue(); } thisLine = bf.readLine(); st = new java.util.StringTokenizer(thisLine, delimiter); for (int k = 0; k < 256; k++ ) { hrtfI_l[i][j][k] = Double.valueOf(st.nextToken()).doubleValue(); } // right side thisLine = bf.readLine(); st = new java.util.StringTokenizer(thisLine, delimiter); for (int k = 0; k < 256; k++ ) { hrtf_r[i][j][k] = Double.valueOf(st.nextToken()).doubleValue(); } // imaginary right thisLine = bf.readLine(); st = new java.util.StringTokenizer(thisLine, delimiter); for (int k = 0; k < 256; k++ ) { hrtfI_r[i][j][k] = Double.valueOf(st.nextToken()).doubleValue(); } } } } catch (Exception e) { e.printStackTrace(); } } public void startSpatial() { doSpatial = true; } public void stopSpatial() { doSpatial = false; } /** * spatialize a mono audio buffer * at this point, the HRTF has already been prepared @param buf mono audio buffer to process. @param outL left channel output @param outR right channel output */ public void process(float [] buf, float [] outL, float [] outR) { int numWindows = buf.length / windowLength; // convert to input buf to double and // separate them into left/right channels int index = 0; //keeps track of window-index. for (int i = 0; i < numWindows; i++) { // copy source into left and right channels. separate(buf, index, index+windowLength, channelL, channelR); if (doSpatial) { // zero imaginary part for (int k = 0; k< channelL_img.length; k++) { channelL_img[k] = 0; } // fft input signal fft.doFFT(channelL, channelL_img, false); fft.doFFT(channelR, channelR_img, false); if (DEBUG) { if (i == 0) { System.out.println("orig"); for (int k = 0; k < windowLength; k++) { System.out.println(channelL[k] + " " + channelL_img[k]); } } if (i == 0) { System.out.println("hrtf_______________"); for (int k = 0; k < windowLength; k++) { System.out.println(hrtfL[k] + " " + hrtfL_img[k]); } } } // multiply signals together rp.multiplyRect(hrtfL, hrtfL_img, channelL, channelL_img); rp.multiplyRect(hrtfR, hrtfR_img, channelR, channelR_img); if (DEBUG) { if (i == 0) { System.out.println("after"); for (int k = 0; k < windowLength; k++) { System.out.println(channelL[k] + " "+ channelL_img[k]); } } } // inverse fft fft.doFFT(channelL, channelL_img, true); fft.doFFT(channelR, channelR_img, true); // copy window to output array for (int j = 0; j < windowLength; j++ ) { outL[index + j] = gain*(float)channelL[j]; outR[index + j] = gain*(float)channelR[j]; } } else { // copy window to output array for (int j = 0; j < windowLength; j++ ) { outL[index + j] = (float)channelL[j]; outR[index + j] = (float)channelR[j]; } } index += windowLength; } } /** right now, this just copies */ public void separate(float [] inputBuf, int start, int end, double [] outLeft, double [] outRight) { int j = 0; for (int i = start; i < end; i++) { outLeft[j] = inputBuf[i]; outRight[j] = inputBuf[i]; j++; } } public void setLocation(PositionData p) { float azi = p.getAzimuth(); float elev = p.getElevation(); // System.out.println("setlocation: orig " + azi + " " + elev); int [] init = ucdpulse.getNearest(azi, elev); int az = init[0]; int el = init[1]; /* System.out.println("HRTF.setlocation init[0]: " + init[0] + " init[1] "+ init[1]); System.out.println("HRTF.setlocation fftdHrtf.length: " + fftdHrtfL.length); System.out.println("HRTF.setlocation init.length: " + init.length); */ hrtfL = hrtf_l[az][el]; hrtfR = hrtf_r[az][el]; hrtfL_img = hrtfI_l[az][el]; hrtfR_img = hrtfI_r[az][el]; } }