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);
}