1 /***************************************************************************
\r
2 reverb.c - description
\r
4 begin : Wed May 15 2002
\r
5 copyright : (C) 2002 by Pete Bernert
\r
6 email : BlackDove@addcom.de
\r
8 Portions (C) GraÅžvydas "notaz" Ignotas, 2010-2011
\r
9 Portions (C) SPU2-X, gigaherz, Pcsx2 Development Team
\r
11 ***************************************************************************/
\r
12 /***************************************************************************
\r
14 * This program is free software; you can redistribute it and/or modify *
\r
15 * it under the terms of the GNU General Public License as published by *
\r
16 * the Free Software Foundation; either version 2 of the License, or *
\r
17 * (at your option) any later version. See also the license.txt file for *
\r
18 * additional informations. *
\r
20 ***************************************************************************/
\r
26 // will be included from spu.c
\r
29 ////////////////////////////////////////////////////////////////////////
\r
31 ////////////////////////////////////////////////////////////////////////
\r
33 // REVERB info and timing vars...
\r
37 int * sRVBStart = 0;
\r
39 ////////////////////////////////////////////////////////////////////////
\r
41 ////////////////////////////////////////////////////////////////////////
\r
43 INLINE void StartREVERB(int ch)
\r
45 if(s_chan[ch].bReverb && (spuCtrl&0x80)) // reverb possible?
\r
47 s_chan[ch].bRVBActive=!!iUseReverb;
\r
49 else s_chan[ch].bRVBActive=0; // else -> no reverb
\r
52 ////////////////////////////////////////////////////////////////////////
\r
53 // HELPER FOR NEILL'S REVERB: re-inits our reverb mixing buf
\r
54 ////////////////////////////////////////////////////////////////////////
\r
56 INLINE void InitREVERB(int ns_to)
\r
58 memset(sRVBStart,0,ns_to*sizeof(sRVBStart[0])*2);
\r
61 ////////////////////////////////////////////////////////////////////////
\r
63 INLINE int rvb2ram_offs(int curr, int space, int iOff)
\r
66 if (iOff >= 0x40000) iOff -= space;
\r
70 // get_buffer content helper: takes care about wraps
\r
71 #define g_buffer(var) \
\r
72 ((int)(signed short)spuMem[rvb2ram_offs(curr_addr, space, rvb.n##var)])
\r
74 // saturate iVal and store it as var
\r
75 #define s_buffer(var, iVal) \
\r
76 ssat32_to_16(iVal); \
\r
77 spuMem[rvb2ram_offs(curr_addr, space, rvb.n##var)] = iVal
\r
79 #define s_buffer1(var, iVal) \
\r
80 ssat32_to_16(iVal); \
\r
81 spuMem[rvb2ram_offs(curr_addr, space, rvb.n##var + 1)] = iVal
\r
83 ////////////////////////////////////////////////////////////////////////
\r
85 // portions based on spu2-x from PCSX2
\r
86 static void MixREVERB(int ns_to)
\r
88 int l_old = rvb.iRVBLeft;
\r
89 int r_old = rvb.iRVBRight;
\r
90 int curr_addr = rvb.CurrAddr;
\r
91 int space = 0x40000 - rvb.StartAddr;
\r
92 int l = 0, r = 0, ns;
\r
94 for (ns = 0; ns < ns_to * 2; )
\r
96 int IIR_ALPHA = rvb.IIR_ALPHA;
\r
97 int ACC0, ACC1, FB_A0, FB_A1, FB_B0, FB_B1;
\r
98 int mix_dest_a0, mix_dest_a1, mix_dest_b0, mix_dest_b1;
\r
100 int input_L = sRVBStart[ns] * rvb.IN_COEF_L;
\r
101 int input_R = sRVBStart[ns+1] * rvb.IN_COEF_R;
\r
103 int IIR_INPUT_A0 = ((g_buffer(IIR_SRC_A0) * rvb.IIR_COEF) + input_L) >> 15;
\r
104 int IIR_INPUT_A1 = ((g_buffer(IIR_SRC_A1) * rvb.IIR_COEF) + input_R) >> 15;
\r
105 int IIR_INPUT_B0 = ((g_buffer(IIR_SRC_B0) * rvb.IIR_COEF) + input_L) >> 15;
\r
106 int IIR_INPUT_B1 = ((g_buffer(IIR_SRC_B1) * rvb.IIR_COEF) + input_R) >> 15;
\r
108 int iir_dest_a0 = g_buffer(IIR_DEST_A0);
\r
109 int iir_dest_a1 = g_buffer(IIR_DEST_A1);
\r
110 int iir_dest_b0 = g_buffer(IIR_DEST_B0);
\r
111 int iir_dest_b1 = g_buffer(IIR_DEST_B1);
\r
113 int IIR_A0 = iir_dest_a0 + ((IIR_INPUT_A0 - iir_dest_a0) * IIR_ALPHA >> 15);
\r
114 int IIR_A1 = iir_dest_a1 + ((IIR_INPUT_A1 - iir_dest_a1) * IIR_ALPHA >> 15);
\r
115 int IIR_B0 = iir_dest_b0 + ((IIR_INPUT_B0 - iir_dest_b0) * IIR_ALPHA >> 15);
\r
116 int IIR_B1 = iir_dest_b1 + ((IIR_INPUT_B1 - iir_dest_b1) * IIR_ALPHA >> 15);
\r
118 s_buffer1(IIR_DEST_A0, IIR_A0);
\r
119 s_buffer1(IIR_DEST_A1, IIR_A1);
\r
120 s_buffer1(IIR_DEST_B0, IIR_B0);
\r
121 s_buffer1(IIR_DEST_B1, IIR_B1);
\r
123 ACC0 = (g_buffer(ACC_SRC_A0) * rvb.ACC_COEF_A +
\r
124 g_buffer(ACC_SRC_B0) * rvb.ACC_COEF_B +
\r
125 g_buffer(ACC_SRC_C0) * rvb.ACC_COEF_C +
\r
126 g_buffer(ACC_SRC_D0) * rvb.ACC_COEF_D) >> 15;
\r
127 ACC1 = (g_buffer(ACC_SRC_A1) * rvb.ACC_COEF_A +
\r
128 g_buffer(ACC_SRC_B1) * rvb.ACC_COEF_B +
\r
129 g_buffer(ACC_SRC_C1) * rvb.ACC_COEF_C +
\r
130 g_buffer(ACC_SRC_D1) * rvb.ACC_COEF_D) >> 15;
\r
132 FB_A0 = g_buffer(FB_SRC_A0);
\r
133 FB_A1 = g_buffer(FB_SRC_A1);
\r
134 FB_B0 = g_buffer(FB_SRC_B0);
\r
135 FB_B1 = g_buffer(FB_SRC_B1);
\r
137 mix_dest_a0 = ACC0 - ((FB_A0 * rvb.FB_ALPHA) >> 15);
\r
138 mix_dest_a1 = ACC1 - ((FB_A1 * rvb.FB_ALPHA) >> 15);
\r
140 mix_dest_b0 = FB_A0 + (((ACC0 - FB_A0) * rvb.FB_ALPHA - FB_B0 * rvb.FB_X) >> 15);
\r
141 mix_dest_b1 = FB_A1 + (((ACC1 - FB_A1) * rvb.FB_ALPHA - FB_B1 * rvb.FB_X) >> 15);
\r
143 s_buffer(MIX_DEST_A0, mix_dest_a0);
\r
144 s_buffer(MIX_DEST_A1, mix_dest_a1);
\r
145 s_buffer(MIX_DEST_B0, mix_dest_b0);
\r
146 s_buffer(MIX_DEST_B1, mix_dest_b1);
\r
148 l = (mix_dest_a0 + mix_dest_b0) / 2;
\r
149 r = (mix_dest_a1 + mix_dest_b1) / 2;
\r
151 l = (l * rvb.VolLeft) >> 15; // 15?
\r
152 r = (r * rvb.VolRight) >> 15;
\r
154 SSumLR[ns++] += (l + l_old) / 2;
\r
155 SSumLR[ns++] += (r + r_old) / 2;
\r
163 if (curr_addr >= 0x40000) curr_addr = rvb.StartAddr;
\r
168 rvb.CurrAddr = curr_addr;
\r
171 static void MixREVERB_off(int ns_to)
\r
173 int l_old = rvb.iRVBLeft;
\r
174 int r_old = rvb.iRVBRight;
\r
175 int curr_addr = rvb.CurrAddr;
\r
176 int space = 0x40000 - rvb.StartAddr;
\r
177 int l = 0, r = 0, ns;
\r
179 for (ns = 0; ns < ns_to * 2; )
\r
181 l = (g_buffer(MIX_DEST_A0) + g_buffer(MIX_DEST_B0)) / 2;
\r
182 r = (g_buffer(MIX_DEST_A1) + g_buffer(MIX_DEST_B1)) / 2;
\r
184 l = (l * rvb.VolLeft) >> 15;
\r
185 r = (r * rvb.VolRight) >> 15;
\r
187 SSumLR[ns++] += (l + l_old) / 2;
\r
188 SSumLR[ns++] += (r + r_old) / 2;
\r
196 if (curr_addr >= 0x40000) curr_addr = rvb.StartAddr;
\r
201 rvb.CurrAddr = curr_addr;
\r
204 static void prepare_offsets(void)
\r
206 int space = 0x40000 - rvb.StartAddr;
\r
208 #define prep_offs(v) \
\r
210 while (t >= space) \
\r
213 #define prep_offs2(d, v1, v2) \
\r
214 t = rvb.v1 - rvb.v2; \
\r
215 while (t >= space) \
\r
219 prep_offs(IIR_SRC_A0);
\r
220 prep_offs(IIR_SRC_A1);
\r
221 prep_offs(IIR_SRC_B0);
\r
222 prep_offs(IIR_SRC_B1);
\r
223 prep_offs(IIR_DEST_A0);
\r
224 prep_offs(IIR_DEST_A1);
\r
225 prep_offs(IIR_DEST_B0);
\r
226 prep_offs(IIR_DEST_B1);
\r
227 prep_offs(ACC_SRC_A0);
\r
228 prep_offs(ACC_SRC_A1);
\r
229 prep_offs(ACC_SRC_B0);
\r
230 prep_offs(ACC_SRC_B1);
\r
231 prep_offs(ACC_SRC_C0);
\r
232 prep_offs(ACC_SRC_C1);
\r
233 prep_offs(ACC_SRC_D0);
\r
234 prep_offs(ACC_SRC_D1);
\r
235 prep_offs(MIX_DEST_A0);
\r
236 prep_offs(MIX_DEST_A1);
\r
237 prep_offs(MIX_DEST_B0);
\r
238 prep_offs(MIX_DEST_B1);
\r
239 prep_offs2(FB_SRC_A0, MIX_DEST_A0, FB_SRC_A);
\r
240 prep_offs2(FB_SRC_A1, MIX_DEST_A1, FB_SRC_A);
\r
241 prep_offs2(FB_SRC_B0, MIX_DEST_B0, FB_SRC_B);
\r
242 prep_offs2(FB_SRC_B1, MIX_DEST_B1, FB_SRC_B);
\r
249 INLINE void REVERBDo(int ns_to)
\r
251 if (!rvb.StartAddr) // reverb is off
\r
253 rvb.iRVBLeft = rvb.iRVBRight = 0;
\r
257 if (spuCtrl & 0x80) // -> reverb on? oki
\r
259 if (unlikely(rvb.dirty))
\r
264 else if (rvb.VolLeft || rvb.VolRight)
\r
266 if (unlikely(rvb.dirty))
\r
269 MixREVERB_off(ns_to);
\r
271 else // -> reverb off
\r
273 // reverb runs anyway
\r
274 rvb.CurrAddr += ns_to / 2;
\r
275 while (rvb.CurrAddr >= 0x40000)
\r
276 rvb.CurrAddr -= 0x40000 - rvb.StartAddr;
\r
280 ////////////////////////////////////////////////////////////////////////
\r
285 -----------------------------------------------------------------------------
\r
286 PSX reverb hardware notes
\r
288 -----------------------------------------------------------------------------
\r
290 Yadda yadda disclaimer yadda probably not perfect yadda well it's okay anyway
\r
293 -----------------------------------------------------------------------------
\r
298 - The reverb buffer is 22khz 16-bit mono PCM.
\r
299 - It starts at the reverb address given by 1DA2, extends to
\r
300 the end of sound RAM, and wraps back to the 1DA2 address.
\r
302 Setting the address at 1DA2 resets the current reverb work address.
\r
304 This work address ALWAYS increments every 1/22050 sec., regardless of
\r
305 whether reverb is enabled (bit 7 of 1DAA set).
\r
307 And the contents of the reverb buffer ALWAYS play, scaled by the
\r
308 "reverberation depth left/right" volumes (1D84/1D86).
\r
309 (which, by the way, appear to be scaled so 3FFF=approx. 1.0, 4000=-1.0)
\r
311 -----------------------------------------------------------------------------
\r
316 These are probably not their real names.
\r
317 These are probably not even correct names.
\r
318 We will use them anyway, because we can.
\r
320 1DC0: FB_SRC_A (offset)
\r
321 1DC2: FB_SRC_B (offset)
\r
322 1DC4: IIR_ALPHA (coef.)
\r
323 1DC6: ACC_COEF_A (coef.)
\r
324 1DC8: ACC_COEF_B (coef.)
\r
325 1DCA: ACC_COEF_C (coef.)
\r
326 1DCC: ACC_COEF_D (coef.)
\r
327 1DCE: IIR_COEF (coef.)
\r
328 1DD0: FB_ALPHA (coef.)
\r
330 1DD4: IIR_DEST_A0 (offset)
\r
331 1DD6: IIR_DEST_A1 (offset)
\r
332 1DD8: ACC_SRC_A0 (offset)
\r
333 1DDA: ACC_SRC_A1 (offset)
\r
334 1DDC: ACC_SRC_B0 (offset)
\r
335 1DDE: ACC_SRC_B1 (offset)
\r
336 1DE0: IIR_SRC_A0 (offset)
\r
337 1DE2: IIR_SRC_A1 (offset)
\r
338 1DE4: IIR_DEST_B0 (offset)
\r
339 1DE6: IIR_DEST_B1 (offset)
\r
340 1DE8: ACC_SRC_C0 (offset)
\r
341 1DEA: ACC_SRC_C1 (offset)
\r
342 1DEC: ACC_SRC_D0 (offset)
\r
343 1DEE: ACC_SRC_D1 (offset)
\r
344 1DF0: IIR_SRC_B1 (offset)
\r
345 1DF2: IIR_SRC_B0 (offset)
\r
346 1DF4: MIX_DEST_A0 (offset)
\r
347 1DF6: MIX_DEST_A1 (offset)
\r
348 1DF8: MIX_DEST_B0 (offset)
\r
349 1DFA: MIX_DEST_B1 (offset)
\r
350 1DFC: IN_COEF_L (coef.)
\r
351 1DFE: IN_COEF_R (coef.)
\r
353 The coefficients are signed fractional values.
\r
354 -32768 would be -1.0
\r
355 32768 would be 1.0 (if it were possible... the highest is of course 32767)
\r
357 The offsets are (byte/8) offsets into the reverb buffer.
\r
358 i.e. you multiply them by 8, you get byte offsets.
\r
359 You can also think of them as (samples/4) offsets.
\r
360 They appear to be signed. They can be negative.
\r
361 None of the documented presets make them negative, though.
\r
363 Yes, 1DF0 and 1DF2 appear to be backwards. Not a typo.
\r
365 -----------------------------------------------------------------------------
\r
370 We take all reverb sources:
\r
371 - regular channels that have the reverb bit on
\r
372 - cd and external sources, if their reverb bits are on
\r
373 and mix them into one stereo 44100hz signal.
\r
375 Lowpass/downsample that to 22050hz. The PSX uses a proper bandlimiting
\r
376 algorithm here, but I haven't figured out the hysterically exact specifics.
\r
377 I use an 8-tap filter with these coefficients, which are nice but probably
\r
389 So we have two input samples (INPUT_SAMPLE_L, INPUT_SAMPLE_R) every 22050hz.
\r
391 * IN MY EMULATION, I divide these by 2 to make it clip less.
\r
392 (and of course the L/R output coefficients are adjusted to compensate)
\r
393 The real thing appears to not do this.
\r
395 At every 22050hz tick:
\r
396 - If the reverb bit is enabled (bit 7 of 1DAA), execute the reverb
\r
397 steady-state algorithm described below
\r
398 - AFTERWARDS, retrieve the "wet out" L and R samples from the reverb buffer
\r
399 (This part may not be exactly right and I guessed at the coefs. TODO: check later.)
\r
400 L is: 0.333 * (buffer[MIX_DEST_A0] + buffer[MIX_DEST_B0])
\r
401 R is: 0.333 * (buffer[MIX_DEST_A1] + buffer[MIX_DEST_B1])
\r
402 - Advance the current buffer position by 1 sample
\r
404 The wet out L and R are then upsampled to 44100hz and played at the
\r
405 "reverberation depth left/right" (1D84/1D86) volume, independent of the main
\r
408 -----------------------------------------------------------------------------
\r
410 Reverb steady-state
\r
411 -------------------
\r
413 The reverb steady-state algorithm is fairly clever, and of course by
\r
414 "clever" I mean "batshit insane".
\r
416 buffer[x] is relative to the current buffer position, not the beginning of
\r
417 the buffer. Note that all buffer offsets must wrap around so they're
\r
418 contained within the reverb work area.
\r
420 Clipping is performed at the end... maybe also sooner, but definitely at
\r
423 IIR_INPUT_A0 = buffer[IIR_SRC_A0] * IIR_COEF + INPUT_SAMPLE_L * IN_COEF_L;
\r
424 IIR_INPUT_A1 = buffer[IIR_SRC_A1] * IIR_COEF + INPUT_SAMPLE_R * IN_COEF_R;
\r
425 IIR_INPUT_B0 = buffer[IIR_SRC_B0] * IIR_COEF + INPUT_SAMPLE_L * IN_COEF_L;
\r
426 IIR_INPUT_B1 = buffer[IIR_SRC_B1] * IIR_COEF + INPUT_SAMPLE_R * IN_COEF_R;
\r
428 IIR_A0 = IIR_INPUT_A0 * IIR_ALPHA + buffer[IIR_DEST_A0] * (1.0 - IIR_ALPHA);
\r
429 IIR_A1 = IIR_INPUT_A1 * IIR_ALPHA + buffer[IIR_DEST_A1] * (1.0 - IIR_ALPHA);
\r
430 IIR_B0 = IIR_INPUT_B0 * IIR_ALPHA + buffer[IIR_DEST_B0] * (1.0 - IIR_ALPHA);
\r
431 IIR_B1 = IIR_INPUT_B1 * IIR_ALPHA + buffer[IIR_DEST_B1] * (1.0 - IIR_ALPHA);
\r
433 buffer[IIR_DEST_A0 + 1sample] = IIR_A0;
\r
434 buffer[IIR_DEST_A1 + 1sample] = IIR_A1;
\r
435 buffer[IIR_DEST_B0 + 1sample] = IIR_B0;
\r
436 buffer[IIR_DEST_B1 + 1sample] = IIR_B1;
\r
438 ACC0 = buffer[ACC_SRC_A0] * ACC_COEF_A +
\r
439 buffer[ACC_SRC_B0] * ACC_COEF_B +
\r
440 buffer[ACC_SRC_C0] * ACC_COEF_C +
\r
441 buffer[ACC_SRC_D0] * ACC_COEF_D;
\r
442 ACC1 = buffer[ACC_SRC_A1] * ACC_COEF_A +
\r
443 buffer[ACC_SRC_B1] * ACC_COEF_B +
\r
444 buffer[ACC_SRC_C1] * ACC_COEF_C +
\r
445 buffer[ACC_SRC_D1] * ACC_COEF_D;
\r
447 FB_A0 = buffer[MIX_DEST_A0 - FB_SRC_A];
\r
448 FB_A1 = buffer[MIX_DEST_A1 - FB_SRC_A];
\r
449 FB_B0 = buffer[MIX_DEST_B0 - FB_SRC_B];
\r
450 FB_B1 = buffer[MIX_DEST_B1 - FB_SRC_B];
\r
452 buffer[MIX_DEST_A0] = ACC0 - FB_A0 * FB_ALPHA;
\r
453 buffer[MIX_DEST_A1] = ACC1 - FB_A1 * FB_ALPHA;
\r
454 buffer[MIX_DEST_B0] = (FB_ALPHA * ACC0) - FB_A0 * (FB_ALPHA^0x8000) - FB_B0 * FB_X;
\r
455 buffer[MIX_DEST_B1] = (FB_ALPHA * ACC1) - FB_A1 * (FB_ALPHA^0x8000) - FB_B1 * FB_X;
\r
457 -----------------------------------------------------------------------------
\r
460 // vim:shiftwidth=1:expandtab
\r