]> git.lizzy.rs Git - plan9front.git/commitdiff
games/opl3: use correct sampling rate
authorqwx <devnull@localhost>
Wed, 5 May 2021 14:57:19 +0000 (16:57 +0200)
committerqwx <devnull@localhost>
Wed, 5 May 2021 14:57:19 +0000 (16:57 +0200)
games/dmid uses the same sample rate as the chip for music, but other
applications do not.  opl3 and its older version opl2 (not in 9front)
read an input stream of commands in basically IMF format, something
used in other id Software games and some others, which assumes a
given input sampling rate:  700 Hz for Wolfenstein 3D music, 560 Hz
for Commander Keen, 60 Hz for Ultima 6, etc.

The opl3 emulation on the other hand is not really intended to run at
a sampling rate different that the chip's 49.716 kHz sampling rate.
Previously, we assumed it runs at 44.1 kHz and just used the input
rate as a divisor to get the number of samples per delay tic.

From what I understand, the correct way to use it for accurate
emulation is to run the opl chip emulator at its intended sampling
frequency, then downsample to 44.1 kHz.  This means better output
but more code.  The alternative is to basically do the same as
before rev 8433, except with no buffering, but at accuracy/quality
loss.  This change implements the former and just forks pcmconv to
deal with resampling.

sys/man/1/opl3
sys/src/games/dmid.c
sys/src/games/opl3/opl3m.c

index 7419ff134da7d21dd21e340f06647326db10733d..280a3196f36b2a7d13e5f7cad38842711d4372a0 100644 (file)
@@ -17,7 +17,11 @@ is an emulator of a single Yamaha 262 chip, also known as
 The emulated chip is programmed by a stream of commands either from
 .I file
 or from standard in.
-It then synthesizes a number of stereo 16 bit little-endian samples for a sampling rate of 44.1 kHz,
+It then synthesizes stereo 16-bit little-endian PCM samples
+at the chip's sampling rate, 49.716 kHz,
+resamples them to
+.IR audio (3)'s
+default 44.1 kHz rate,
 and writes them to standard out.
 .PP
 Commands are 5 bytes wide, in little-endian byte order:
@@ -42,14 +46,24 @@ field provides timing.
 It is a multiple of a command period, during which the
 .SM OPL3
 chip may be sampled before processing the next command.
-The period itself is the inverse of the sampling rate, 44100 Hz by default.
-This rate can be set using the
+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.
+parameter
+sets the input sampling rate.
 .SH SOURCE
 .B /sys/src/games/opl3
 .SH "SEE ALSO"
 .IR audio (3)
+.PP
+Yamaha
+``YMF262 Manual'',
+1994.
+.PP
+V. Arnost
+``Programmer's Guide to Yamaha YMF 262/OPL3 FM Music Synthesizer'',
+version 1.12 dated Nov. 23rd 2000.
 .SH HISTORY
 .I Opl3
 first appeared in 9front (July, 2018), based on
index 254d42df9f688f4047c08f5535951541f23f6125..9361605a4adaad55e72ac7d64003cb374694e870 100644 (file)
@@ -8,7 +8,7 @@ typedef struct Opl Opl;
 typedef struct Chan Chan;
 typedef struct Trk Trk;
 enum{
-       Rate = 44100,
+       Rate = 49716,           /* opl3 sampling rate */
        Ninst = 128 + 81-35+1,
 
        Rwse = 0x01,
@@ -236,7 +236,7 @@ setoct(Opl *o)
        e = freq[n] + (d % 0x1000) * (freq[n+1] - freq[n]) / 0x1000;
        if(o->c->i->fixed)
                e = (double)(int)e;
-       f = (e * (1 << 20)) / 49716;
+       f = (e * (1 << 20)) / Rate;
        for(b=1; b<8; b++, f>>=1)
                if(f < 1024)
                        break;
index 3d74e9e656f02d644d41e965b6ae89824548ce0e..17dbae1e53fa021f7c3c068f710299c71aeaee9e 100644 (file)
@@ -6,6 +6,10 @@ void   opl3out(uchar *, int);
 void   opl3wr(int, int);
 void   opl3init(int);
 
+enum{
+       OPLrate = 49716,        /* 14318180Hz master clock / 288 */
+};
+
 void
 usage(void)
 {
@@ -16,15 +20,18 @@ usage(void)
 void
 main(int argc, char **argv)
 {
-       int rate, r, v, dt, fd;
+       int rate, n, r, v, fd, pfd[2];
        uchar sb[65536 * 4], u[5];
+       double f, dt;
        Biobuf *bi;
 
        fd = 0;
-       rate = 44100;
+       rate = OPLrate;
        ARGBEGIN{
        case 'r':
                rate = atoi(EARGF(usage()));
+               if(rate <= 0 || rate > OPLrate)
+                       usage();
                break;
        default:
                usage();
@@ -35,14 +42,41 @@ main(int argc, char **argv)
        bi = Bfdopen(fd, OREAD);
        if(bi == nil)
                sysfatal("Bfdopen: %r");
-       opl3init(rate);
-       while(Bread(bi, u, sizeof u) > 0){
+       opl3init(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[1] << 8 | u[0];
                v = u[2];
                opl3wr(r, v);
-               if(dt = (u[4] << 8 | u[3]) * 4){        /* 16-bit stereo */
-                       opl3out(sb, dt);
-                       write(1, sb, dt);
+               dt += (u[4] << 8 | u[3]) * f;
+               while((n = dt) > 0){
+                       if(n > sizeof sb / 4)
+                               n = sizeof sb / 4;
+                       dt -= n;
+                       n *= 4;
+                       opl3out(sb, n);
+                       write(pfd[1], sb, n);
                }
        }
+       if(n < 0)
+               sysfatal("read: %r");
+       close(pfd[1]);
+       waitpid();
+       exits(nil);
 }