summaryrefslogtreecommitdiff
path: root/src/libultra/audio/reverb.c
blob: 9edf3ab9a6b8a89d3f3124e6a75a56b2d659cd4a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
#include "libaudio.h"
#include "synthInternals.h"
#include "stdbool.h"
#include "stddef.h"
#include "stdint.h"
#include "ultra64/convert.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;
    s16 gain;
    s16* prev_out_ptr = NULL;
    ALDelay* d;
    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;
}