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:
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
typedef struct Chan Chan;
typedef struct Trk Trk;
enum{
- Rate = 44100,
+ Rate = 49716, /* opl3 sampling rate */
Ninst = 128 + 81-35+1,
Rwse = 0x01,
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;
void opl3wr(int, int);
void opl3init(int);
+enum{
+ OPLrate = 49716, /* 14318180Hz master clock / 288 */
+};
+
void
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();
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);
}