Re: App database, libsynth

Ryan Underwood
2003-07-15T23:48:20Z

Hi Paul,

On Wed, Jul 16, 2003 at 12:26:48AM +1200, Paul Eggleton wrote:
> > Argh! I need to work on this...
>
> I feel bad because I promised Stas I would help out with the sound code a
> while back, but as yet I haven't been able to set aside any time to actually
> do any work on it :(
>
> If you can point out any specific areas you might need help with I'll see what
> I can do (simple stuff is fine, I can code C but I have done only a very
> little real DOS programming and no sound programming to speak of).

Well, this is probably as good a time and place as ever to discuss the
library design. Won't really go into excruciating detail here, but it
should give a good idea what i'm after.

int SynthOpen(synthlist_t model, preference_t preference, panning_t
panning) { }

This function is called by the programmer before he wants to use a
particular synth. The program needs to figure out what type of synth it
wants to use, based on the options the user picks probably, or default
behavior.

If a program wants a OPL2 synth:

if (!SynthOpen(SYNTH_OPL2, PREFER_REAL, PANNING_MONO)) {
printf("No OPL2 available, disabling sound\n");
...
}

SYNTH_OPL2 is an enumeration that specifies that we want a YM3812
device.

PREFER_REAL means that we want to use real hardware if available, else
fall back to an emulated device if it can be created. The PREFER_REAL
does hardware detection of the relevant device, and if the hardware
detection fails, it tries to create an emulated device instead.
Some synth chips cannot be hardware detected, so PREFER_REAL is
equivalent to DEMAND_REAL in those cases -- the real chip will be
attempted to used regardless of whether we know it exists or not.

PANNING_MONO is only relevant in the case that we create an emulated
device. It specifies the output channel(s) of the dsp that the emulated
output will be written to. In this case, the programmer probably wanted
the equivalent of an AdLib card or the 0x388 port on a soundblaster
card; any writes to it will produce sound that goes to both channels.

If you wanted to emulated a SB Pro with dual OPL2, you could create two
SYNTH_OPL2 PREFER_EMULATED devices one on PANNING_LEFT and one on
PANNING_RIGHT. Then program them just like 0x220-0x223 on a SB Pro.

If SynthOpen() succeeds, it returns a int which is the library slot
for that synth. That int can be used like a handle for that particular
synth. You can SynthWrite(synthno, addr, data) and if the real hardware
is being used, it will simply be passed through, and if an emulated
hardware is being used, it sends it to the emulator daemon. The
emulator daemon since it is scheduled independently of the parent
process, internally updates its state on a continuous basis and writes
the result to the dsp (or to a file).

The design is simple to me. The programmer of the application need do
nothing but SynthOpen the correct devices, and then SynthWrite the data
to them.

The emulator daemon, synthd, is spawned when the library first
encounters a need for an emulated device. IPC is done through a
unix socket with the library. synthd is multi-threaded and can handle
emulation of as many different synth devices concurrently as the library is
allowed to create. The output of all the synth devices is mixed and
written to the dsp. so you could have an emulated SID-chip as well as a
OPL3 or something all at the same time. Or perhaps a POKEY on the left
channel and a PC speaker on the right. The possibilities are very open.

Plan to support the following synths in the library, all can be
either hardware or emulated:

SYNTH_PCSP = 0, /* PC Speaker */
SYNTH_OPL, /* YM3526 */
SYNTH_OPL2, /* YM3812 */
SYNTH_OPL3, /* YMF262 */
SYNTH_PCJR, /* TI-SN76496 */
SYNTH_SID, /* MOS 6581 */
SYNTH_SID2, /* MOS 8580 */
SYNTH_NES, /* Ricoh 2A03 */
SYNTH_POKEY, /* Atari 400/800 POKEY */

Though right now I only implement OPL2 for the DOSEMU project to have
something to work with. Note that for a particular synth to fit the design
of this library, it needs to be able to be programmed through simply
reading and writing address and data pairs. So chips that need IRQ-lines
or DMA and such are not candidates. The OPL-chip has an internal IRQ but
it can be triggered or cleared by simply writing to the OPL's port.

synthd checks regularly for the existence of the parent process, and when the
parent process pid/argv[0] pair no longer exists, it self-terminates so a
bunch of synthd are not left hanging around. I don't think synthd is incredibly
useful without libsynth managing it, so this does not seem to be a problem.

Any comments, ideas?

--