void LateReflectionsNode::recompute() { float density = getProperty(Lav_LATE_REFLECTIONS_DENSITY).getFloatValue(); float t60=getProperty(Lav_LATE_REFLECTIONS_T60).getFloatValue(); float t60_high =getProperty(Lav_LATE_REFLECTIONS_HF_T60).getFloatValue(); float t60_low =getProperty(Lav_LATE_REFLECTIONS_LF_T60).getFloatValue(); float hf_reference=getProperty(Lav_LATE_REFLECTIONS_HF_REFERENCE).getFloatValue(); float lf_reference = getProperty(Lav_LATE_REFLECTIONS_LF_REFERENCE).getFloatValue(); //The base delay is the amount we are delaying all delay lines by. float baseDelay = 0.003+(1.0f-density)*0.025; //Approximate delay line lengths using powers of primes. for(int i = 0; i < 16; i+=1) { //0, 4, 8, 12, 1, 5, 9, 13... int prime= coprimes[(i%4)*4+i/4]; //use change of base. double powerApprox = log(baseDelay*simulation->getSr())/log(prime); int neededPower=round(powerApprox); double delayInSamples = pow(prime, neededPower); double delay=delayInSamples/simulation->getSr(); delay = std::min(delay, 1.0); delays[i] = delay; } //The following two lines were determined experimentaly, and greatly reduce metallicness. //This is probably because, by default, the shortest and longest delay line are adjacent and this node is typically used with panners at the input and output. std::swap(delays[0], delays[15]); std::swap(delays[1], delays[14]); fdn.setDelays(delays); //configure the gains. for(int i= 0; i < order; i++) { gains[i] = t60ToGain(t60_low, delays[i]); } //Configure the filters. for(int i = 0; i < order; i++) { //We get the mid and high t60 gains, and turn them into db. double highGain=t60ToGain(t60_high, delays[i]); double midGain=t60ToGain(t60, delays[i]); double midDb=scalarToDb(midGain, gains[i]); double highDb = scalarToDb(highGain, midGain); //Careful reading of the audio eq cookbook reveals that when s=1, q is always sqrt(2). //We add a very tiny bit to help against numerical error. highshelves[i]->configure(Lav_BIQUAD_TYPE_HIGHSHELF, hf_reference, highDb, 1/sqrt(2.0)+1e-4); midshelves[i]->configure(Lav_BIQUAD_TYPE_HIGHSHELF, lf_reference, midDb, 1.0/sqrt(2.0)+1e-4); } //Finally, bake the gains into the fdn matrix: hadamard(order, fdn_matrix); for(int i=0; i < order; i++) { for(int j = 0; j < order; j++) { fdn_matrix[i*order+j]*=gains[i]; } } fdn.setMatrix(fdn_matrix); //Reduce the panning effect. //Explanation: the first sample of output should reach all of the 16 outputs at the same time, before degrading normally. //This offset basically helps the reflections feel more "centered" when all channels are fed by the source. //We add one sample here so that we never have a delay of 0, which reduces some possible compatability issues with delay lines. double panReductionDelay = *std::max_element(delays, delays+order)+1.0/simulation->getSr(); for(int i=0; i < order; i++) { double neededDelay = panReductionDelay-delays[i]; pan_reducers[i]->setDelay(neededDelay); } }
double gainToDb(double gain) { return scalarToDb(gain, 1.0); }