void HrtfData::computeCoefficientsStereo(float elevation, float azimuth, float *left, float* right, bool linphase) { //wrap azimuth to be > 0 and < 360. azimuth = ringmodf(azimuth, 360.0f); //the hrtf datasets are right ear coefficients. Consequently, the right ear requires no changes. computeCoefficientsMono(elevation, azimuth, right, linphase); //the left ear is found at an azimuth which is reflectred about 0 degrees. azimuth = ringmodf(360-azimuth, 360.0f); computeCoefficientsMono(elevation, azimuth, left, linphase); }
void AmplitudePanner::pan(float* input, float** outputs) { //TODO: move all this logic into something that's only called when needed. //the two degenerates: 0 and 1 channels. if(input == nullptr || outputs == nullptr || channels.size() == 0) return; if(channels.size() == 1) { std::copy(input, input+block_size, outputs[channels[0].channel]); return; } //We need a local copy. float angle = azimuth; angle = ringmodf(angle, 360.0f); int left = 0, right = 0; bool found_right= false; for(int i = 0; i < channels.size(); i++) { if(channels[i].angle >= angle) { right = i; found_right = true; break; } } if(found_right == false) { right = 0; left = channels.size()-1; } else { left = right == 0 ? channels.size()-1 : right-1; } int channel1 = channels[left].channel; int channel2 = channels[right].channel; //two cases: we wrapped or didn't. float angle1, angle2, angleSum; if(right == 0) { //left is all the way around, special handling is needed. if(angle >= channels[left].angle) { angle1 = angle-channels[left].angle; //angle between left and source. angle2 = (360.0f-angle)+channels[right].angle; } else { angle1 = (360-channels[left].angle)+angle; angle2 = fabs(channels[right].angle-angle); } } else { angle1 = fabs(channels[left].angle-angle); angle2 = fabs(channels[right].angle-angle); } angleSum = angle1+angle2; float weight2 = angle1/angleSum; float weight1 = angle2/angleSum; scalarMultiplicationKernel(block_size, weight1, input, outputs[channel1]); scalarMultiplicationKernel(block_size, weight2, input, outputs[channel2]); }
void AmplitudePanner::addEntry(float angle, int channel) { channels.emplace_back(ringmodf(angle, 360.0f), channel); std::sort(channels.begin(), channels.end(), [](AmplitudePannerEntry &a, AmplitudePannerEntry& b) {return a.angle < b.angle;}); }
//a complete HRTF for stereo is two calls to this function. //some final preparation is done afterwords. //This is very complicated, thus the heavy commenting. //todo: can this be made simpler? void HrtfData::computeCoefficientsMono(float elevation, float azimuth, float* out, bool linphase) { //clamp the elevation. if(elevation < min_elevation) { elevation = (float)min_elevation; } else if(elevation > max_elevation) { elevation = (float)max_elevation; } //we need to convert the elevation into an index. First, truncate it to an integer (which rounds towards 0). int truncatedElevation = (int)truncf(elevation); //we now need to know how many degrees there are per elevation increase/decrease. This is a bit tricky and, if done wrong, will result in an off-by-one. int degreesPerElevation = 0; if(min_elevation != max_elevation) { //it's not 0, it has to be something else, and the count has to be at least 2. //it's the difference between min and max, dividedd by the count. //The count includes both endpoints of an interval, as well as all the marks between. //We subtract 1 because we're not counting points, we're counting spaces between points. degreesPerElevation = (max_elevation-min_elevation)/(elev_count-1); } //we have a truncated elevation. We now simply take an integer division, or assume it's 0. //This is an array because We need to do the vertical crossfade in this function. int elevationIndex[2]; elevationIndex[0] = degreesPerElevation ? truncatedElevation/degreesPerElevation : 0; //this is relative to whatever index happens to be "0", that is it is an offset from the 0 index. We have to offset it upwards so it's not negative. int elevationIndexOffset = degreesPerElevation ? abs(min_elevation)/degreesPerElevation : 0; elevationIndex[0] += elevationIndexOffset; elevationIndex[1] = std::min(elevationIndex[0]+1, elev_count-1); double elevationWeights[2]; float ringmoddedElevation = ringmodf(elevation, degreesPerElevation); if(ringmoddedElevation < 0) ringmoddedElevation =degreesPerElevation-ringmoddedElevation; if(ringmoddedElevation > degreesPerElevation) ringmoddedElevation = degreesPerElevation; elevationWeights[0] = (degreesPerElevation-ringmoddedElevation)/degreesPerElevation; elevationWeights[1] = ringmoddedElevation/degreesPerElevation; memset(out, 0, sizeof(float)*hrir_length); for(int i = 0; i < 2; i++) { //ElevationIndex lets us get an array of azimuth coefficients. Go ahead and pull it out now, so we can conceptually forget about all the above variables. float** azimuths = hrirs[elevationIndex[i]]; int azimuthCount = azimuth_counts[elevationIndex[i]]; float degreesPerAzimuth = 360.0f/azimuthCount; int azimuthIndex1, azimuthIndex2; azimuthIndex1 = (int)floorf(azimuth/degreesPerAzimuth); azimuthIndex2 = azimuthIndex1+1; float azimuthWeight1, azimuthWeight2; //this is the same logic as a bunch of other places, with a minor variation. azimuthWeight1 = ceilf(azimuthIndex2*degreesPerAzimuth-azimuth)/degreesPerAzimuth; azimuthWeight2 = floorf(azimuth-azimuthIndex1*degreesPerAzimuth)/degreesPerAzimuth; //now that we have some weights, we need to ringmod the azimuth indices. 360==0 in trig, but causes one of them to go past the end. azimuthIndex1 = ringmodi(azimuthIndex1, azimuthCount); azimuthIndex2 = ringmodi(azimuthIndex2, azimuthCount); //this is probably the only part of this that can't go wrong, assuming the above calculations are all correct. Interpolate between the two azimuths. if(linphase == false) { for(int j = 0; j < hrir_length; j++) { out[j] += elevationWeights[i]*(azimuthWeight1*azimuths[azimuthIndex1][j]+azimuthWeight2*azimuths[azimuthIndex2][j]); } } //otherwise, the slower version; this involves copies and the fft. else { std::copy(azimuths[azimuthIndex1], azimuths[azimuthIndex1]+hrir_length, temporary_buffer1); std::copy(azimuths[azimuthIndex2], azimuths[azimuthIndex2]+hrir_length, temporary_buffer2); linearPhase(temporary_buffer1); linearPhase(temporary_buffer2); for(int j = 0; j < hrir_length; j++) { out[j] += elevationWeights[i]*(temporary_buffer1[j]*azimuthWeight1+temporary_buffer2[j]*azimuthWeight2); } } } }