9front - general discussion about 9front
 help / color / mirror / Atom feed
* [9front] games/snes audio patches
@ 2021-02-15 10:25 Michael Forney
  2021-02-15 10:55 ` Steve Simon
                   ` (2 more replies)
  0 siblings, 3 replies; 12+ messages in thread
From: Michael Forney @ 2021-02-15 10:25 UTC (permalink / raw)
  To: 9front

[-- Attachment #1: Type: text/plain, Size: 1585 bytes --]

I noticed that the audio of games/snes was pretty bad for most games I
tried, so I did some bug hunting and found a few. Patches are attached.

The main issue (patch 1) was incorrect BRR decoding for filter types 2
and 3, which is the sample compression scheme that the SNES audio chip
uses.

I also saw that the noise and echo features of the DSP were
essentially just stubs. Patches 2 and 3 implement noise and echo
respectively.

Finally, I saw that it was using nearest-neighbor upsampling from 32
kHz to 44.1 kHz, which results in noticable aliasing. My first
thought was to pipe the output through audio/pcmconv which has a
high-quality resampler. However, games/snes uses audio write blocking
to control the emulation speed, so using a pipe here was problematic.
It would fill up the pipe buffer, then hang while pcmconv wrote the
upsampled data to /dev/audio, then repeat. Anyone have ideas about a
good way to reuse the pcmconv resampler for this (maybe split off into
a library)? For now, patch 4 changes it to use linear interpolation,
which is a big improvement and may be good enough.

Patch 5 is just a style patch to use the enums already defined instead
of hex constants in a couple places.

Here are some samples demonstrating the specific problems:
- BRR decoding
  https://mforney.org/misc/ct-122a-old.flac
  https://mforney.org/misc/ct-122a-new.flac
- Echo
  https://mforney.org/misc/ct-206-old.flac
  https://mforney.org/misc/ct-206-new.flac
- Noise (supposed to be wave noises)
  https://mforney.org/misc/ct-s02-old.flac
  https://mforney.org/misc/ct-s02-new.flac

[-- Attachment #2: 0001-games-snes-fix-BRR-decoding-with-filters-2-and-3.patch --]
[-- Type: text/plain, Size: 746 bytes --]

 From f3f21968489011d0fee39a054c63c70f1a18bb92
From: Michael Forney <mforney@mforney.org>
Date: Mon, 15 Feb 2021 09:28:12 +0000
Subject: games/snes: fix BRR decoding with filters 2 and 3


s1 and s2 should store the last and next to last output, but were set
in the wrong order, causing them both to be the last output. This
breaks filters 2 and 3, which both utilize s2.

diff a78495b8877d2a9e805d098b7963d3a3cf90c7c3 f3f21968489011d0fee39a054c63c70f1a18bb92
--- a/sys/src/games/snes/dsp.c	Wed Feb 10 01:00:05 2021
+++ b/sys/src/games/snes/dsp.c	Mon Feb 15 01:28:12 2021
@@ -275,8 +275,8 @@
 		d = (s16int)(clamp16(d) << 1);
 		p->buf[p->bp] = d;
 		p->buf[p->bp++ + 12] = d;
-		s1 = d;
 		s2 = s1;
+		s1 = d;
 		brr <<= 4;
 	}
 	if(p->bp == 12)

[-- Attachment #3: 0002-games-snes-implement-DSP-noise.patch --]
[-- Type: text/plain, Size: 933 bytes --]

 From c9d42c085558d9ac0272cd3df4e1868a2c410e46
From: Michael Forney <mforney@mforney.org>
Date: Mon, 15 Feb 2021 09:30:29 +0000
Subject: games/snes: implement DSP noise


I'm not sure if this LFSR is the same one used by the hardware or is
arbitrary, but it matches the noise sequence used by all other snes
emulators I looked at.

diff f3f21968489011d0fee39a054c63c70f1a18bb92 c9d42c085558d9ac0272cd3df4e1868a2c410e46
--- a/sys/src/games/snes/dsp.c	Mon Feb 15 01:28:12 2021
+++ b/sys/src/games/snes/dsp.c	Mon Feb 15 01:30:29 2021
@@ -6,7 +6,7 @@
 #include "fns.h"
 
 u8int dsp[256], dspstate;
-u16int dspcounter, noise;
+u16int dspcounter, noise = 0x8000;
 static s16int samp[2], echoin[2];
 
 enum {
@@ -517,6 +517,8 @@
 		}
 		if(dspcounter-- == 0)
 			dspcounter = 0x77ff;
+		if(envyes(dsp[FLG] & 0x1f))
+			noise = (noise << 13 ^ noise << 14) & 0x8000 | noise >> 1 & ~1;
 		break;
 	case 31: voice(0, 4); voice(2, 1); break;
 	}

[-- Attachment #4: 0003-games-snes-implement-DSP-echo.patch --]
[-- Type: text/plain, Size: 3281 bytes --]

 From b78eaf915465b6d6d13f2dc36964c9aad9a2b76c
From: Michael Forney <mforney@mforney.org>
Date: Mon, 15 Feb 2021 09:38:22 +0000
Subject: games/snes: implement DSP echo

diff c9d42c085558d9ac0272cd3df4e1868a2c410e46 b78eaf915465b6d6d13f2dc36964c9aad9a2b76c
--- a/sys/src/games/snes/dsp.c	Mon Feb 15 01:30:29 2021
+++ b/sys/src/games/snes/dsp.c	Mon Feb 15 01:38:22 2021
@@ -7,7 +7,9 @@
 
 u8int dsp[256], dspstate;
 u16int dspcounter, noise = 0x8000;
-static s16int samp[2], echoin[2];
+static s16int samp[2], echoin[2], echobuf[2*2*8];
+static u16int echoaddr;
+static int echobp, echopos;
 
 enum {
 	VOLL = 0,
@@ -24,15 +26,17 @@
 	EFB = 0x0d,
 	MVOLR = 0x1c,
 	EVOLL = 0x2c,
-	EVOLR = 0x2d,
 	PMON = 0x2d,
+	EVOLR = 0x3c,
 	NON = 0x3d,
 	EON = 0x4d,
 	KON = 0x4c,
 	KOFF = 0x5c,
 	DIR = 0x5d,
 	FLG = 0x6c,
+	ESA = 0x6d,
 	ENDX = 0x7c,
+	EDL = 0x7d,
 	NEWKON = 0x8e,
 	INT = 0x80,
 };
@@ -96,6 +100,13 @@
 	return v;
 }
 
+static void
+spcput16(u16int p, u16int v)
+{
+	spcmem[p++] = v;
+	spcmem[p] = v >> 8;
+}
+
 u8int
 dspread(u8int p)
 {
@@ -412,33 +423,57 @@
 echo(int s)
 {
 	static s16int echoout[2];
-	static u8int fir[8];
+	s16int *x;
+	s8int h;
 	int a, b;
 
+	x = echobuf + echobp;
 	switch(s){
 	case 22:
-		echoout[0] = 0;
-		fir[0] = dsp[0x0f];
+		echoaddr = (dsp[INT|ESA] << 8) + echopos;
+		x[0] = x[16] = spc16(echoaddr);
+		h = dsp[0x0f];
+		echoout[0] = x[2] * h >> 7;
+		echoout[1] = x[3] * h >> 7;
 		break;
 	case 23:
-		fir[1] = dsp[0x1f];
-		fir[2] = dsp[0x2f];
+		h = dsp[0x1f];
+		echoout[0] += x[4] * h >> 7;
+		echoout[1] += x[5] * h >> 7;
+		h = dsp[0x2f];
+		echoout[0] += x[6] * h >> 7;
+		echoout[1] += x[7] * h >> 7;
+		x[1] = x[17] = spc16(echoaddr + 2);
 		break;
 	case 24:
-		fir[3] = dsp[0x3f];
-		fir[4] = dsp[0x4f];
-		fir[5] = dsp[0x5f];
+		h = dsp[0x3f];
+		echoout[0] += x[8] * h >> 7;
+		echoout[1] += x[9] * h >> 7;
+		h = dsp[0x4f];
+		echoout[0] += x[10] * h >> 7;
+		echoout[1] += x[11] * h >> 7;
+		h = dsp[0x5f];
+		echoout[0] += x[12] * h >> 7;
+		echoout[1] += x[13] * h >> 7;
 		break;
 	case 25:
-		fir[6] = dsp[0x6f];
-		fir[7] = dsp[0x7f];
+		h = dsp[0x6f];
+		echoout[0] += x[14] * h >> 7;
+		echoout[1] += x[15] * h >> 7;
+		h = dsp[0x7f];
+		echoout[0] += x[16] * h >> 7;
+		echoout[1] += x[17] * h >> 7;
+		echoout[0] &= ~1;
+		echoout[1] &= ~1;
 		break;
 	case 26:
 		a = (samp[0] * (s8int)dsp[MVOLL]) >> 7;
 		b = (echoout[0] * (s8int)dsp[EVOLL]) >> 7;
 		samp[0] = clamp16(a + b);
-		echoin[0] = (echoin[0] * (s8int)dsp[EFB]) >> 7;
-		echoin[1] = (echoin[1] * (s8int)dsp[EFB]) >> 7;
+		a = echoout[0] * (s8int)dsp[EFB] >> 7;
+		echoin[0] = clamp16(echoin[0] + a) & ~1;
+		a = echoout[1] * (s8int)dsp[EFB] >> 7;
+		echoin[1] = clamp16(echoin[1] + a) & ~1;
 		break;
 	case 27:
 		a = (samp[1] * (s8int)dsp[MVOLR]) >> 7;
@@ -449,9 +484,22 @@
 		dsp[INT|FLG] = dsp[FLG];
 		break;
 	case 29:
+		dsp[INT|ESA] = dsp[ESA];
+		if(echopos == 0)
+			dsp[INT|EDL] = dsp[EDL] & 0xf;
+		echopos += 4;
+		if(echopos >= dsp[INT|EDL] << 11)
+			echopos = 0;
+		if((dsp[INT|FLG] & 0x20) == 0)
+			spcput16(echoaddr, echoin[0]);
+		echoin[0] = 0;
 		dsp[INT|FLG] = dsp[FLG];
 		break;
 	case 30:
+		if((dsp[INT|FLG] & 0x20) == 0)
+			spcput16(echoaddr + 2, echoin[1]);
+		echoin[1] = 0;
+		echobp = echobp + 2 & 15;
 		break;
 	}
 }

[-- Attachment #5: 0004-games-snes-use-linear-interpolation-to-resample.patch --]
[-- Type: text/plain, Size: 821 bytes --]

 From 909e181a497074c52e0be927c007cb6b6d66fc8c
From: Michael Forney <mforney@mforney.org>
Date: Mon, 15 Feb 2021 09:40:11 +0000
Subject: games/snes: use linear interpolation to resample


This is still not ideal, but noticably better than nearest-neighbor.

diff b78eaf915465b6d6d13f2dc36964c9aad9a2b76c 909e181a497074c52e0be927c007cb6b6d66fc8c
--- a/sys/src/games/snes/dsp.c	Mon Feb 15 01:38:22 2021
+++ b/sys/src/games/snes/dsp.c	Mon Feb 15 01:40:11 2021
@@ -64,13 +64,17 @@
 static void
 audiosample(s16int *s)
 {
+	static s16int s1[2];
+
 	stime -= 1<<16;
 	do {
-		sbufp[0] = s[0];
-		sbufp[1] = s[1];
+		sbufp[0] = s[0] + ((s[0] - s1[0]) * stime >> 16);
+		sbufp[1] = s[1] + ((s[1] - s1[1]) * stime >> 16);
 		sbufp += 2;
 		stime += (32000<<16)/Freq;
 	} while(stime < 0);
+	s1[0] = s[0];
+	s1[1] = s[1];
 }
 
 int

[-- Attachment #6: 0005-games-snes-use-enum-constants-KON-and-ENDX-instead-of-their-values.patch --]
[-- Type: text/plain, Size: 571 bytes --]

 From 903b37d82b46bee9050d911e81235c4d4d25e4e2
From: Michael Forney <mforney@mforney.org>
Date: Mon, 15 Feb 2021 09:42:11 +0000
Subject: games/snes: use enum constants KON and ENDX instead of their values

diff 909e181a497074c52e0be927c007cb6b6d66fc8c 903b37d82b46bee9050d911e81235c4d4d25e4e2
--- a/sys/src/games/snes/dsp.c	Mon Feb 15 01:40:11 2021
+++ b/sys/src/games/snes/dsp.c	Mon Feb 15 01:42:11 2021
@@ -124,10 +124,10 @@
 	if(p >= 0x80)
 		return;
 	switch(p){
-	case 0x4c:
+	case KON:
 		dsp[NEWKON] = v;
 		break;
-	case 0x7c:
+	case ENDX:
 		v = 0;
 		break;
 	}

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2021-02-22 19:46 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-02-15 10:25 [9front] games/snes audio patches Michael Forney
2021-02-15 10:55 ` Steve Simon
2021-02-17  0:05 ` qwx
2021-02-17  5:31   ` Amavect
2021-02-17  9:10     ` hiro
2021-02-17  9:32       ` Steve Simon
2021-02-17  9:57         ` hiro
2021-02-17 10:24           ` qwx
2021-02-17 10:53     ` hiro
2021-02-21 11:06 ` [9front] " Michael Forney
2021-02-22  0:32   ` qwx
2021-02-22 15:15   ` qwx

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).