shithub: opl2

Download patch

ref: 3a694fb7f3cd90ad8d2569db0c32fc5b9e0b684f
parent: f370f7155890fc8f8c7158bd96f02176e13d7887
author: qwx <qwx@sciops.net>
date: Mon May 3 23:30:42 EDT 2021

use opl2's actual sampling rate and don't buffer output

- don't buffer output: rather than loop and use bio(2), just generate
all at once and write immediately; this fixes realtime applications
(from umbraticus' merged patch for games/opl3).
- sanity checks and exit status
- the opl2 chip has a weird sampling rate of 49.716kHz.  output rate
is still audio(3)'s default 44.1kHz, and input rate is arbitrary
(ultima6 m files: 60Hz, doom: same as opl chip).  instead of
implementing downsampling ourselves, we let pcmconv take care of it.

--- a/man/1/opl2
+++ b/man/1/opl2
@@ -4,8 +4,8 @@
 .SH SYNOPSIS
 .B opl2
 [
-.B -n
-.I rate
+.B -r
+.I input rate
 ] [
 .I file
 ]
@@ -17,10 +17,12 @@
 The emulated chip is programmed by a stream of commands either from
 .I file
 or from standard in.
-Guided by these commands, it then synthesizes a number of 16 bit little-endian samples for a sampling rate of 44.1 kHz.
+Guided by these commands, it then synthesizes a number of 16 bit little-endian samples for
+the chip's output sampling rate, 49.716 kHz.
 Each is duplicated for stereo sound and written to standard out.
 .PP
-Commands are 4 bytes formatted as follows, where the size of each field is given in bytes between brackets:
+Commands are 4 bytes formatted as follows,
+where the size of each field is given in bytes between brackets:
 .PP
 .RS
 .IR register [1]
@@ -43,37 +45,43 @@
 It is a multiple of a command period, during which the
 .SM OPL2
 chip may be sampled before processing the next command.
-The period itself is the inverse of the command rate, 700 Hz by default.
-This rate can be set using the
-.B -n
-parameter.
+The period itself is the inverse of the input stream's sampling rate,
+by default the same as the chip's output sampling rate.
+The
+.B -r
+parameter
+sets the input sampling rate.
 .SH EXAMPLES
 Use with Wolfenstein 3D IMF files (default rate):
 .IP
 .EX
-% opl2 -n 700 <imf >/dev/audio
+% opl2 -r 700 <imf >/dev/audio
 .EE
 .PP
 Use with Commander Keen IMF files:
 .IP
 .EX
-% opl2 -n 560 <imf >/dev/audio
+% opl2 -r 560 <imf >/dev/audio
 .EE
 .PP
 Use with Wolfenstein 3D adlib sound effects:
 .IP
 .EX
-% opl2 -n 140 <al >/dev/audio
+% opl2 -r 140 <al >/dev/audio
 .EE
 .PP
 Use with Ultima 6 M files:
 .IP
 .EX
-% opl2 -n 60 <mfile >/dev/audio
+% opl2 -r 60 <mfile >/dev/audio
 .EE
 .SH "SEE ALSO"
 .IR wl3d (1) ,
 .IR audio (3)
+.PP
+Yamaha
+``YM3812 Application Manual'',
+1994.
 .SH HISTORY
 .I Opl2
 first appeared in 9front (May, 2016), based on
--- a/opl2m.c
+++ b/opl2m.c
@@ -7,13 +7,13 @@
 void	opl2init(int);
 
 enum{
-	Rate = 44100,
+	OPLrate = 49716,	/* 3579545Hz master clock / 72 */
 };
 
 void
 usage(void)
 {
-	fprint(2, "usage: %s [-n nsamp] [file]\n", argv0);
+	fprint(2, "usage: %s [-r rate] [file]\n", argv0);
 	exits("usage");
 }
 
@@ -20,15 +20,18 @@
 void
 main(int argc, char **argv)
 {
-	int r, v, dt, nsamp, fd;
-	uchar *sb, u[4];
-	Biobuf *bi, *bo;
+	int rate, n, r, v, fd, pfd[2];
+	uchar sb[65536 * 4], u[4];
+	double f, dt;
+	Biobuf *bi;
 
 	fd = 0;
-	nsamp = Rate / 700;
+	rate = OPLrate;
 	ARGBEGIN{
-	case 'n':
-		nsamp = Rate / atoi(EARGF(usage()));
+	case 'r':
+		rate = atoi(EARGF(usage()));
+		if(rate <= 0 || rate > OPLrate)
+			usage();
 		break;
 	default:
 		usage();
@@ -37,21 +40,41 @@
 		if((fd = open(*argv, OREAD)) < 0)
 			sysfatal("open: %r");
 	bi = Bfdopen(fd, OREAD);
-	bo = Bfdopen(1, OWRITE);
-	if(bi == nil || bo == nil)
+	if(bi == nil)
 		sysfatal("Bfdopen: %r");
-	nsamp *= 4;
-	if((sb = malloc(nsamp)) == nil)
-		sysfatal("malloc: %r");
-	opl2init(Rate);
-	while(Bread(bi, u, sizeof u) > 0){
+	opl2init(OPLrate);
+	if(pipe(pfd) < 0)
+		sysfatal("pipe: %r");
+	switch(rfork(RFPROC|RFFDG)){
+	case -1:
+		sysfatal("rfork: %r");
+	case 0:
+		close(0);
+		close(pfd[1]);
+		dup(pfd[0], 0);
+		execl("/bin/audio/pcmconv", "pcmconv", "-i", "s16c2r49716", "-o", "s16c2r44100", nil);
+		sysfatal("execl: %r");
+	default:
+		close(1);
+		close(pfd[0]);
+	}
+	f = (double)OPLrate / rate;
+	dt = 0;
+	while((n = Bread(bi, u, sizeof u)) > 0){
 		r = u[0];
 		v = u[1];
-		dt = u[3]<<8 | u[2];
 		opl2wr(r, v);
-		while(dt-- > 0){
-			opl2out(sb, nsamp);
-			Bwrite(bo, sb, nsamp);
+		dt += (u[3] << 8 | u[2]) * f;
+		while((n = dt) > 0){
+			if(n > sizeof sb / 4)
+				n = sizeof sb / 4;
+			dt -= n;
+			n *= 4;
+			opl2out(sb, n);
+			write(pfd[1], sb, n);
 		}
 	}
+	if(n < 0)
+		sysfatal("read: %r");
+	exits(nil);
 }