1
0
Fork 0
mirror of https://github.com/zeldaret/oot.git synced 2025-06-08 01:21:52 +00:00
oot/src/libultra/audio/reverb.c
2025-06-07 05:51:14 +02:00

349 lines
12 KiB
C

#include "libaudio.h"
#include "synthInternals.h"
#include "stdbool.h"
#include "stddef.h"
#include "stdint.h"
#include "ultra64/convert.h"
#include "attributes.h"
#define RANGE 2.0
extern ALGlobals* alGlobals;
Acmd* _loadOutputBuffer(ALFx* r, ALDelay* d, s32 buff, s32 incount, Acmd* p);
Acmd* _loadBuffer(ALFx* r, s16* curr_ptr, s32 buff, s32 count, Acmd* p);
Acmd* _saveBuffer(ALFx* r, s16* curr_ptr, s32 buff, s32 count, Acmd* p);
Acmd* _filterBuffer(ALLowPass* lp, s32 buff, s32 count, Acmd* p);
f32 _doModFunc(ALDelay* d, s32 count);
Acmd* alFxPull(void* filter, s16* outp, s32 outCount, s32 sampleOffset, Acmd* p) {
Acmd* ptr = p;
ALFx* r = (ALFx*)filter;
ALFilter* source = r->filter.source;
s16 i;
s16 buff1;
s16 buff2;
s16 input;
s16 output;
s16* in_ptr;
s16* out_ptr;
UNUSED s16 gain;
s16* prev_out_ptr = NULL;
ALDelay* d;
UNUSED ALDelay* pd;
// pull channels going into this effect first
ptr = (*source->handler)(source, outp, outCount, sampleOffset, p);
input = AL_AUX_L_OUT;
output = AL_AUX_R_OUT;
buff1 = AL_TEMP_0;
buff2 = AL_TEMP_1;
aSetBuffer(ptr++, 0, 0x000, 0x000, outCount << 1); // set the buffer size
aMix(ptr++, 0, 0xDA83, AL_AUX_L_OUT, input); // .707L = L - .293L
aMix(ptr++, 0, 0x5A82, AL_AUX_R_OUT, input); // mix the AuxL and AuxR into the AuxL
// and write the mixed value to the delay line at r->input
ptr = _saveBuffer(r, r->input, input, outCount, ptr);
// clear the AL_AUX_R_OUT
aClearBuffer(ptr++, output, outCount << 1);
for (i = 0; i < r->section_count; i++) {
d = &r->delay[i]; // get the ALDelay structure
in_ptr = &r->input[-d->input];
out_ptr = &r->input[-d->output];
if (in_ptr == prev_out_ptr) {
s16 t = buff2;
buff2 = buff1;
buff1 = t;
} else {
// load data at in_ptr into buff1
ptr = _loadBuffer(r, in_ptr, buff1, outCount, ptr);
}
ptr = _loadOutputBuffer(r, d, buff2, outCount, ptr);
if (d->ffcoef != 0) {
aMix(ptr++, 0, (u16)d->ffcoef, buff1, buff2);
if (d->rs == NULL && d->lp == NULL) {
ptr = _saveBuffer(r, out_ptr, buff2, outCount, ptr);
}
}
if (d->fbcoef != 0) {
aMix(ptr++, 0, (u16)d->fbcoef, buff2, buff1);
ptr = _saveBuffer(r, in_ptr, buff1, outCount, ptr);
}
if (d->lp != NULL) {
ptr = _filterBuffer(d->lp, buff2, outCount, ptr);
}
if (d->rs == NULL) {
ptr = _saveBuffer(r, out_ptr, buff2, outCount, ptr);
}
if (d->gain != 0) {
aMix(ptr++, 0, (u16)d->gain, buff2, output);
}
prev_out_ptr = &r->input[d->output];
}
// bump the master delay line input pointer modulo the length
r->input += outCount;
if (r->input > &r->base[r->length]) {
r->input -= r->length;
}
// output already in AL_AUX_R_OUT, just copy to AL_AUX_L_OUT
aDMEMMove(ptr++, output, AL_AUX_L_OUT, outCount << 1);
return ptr;
}
s32 alFxParam(void* filter, s32 paramID, void* param) {
if (paramID == AL_FILTER_SET_SOURCE) {
ALFilter* f = (ALFilter*)filter;
f->source = (ALFilter*)param;
}
return 0;
}
/**
* This routine gets called by alSynSetFXParam. No checking takes place to
* verify the validity of the paramID or the param value. input and output
* values must be 8 byte aligned, so round down any param passed.
*/
s32 alFxParamHdl(void* filter, s32 paramID, void* param) {
ALFx* f = (ALFx*)filter;
s32 p = (paramID - 2) % 8;
s32 s = (paramID - 2) / 8;
s32 val = *(s32*)param;
#define INPUT_PARAM 0
#define OUTPUT_PARAM 1
#define FBCOEF_PARAM 2
#define FFCOEF_PARAM 3
#define GAIN_PARAM 4
#define CHORUSRATE_PARAM 5
#define CHORUSDEPTH_PARAM 6
#define LPFILT_PARAM 7
switch (p) {
case INPUT_PARAM:
f->delay[s].input = (u32)val & 0xFFFFFFF8;
break;
case OUTPUT_PARAM:
f->delay[s].output = (u32)val & 0xFFFFFFF8;
break;
case FFCOEF_PARAM:
f->delay[s].ffcoef = (s16)val;
break;
case FBCOEF_PARAM:
f->delay[s].fbcoef = (s16)val;
break;
case GAIN_PARAM:
f->delay[s].gain = (s16)val;
break;
case CHORUSRATE_PARAM:
f->delay[s].rsinc = (((f32)val / 1000) * RANGE) / alGlobals->drvr.outputRate;
break;
// the following constant is derived from:
//
// ratio = 2^(cents/1200)
//
// and therefore for hundredths of a cent
// x
// ln(ratio) = ---------------
// (120,000)/ln(2)
// where
// 120,000/ln(2) = 173123.40...
#define CONVERT 173123.404906676
case CHORUSDEPTH_PARAM:
f->delay[s].rsgain = ((f32)val / CONVERT) * (f->delay[s].output - f->delay[s].input);
break;
case LPFILT_PARAM:
if (f->delay[s].lp != NULL) {
f->delay[s].lp->fc = (s16)val;
_init_lpfilter(f->delay[s].lp);
}
break;
}
return 0;
}
Acmd* _loadOutputBuffer(ALFx* r, ALDelay* d, s32 buff, s32 incount, Acmd* p) {
Acmd* ptr = p;
s32 ratio;
s32 count;
s32 rbuff = AL_TEMP_2;
s16* out_ptr;
f32 fincount;
f32 fratio;
f32 delta;
s32 ramalign = 0;
s32 length;
// The following section implements the chorus resampling. Modulate where you pull
// the samples from, since you need varying amounts of samples.
if (d->rs != NULL) {
length = d->output - d->input;
// get the number of samples to modulate by
delta = _doModFunc(d, incount);
// find ratio of delta to delay length and quantize to same resolution as resampler
delta /= length; // convert delta from number of samples to a pitch ratio
delta = (s32)(delta * UNITY_PITCH); // quantize to value microcode will use
delta = delta / UNITY_PITCH;
// pitch ratio needs to be centered around 1, not zero
fratio = 1.0 - delta;
// d->rs->delta is the difference between the fractional and integer value of the samples needed.
// fratio * incount + rs->delta gives the number of samples needed for this frame.
fincount = d->rs->delta + (fratio * (f32)incount);
count = (s32)fincount; // quantize to s32
d->rs->delta = fincount - (f32)count; // calculate the round off and store
// d->rsdelta is amount the out_ptr has deviated from its starting position.
// You calc the out_ptr by taking d->output - d->rsdelta, and then using the
// negative of that as an index into the delay buffer. loadBuffer that uses this
// value then bumps it up if it is below the delay buffer.
out_ptr = &r->input[-(d->output - d->rsdelta)];
// calculate the number of samples needed to align the buffer
ramalign = ((intptr_t)out_ptr & 7) >> 1;
// load the rbuff with samples, note that there will be ramalign worth of samples at the
// begining which you don't care about.
ptr = _loadBuffer(r, out_ptr - ramalign, rbuff, count + ramalign, ptr);
// convert fratio to 16 bit fraction for microcode use
ratio = (s32)(fratio * UNITY_PITCH);
// set the buffers, and do the resample
aSetBuffer(ptr++, 0, rbuff + (ramalign << 1), buff, incount << 1);
aResample(ptr++, d->rs->first, ratio, osVirtualToPhysical(d->rs->state));
d->rs->first = false; // turn off first time flag
d->rsdelta += count - incount; // add the number of samples to d->rsdelta
} else {
out_ptr = &r->input[-d->output];
ptr = _loadBuffer(r, out_ptr, buff, incount, ptr);
}
return ptr;
}
/**
* This routine is for loading data from the delay line buff. If the
* address of curr_ptr < r->base, it will force it to be within r->base
* space, If the load goes past the end of r->base it will wrap around.
* Cause count bytes of data at curr_ptr (within the delay line) to be
* loaded into buff. (Buff is a dmem buffer)
*/
Acmd* _loadBuffer(ALFx* r, s16* curr_ptr, s32 buff, s32 count, Acmd* p) {
Acmd* ptr = p;
s32 after_end;
s32 before_end;
s16* updated_ptr;
s16* delay_end;
delay_end = &r->base[r->length];
if (curr_ptr < r->base) {
curr_ptr += r->length;
}
updated_ptr = curr_ptr + count;
if (updated_ptr > delay_end) {
after_end = updated_ptr - delay_end;
before_end = delay_end - curr_ptr;
aSetBuffer(ptr++, 0, buff, 0x000, before_end << 1);
aLoadBuffer(ptr++, osVirtualToPhysical(curr_ptr));
aSetBuffer(ptr++, 0, buff + (before_end << 1), 0, after_end << 1);
aLoadBuffer(ptr++, osVirtualToPhysical(r->base));
} else {
aSetBuffer(ptr++, 0, buff, 0x000, count << 1);
aLoadBuffer(ptr++, osVirtualToPhysical(curr_ptr));
}
aSetBuffer(ptr++, 0, 0x000, 0x000, count << 1);
return ptr;
}
/**
* This routine is for writing data to the delay line buff. If the
* address of curr_ptr < r->base, it will force it to be within r->base
* space. If the write goes past the end of r->base, it will wrap around
* Cause count bytes of data at buff to be written to delay line, curr_ptr.
*/
Acmd* _saveBuffer(ALFx* r, s16* curr_ptr, s32 buff, s32 count, Acmd* p) {
Acmd* ptr = p;
s32 after_end;
s32 before_end;
s16* updated_ptr;
s16* delay_end;
delay_end = &r->base[r->length];
if (curr_ptr < r->base) {
// probably just security, shouldn't occur
curr_ptr += r->length;
}
updated_ptr = curr_ptr + count;
if (updated_ptr > delay_end) { // if the data wraps past end of r->base
after_end = updated_ptr - delay_end;
before_end = delay_end - curr_ptr;
aSetBuffer(ptr++, 0, 0x000, buff, before_end << 1);
aSaveBuffer(ptr++, osVirtualToPhysical(curr_ptr));
aSetBuffer(ptr++, 0, 0x000, buff + (before_end << 1), after_end << 1);
aSaveBuffer(ptr++, osVirtualToPhysical(r->base));
aSetBuffer(ptr++, 0, 0x000, 0x000, count << 1);
} else {
aSetBuffer(ptr++, 0, 0x000, buff, count << 1);
aSaveBuffer(ptr++, osVirtualToPhysical(curr_ptr));
}
return ptr;
}
Acmd* _filterBuffer(ALLowPass* lp, s32 buff, s32 count, Acmd* p) {
Acmd* ptr = p;
aSetBuffer(ptr++, 0, buff, buff, count << 1);
aLoadADPCM(ptr++, 32, osVirtualToPhysical(lp->fcvec.fccoef));
aPoleFilter(ptr++, lp->first, lp->fgain, osVirtualToPhysical(lp->fstate));
lp->first = false;
return ptr;
}
/**
* Generate a triangle wave from -1 to 1, and find the current position
* in the wave. (Rate of the wave is controlled by d->rsinc, which is chorus
* rate) Multiply the current triangle wave value by d->rsgain, (chorus depth)
* which is expressed in number of samples back from output pointer the chorus
* should go at it's full chorus. In otherwords, this function returns a number
* of samples the output pointer should modulate backwards.
*/
f32 _doModFunc(ALDelay* d, s32 count) {
f32 val;
// generate bipolar sawtooth from -RANGE to +RANGE
d->rsval += d->rsinc * count;
d->rsval = (d->rsval > RANGE) ? (d->rsval - RANGE * 2) : d->rsval;
// convert to monopolar triangle from 0 to RANGE
val = d->rsval;
val = (val < 0) ? -val : val;
// convert to bipolar triangle from -1 to 1
val -= RANGE / 2;
return d->rsgain * val;
}