/*
* some code for sample mixing
* (C) notaz, 2006,2007
+ * (C) kub, 2019,2020 added filtering
*
* This work is licensed under the terms of MAME license.
* See COPYING file in the top-level directory.
/* limitter */
#define Limit16(val) \
+ val -= val >> 2; /* reduce level to avoid clipping */ \
if ((short)val != val) val = (val < 0 ? MINOUT : MAXOUT)
int mix_32_to_16l_level;
-static struct iir2 { // 2-pole IIR
- int x[2]; // sample buffer
+static struct iir {
+ int alpha; // alpha for EMA low pass
int y[2]; // filter intermediates
- int i;
} lfi2, rfi2;
// NB ">>" rounds to -infinity, "/" to 0. To compensate the effect possibly use
// "-(-y>>n)" (round to +infinity) instead of "y>>n" in places.
-// NB uses Q12 fixpoint; samples mustn't have more than 20 bits for this.
+// NB uses fixpoint; samples mustn't have more than (32-QB) bits. Adding the
+// outputs of the sound sources together yields a max. of 18 bits, restricting
+// QB to a maximum of 14.
#define QB 12
+// NB alpha for DC filtering shouldn't be smaller than 1/(1<<QB) to avoid loss.
+// exponential moving average combined DC filter and lowpass filter
+// y0[n] = (x[n]-y0[n-1])*alpha+y0[n-1], y1[n] = (y0[n] - y1[n-1])*(1-1/8192)
+static inline int filter_band(struct iir *fi2, int x)
+{
+ // low pass. alpha is Q8 to avoid loss by 32 bit overflow.
+// fi2->y[0] += ((x<<(QB-8)) - (fi2->y[0]>>8)) * fi2->alpha;
+ fi2->y[0] += (x - (fi2->y[0]>>QB)) * fi2->alpha;
+ // DC filter. for alpha=1-1/8192 cutoff ~1HZ, for 1-1/1024 ~7Hz
+ fi2->y[1] += (fi2->y[0] - fi2->y[1]) >> QB;
+ return (fi2->y[0] - fi2->y[1]) >> QB;
+}
+
// exponential moving average filter for DC filtering
-// y[n] = (x[n]-y[n-1])*(1/8192) (corner approx. 20Hz, gain 1)
-static inline int filter_exp(struct iir2 *fi2, int x)
+// y[n] = (x[n]-y[n-1])*(1-1/8192) (corner approx. 1Hz, gain 1)
+static inline int filter_exp(struct iir *fi2, int x)
{
- int xf = (x<<QB) - fi2->y[0];
- fi2->y[0] += xf >> 13;
- xf -= xf >> 2; // level reduction to avoid clipping from overshoot
- return xf>>QB;
+ fi2->y[1] += ((x << QB) - fi2->y[1]) >> QB;
+ return x - (fi2->y[1] >> QB);
}
// unfiltered (for testing)
-static inline int filter_null(struct iir2 *fi2, int x)
+static inline int filter_null(struct iir *fi2, int x)
{
return x;
}
+#define filter filter_band
+
#define mix_32_to_16l_stereo_core(dest, src, count, lv, fl) { \
int l, r; \
+ struct iir lf = lfi2, rf = rfi2; \
\
for (; count > 0; count--) \
{ \
l = r = *dest; \
l += *src++ >> lv; \
r += *src++ >> lv; \
- l = fl(&lfi2, l); \
- r = fl(&rfi2, r); \
+ l = fl(&lf, l); \
+ r = fl(&rf, r); \
Limit16(l); \
Limit16(r); \
*dest++ = l; \
*dest++ = r; \
} \
+ lfi2 = lf, rfi2 = rf; \
}
void mix_32_to_16l_stereo_lvl(short *dest, int *src, int count)
{
- mix_32_to_16l_stereo_core(dest, src, count, mix_32_to_16l_level, filter_exp);
+ mix_32_to_16l_stereo_core(dest, src, count, mix_32_to_16l_level, filter);
}
void mix_32_to_16l_stereo(short *dest, int *src, int count)
{
- mix_32_to_16l_stereo_core(dest, src, count, 0, filter_exp);
+ mix_32_to_16l_stereo_core(dest, src, count, 0, filter);
}
void mix_32_to_16_mono(short *dest, int *src, int count)
{
int l;
+ struct iir lf = lfi2;
for (; count > 0; count--)
{
l = *dest;
l += *src++;
- l = filter_exp(&lfi2, l);
+ l = filter(&lf, l);
Limit16(l);
*dest++ = l;
}
+ lfi2 = lf;
}
}
}
-void mix_reset(void)
+void mix_reset(int alpha_q16)
{
memset(&lfi2, 0, sizeof(lfi2));
memset(&rfi2, 0, sizeof(rfi2));
+ lfi2.alpha = rfi2.alpha = (0x10000-alpha_q16) >> 4; // filter alpha, Q12
}
extern int mix_32_to_16l_level;
void mix_32_to_16l_stereo_lvl(short *dest, int *src, int count);
-void mix_reset(void);
+void mix_reset(int alpha_q16);
@ limit
-@ reg=int_sample, lr=1, r3=tmp, kills flags
+@ reg=int_sample, r12=1, r8=tmp, kills flags
.macro Limit reg
- add r3, lr, \reg, asr #15
- bics r3, r3, #1 @ in non-overflow conditions r3 is 0 or 1
+ sub \reg, \reg, \reg, asr #2 @ reduce audio lvl some to avoid clipping
+ add r8, r12, \reg, asr #15
+ bics r8, r8, #1 @ in non-overflow conditions r8 is 0 or 1
movne \reg, #0x8000
subpl \reg, \reg, #1
.endm
-
@ limit and shift up by 16
-@ reg=int_sample, lr=1, r3=tmp, kills flags
+@ reg=int_sample, r12=1, r8=tmp, kills flags
.macro Limitsh reg
- add r3, lr, \reg, asr #15
- bics r3, r3, #1 @ in non-overflow conditions r3 is 0 or 1
+ sub \reg, \reg, \reg, asr #2 @ reduce audio lvl some to avoid clipping
+ add r8, r12,\reg, asr #15
+ bics r8, r8, #1 @ in non-overflow conditions r8 is 0 or 1
moveq \reg, \reg, lsl #16
movne \reg, #0x80000000
subpl \reg, \reg, #0x00010000
.endm
+
@ filter out DC offset
-@ in=int_sample (max 20 bit), y=filter memory, r3=tmp
+@ in=int_sample (max 20 bit), y=filter memory, r8=tmp
.macro DCfilt in y
- rsb r3, \y, \in, lsl #12 @ fixpoint 20.12
- add \y, \y, r3, asr #13
- sub r3, r3, r3, asr #2 @ reduce audio lvl some
- asr \in, r3, #12
+ rsb r8, \y, \in, lsl #12 @ fixpoint 20.12
+ add \y, \y, r8, asr #12 @ alpha = 1-1/4094
+ sub \in, \in, \y, asr #12
+.endm
+
+@ lowpass filter
+@ in=int_sample (max 20 bit), y=filter memory, r12=alpha(Q8), r8=tmp
+.macro LPfilt in y
+@ asr r8, \y, #8
+@ rsb r8, r8, \in, lsl #4 @ fixpoint 20.12
+ sub r8, \in, \y, asr #12 @ fixpoint 20.12
+ mla \y, r8, r12, \y
+ asr \in, \y, #12
.endm
+
@ mix 32bit audio (with 16bits really used, upper bits indicate overflow) with normal 16 bit audio with left channel only
@ warning: this function assumes dest is word aligned
.global mix_32_to_16l_stereo @ short *dest, int *src, int count
subs r2, r2, #4
bmi m32_16l_st_end
- mov lr, #1
ldr r12, =filter
- ldmia r12, {r10-r11}
+ ldr r8, [r12], #4
+ ldmia r12, {r3,r10-r11,lr}
+ str r8, [sp, #-4]!
m32_16l_st_loop:
ldmia r0, {r8,r12}
add r5, r5, r8, asr #16
add r6, r6, r12,asr #16
add r7, r7, r12,asr #16
+ ldr r12,[sp]
+ LPfilt r4, r3
+ LPfilt r5, lr
+ LPfilt r6, r3
+ LPfilt r7, lr
DCfilt r4, r10
DCfilt r5, r11
DCfilt r6, r10
DCfilt r7, r11
+ mov r12,#1
Limitsh r4
Limitsh r5
Limitsh r6
ldmia r1!,{r4,r5}
add r4, r4, r6
add r5, r5, r6
+ ldr r12,[sp]
+ LPfilt r4, r3
+ LPfilt r5, lr
DCfilt r4, r10
DCfilt r5, r11
+ mov r12,#1
Limitsh r4
Limitsh r5
orr r4, r5, r4, lsr #16
m32_16l_st_no_unal2:
ldr r12, =filter
- stmia r12, {r10-r11}
+ add r12,r12, #4
+ stmia r12, {r3,r10-r11,lr}
+ add sp, sp, #4
ldmfd sp!, {r4-r8,r10-r11,lr}
bx lr
mix_32_to_16_mono:
stmfd sp!, {r4-r8,r10-r11,lr}
- mov lr, #1
ldr r12, =filter
- ldr r10, [r12]
+ ldr r8, [r12], #4
+ ldmia r12, {r10-r11}
+ str r8, [sp, #-4]!
@ check if dest is word aligned
tst r0, #2
ldr r4, [r1], #4
sub r2, r2, #1
add r4, r4, r5
+ ldr r12,[sp]
+ LPfilt r4, r11
+ DCfilt r4, r10
+ mov r12,#1
Limit r4
strh r4, [r0], #2
add r7, r7, r12,asr #16
mov r12,r12,lsl #16
add r6, r6, r12,asr #16
+ ldr r12,[sp]
+ LPfilt r4, r11
+ LPfilt r5, r11
+ LPfilt r6, r11
+ LPfilt r7, r11
DCfilt r4, r10
DCfilt r5, r10
DCfilt r6, r10
DCfilt r7, r10
+ mov r12,#1
Limitsh r4
Limitsh r5
Limitsh r6
add r5, r5, r6, asr #16
mov r6, r6, lsl #16
add r4, r4, r6, asr #16
+ ldr r12,[sp]
+ LPfilt r4, r11
+ LPfilt r5, r11
DCfilt r4, r10
DCfilt r5, r10
+ mov r12,#1
Limitsh r4
Limitsh r5
orr r4, r5, r4, lsr #16
ldrsh r5, [r0]
ldr r4, [r1], #4
add r4, r4, r5
+ ldr r12,[sp]
+ LPfilt r4, r11
DCfilt r4, r10
+ mov r12,#1
Limit r4
strh r4, [r0], #2
m32_16_mo_no_unal:
ldr r12, =filter
- str r10, [r12]
+ add r12,r12, #4
+ stmia r12, {r10-r11}
+ add sp, sp, #4
ldmfd sp!, {r4-r8,r10-r11,lr}
bx lr
mov lr, #1
ldr r9, [r9]
ldr r12, =filter
- ldm r12, {r10-r11}
+ ldr r8, [r12], #4
+ ldmia r12, {r3,r10-r11,lr}
+ str r8, [sp, #-4]!
mov r2, r2, lsl #1
subs r2, r2, #4
mov r5, r5, asr r9
mov r6, r6, asr r9
mov r7, r7, asr r9
+ ldr r12,[sp]
+ LPfilt r4, r3
+ LPfilt r5, lr
+ LPfilt r6, r3
+ LPfilt r7, lr
DCfilt r4, r10
DCfilt r5, r11
DCfilt r6, r10
DCfilt r7, r11
+ mov r12,#1
Limitsh r4
Limitsh r5
Limitsh r6
add r5, r5, r6
mov r4, r4, asr r9
mov r5, r5, asr r9
+ ldr r12,[sp]
+ LPfilt r4, r3
+ LPfilt r5, lr
DCfilt r4, r10
DCfilt r5, r11
+ mov r12,#1
Limitsh r4
Limitsh r5
orr r4, r5, r4, lsr #16
m32_16l_st_l_no_unal2:
ldr r12, =filter
- stmia r12, {r10-r11}
+ add r12,r12, #4
+ stmia r12, {r3,r10-r11,lr}
+ add sp, sp, #4
ldmfd sp!, {r4-r11,lr}
bx lr
#endif /* __GP2X__ */
-.global mix_reset @ void
+.global mix_reset @ int alpha_q16
mix_reset:
- ldr r0, =filter
+ ldr r2, =filter
+ rsb r0, r0, #0x10000
+@ asr r0, r0, #8
+ asr r0, r0, #4
+ str r0, [r2], #4
mov r1, #0
- str r1, [r0]
- str r1, [r0, #4]
+ str r1, [r2], #4
+ str r1, [r2], #4
+ str r1, [r2], #4
+ str r1, [r2], #4
bx lr
.data
filter:
- .ds 8
+ .ds 4 @ alpha_q8
+ .ds 8 @ filter history for left channel
+ .ds 8 @ filter history for right channel
@ vim:filetype=armasm
// sn76496\r
extern int *sn76496_regs;\r
\r
-// Low pass filter 'previous' samples\r
-static int32_t lpf_lp;\r
-static int32_t lpf_rp;\r
-\r
-static void low_pass_filter_stereo(int *buf32, int length)\r
-{\r
- int samples = length;\r
- int *out32 = buf32;\r
- // Restore previous samples\r
- int32_t lpf_l = lpf_lp;\r
- int32_t lpf_r = lpf_rp;\r
-\r
- // Single-pole low-pass filter (6 dB/octave)\r
- int32_t factor_a = PicoIn.sndFilterRange;\r
- int32_t factor_b = 0x10000 - factor_a;\r
-\r
- do\r
- {\r
- // Apply low-pass filter\r
- lpf_l = (lpf_l * factor_a) + (out32[0] * factor_b);\r
- lpf_r = (lpf_r * factor_a) + (out32[1] * factor_b);\r
-\r
- // 16.16 fixed point\r
- lpf_l >>= 16;\r
- lpf_r >>= 16;\r
-\r
- // Update sound buffer\r
- *out32++ = lpf_l;\r
- *out32++ = lpf_r;\r
- }\r
- while (--samples);\r
-\r
- // Save last samples for next frame\r
- lpf_lp = lpf_l;\r
- lpf_rp = lpf_r;\r
-}\r
-\r
-static void low_pass_filter_mono(int *buf32, int length)\r
-{\r
- int samples = length;\r
- int *out32 = buf32;\r
- // Restore previous sample\r
- int32_t lpf_l = lpf_lp;\r
-\r
- // Single-pole low-pass filter (6 dB/octave)\r
- int32_t factor_a = PicoIn.sndFilterRange;\r
- int32_t factor_b = 0x10000 - factor_a;\r
-\r
- do\r
- {\r
- // Apply low-pass filter\r
- lpf_l = (lpf_l * factor_a) + (out32[0] * factor_b);\r
-\r
- // 16.16 fixed point\r
- lpf_l >>= 16;\r
-\r
- // Update sound buffer\r
- *out32++ = lpf_l;\r
- }\r
- while (--samples);\r
-\r
- // Save last sample for next frame\r
- lpf_lp = lpf_l;\r
-}\r
-\r
-void (*low_pass_filter)(int *buf32, int length) = low_pass_filter_stereo;\r
-\r
// ym2413\r
#define YM2413_CLK 3579545\r
OPLL old_opll;\r
PsndRerate(0);\r
timers_reset();\r
\r
- // Reset low pass filter\r
- lpf_lp = 0;\r
- lpf_rp = 0;\r
-\r
- mix_reset();\r
+ mix_reset(PicoIn.sndFilter ? PicoIn.sndFilterRange : 0);\r
}\r
\r
\r
// set mixer\r
PsndMix_32_to_16l = (PicoIn.opt & POPT_EN_STEREO) ? mix_32_to_16l_stereo : mix_32_to_16_mono;\r
\r
- // set low pass filter\r
- low_pass_filter = (PicoIn.opt & POPT_EN_STEREO) ? low_pass_filter_stereo : low_pass_filter_mono;\r
-\r
if (PicoIn.AHW & PAHW_PICO)\r
PicoReratePico();\r
}\r
if ((PicoIn.AHW & PAHW_32X) && (PicoIn.opt & POPT_EN_PWM))\r
p32x_pwm_update(buf32, length-offset, stereo);\r
\r
- // Apply low pass filter, if required\r
- if (PicoIn.sndFilter == 1) {\r
- low_pass_filter(buf32, length);\r
- }\r
-\r
// convert + limit to normal 16bit output\r
PsndMix_32_to_16l(PicoIn.sndOut+(offset<<stereo), buf32, length-offset);\r
\r
#include <pico/pico_int.h>
#include <pico/state.h>
#include <pico/patch.h>
+#include <pico/sound/mix.h>
#include "../common/input_pico.h"
#include "../common/version.h"
#include <libretro.h>
unsigned old_frameskip_type;
int old_vout_format;
double new_sound_rate;
+ unsigned short old_snd_filter;
+ int32_t old_snd_filter_range;
var.value = NULL;
var.key = "picodrive_input1";
PicoIn.opt &= ~POPT_EN_DRC;
#endif
+ old_snd_filter = PicoIn.sndFilter;
var.value = NULL;
var.key = "picodrive_audio_filter";
PicoIn.sndFilter = 0;
PicoIn.sndFilter = 1;
}
+ old_snd_filter_range = PicoIn.sndFilterRange;
var.value = NULL;
var.key = "picodrive_lowpass_range";
PicoIn.sndFilterRange = (60 * 65536) / 100;
PicoIn.sndFilterRange = (atoi(var.value) * 65536) / 100;
}
+ if (old_snd_filter != PicoIn.sndFilter || old_snd_filter_range != PicoIn.sndFilterRange) {
+ mix_reset(PicoIn.sndFilter ? PicoIn.sndFilterRange : 0);
+ }
+
old_frameskip_type = frameskip_type;
frameskip_type = 0;
var.key = "picodrive_frameskip";