// The MIT License (MIT) // // Copyright (C) 2026 Scott Duensing // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // audioDrv.h -- sound driver interface of audio.lib // // (Synthesizers - FM chips and MIDI ports - have a descriptor of their // own; see "Synthesizer drivers" below.) // // A sound driver is a driver module (dvxDrv.h): a file .drv in // AUDIO_DRV_DIR, exporting one AudioDriverT named audioDriver (write it // with AUDIO_DRIVER(...)). audio.lib finds the drivers when the first // program opens sound - nothing lists them - and tries them in this // order: the driver named by DVX.INI [audio] driver (unless "auto"), then // the others from the highest priority down. The first whose open and // play succeed is used; the others are unloaded. // // open detects the hardware and decides the output format; it fills in // AudioDrvInfoT (the achieved rate: the mixer runs at exactly that rate) // and must not make a sound yet. play starts output: from then on the // driver calls audioMixRender for every block of output, from its own // interrupt or from the timer service (audioClockStart does that for a // device that plays nothing it has to feed). close stops output and // releases everything; after it returns audioMixRender is not called // again. A driver registers a shutdown hook (platformShutdownHookAdd) if // its hardware would keep running when DVX ends without closing it. // // Interrupt-time rules: code and data an interrupt touches must be pinned // (audioLockSpan for a module's own functions and data; the mixer and the // sound data are pinned by audio.lib), must not call the C library, DVX // or DOS, and must not use floating point. #ifndef AUDIO_DRV_H #define AUDIO_DRV_H #include "audio.h" #include "dvxDrv.h" #include "dvxPlat.h" #include "dvxPrefs.h" // Where drivers are found (relative to the DVX directory) and the name // of the descriptor each exports. #define AUDIO_DRV_DIR "libs" DVX_PATH_SEP "kpunch" DVX_PATH_SEP "audio" DVX_PATH_SEP "drivers" #define AUDIO_DRV_SYMBOL "audioDriver" // DVX.INI section holding the sound settings (drivers read their own // keys there too) #define AUDIO_INI_SECTION "audio" // DVX.INI [audio] driver: the driver tried first (its short name), or // auto; volume: the master volume in percent #define AUDIO_INI_DRIVER "driver" #define AUDIO_INI_VOLUME "volume" #define AUDIO_DRIVER_AUTO "auto" // DVX.INI [audio] midi: where MIDI music plays. auto: the FM chip when // there is one, else the software synthesizer; mpu401: the MPU-401 MIDI // port (at mpuPort, hex, default AUDIO_MPU_PORT_DEFAULT; the port is only // looked for with this setting, since an unused port would play nothing); // fm, software: those. #define AUDIO_INI_MIDI "midi" #define AUDIO_INI_MPU_PORT "mpuPort" #define AUDIO_MIDI_AUTO "auto" #define AUDIO_MIDI_MPU401 "mpu401" #define AUDIO_MIDI_FM "fm" #define AUDIO_MIDI_SOFTWARE "software" #define AUDIO_MPU_PORT_DEFAULT "330" // Driver priorities (tried from the highest down) #define AUDIO_PRIORITY_CARD 100 // sound cards #define AUDIO_PRIORITY_SPEAKER 20 // the PC speaker #define AUDIO_PRIORITY_NULL 0 // plays nothing // Rate of the timer that drives devices without an interrupt of their own // (audioClockStart); each call mixes rate / AUDIO_CLOCK_HZ frames. #define AUDIO_CLOCK_HZ 250 // What audio.lib asks of a driver's open. bits / channels 0 mean the // best the device can do. typedef struct { PrefsHandleT *prefs; // DVX.INI (read-only), section AUDIO_INI_SECTION uint32_t rate; // wanted mixing rate (Hz) uint32_t bits; // wanted output bits: 0, 8 or 16 uint32_t channels; // wanted output channels: 0, 1 or 2 } AudioDrvConfigT; // Hardware tone generator: sound hz (0: silence). Called at interrupt // time. typedef void (*AudioToneFnT)(uint32_t hz); // What a driver's open found. typedef struct { char description[AUDIO_DESC_MAX]; // for people (audioGetInfo) uint32_t rate; // achieved output rate (Hz) uint32_t bits; // 8 (unsigned) or 16 (signed) uint32_t channels; // 1 or 2 AudioToneFnT tone; // hardware tones, or NULL bool hasDac; // plays sampled sound bool hasSynth; // has a hardware synthesizer } AudioDrvInfoT; // The descriptor every driver exports as AUDIO_DRV_SYMBOL. // hdr.name is the short name (DVX.INI driver =, below AUDIO_NAME_MAX), // hdr.priority one of AUDIO_PRIORITY_*. typedef struct { DvxDriverHeaderT hdr; bool (*open)(const AudioDrvConfigT *cfg, AudioDrvInfoT *info); bool (*play)(void); void (*close)(void); } AudioDriverT; // Write a driver's descriptor: AUDIO_DRIVER("x", AUDIO_PRIORITY_CARD, // .open = ..., .play = ..., .close = ...); #define AUDIO_DRIVER(drvName, drvPriority, ...) extern const AudioDriverT audioDriver; const AudioDriverT audioDriver = { .hdr = DVX_DRIVER_HDR(AudioDriverT, drvName, drvPriority), __VA_ARGS__ } // One object for audioLockSpan: data, or a function (size 0), which // counts as its first AUDIO_LOCK_CODE_BYTES bytes. #define AUDIO_LOCK_CODE_BYTES 4096 typedef struct { const void *addr; uint32_t size; } AudioLockItemT; // ============================================================ // Synthesizer drivers // ============================================================ // // A module in AUDIO_DRV_DIR may export an AudioSynthDriverT named // AUDIO_SYNTH_SYMBOL instead (write it with AUDIO_SYNTH_DRIVER(...)): a // music synthesizer beside the sound device - an FM chip (AUDIO_SYNTH_FM, // programmed by register writes) or a MIDI port (AUDIO_SYNTH_MIDI, sent // bytes). When the sound device starts, the synth drivers of each kind // are opened from the highest priority down and the first that finds its // hardware is kept (it is closed with the device). open detects and must // leave the hardware silent; close silences it. write and put are called // at interrupt time as well (music playback runs from the timer service), // so they follow the interrupt-time rules above, and write must make its // index/data pair atomic (interrupts off around it). #define AUDIO_SYNTH_SYMBOL "audioSynth" // Synthesizer kinds #define AUDIO_SYNTH_FM 0 // OPL2 / OPL3 FM chip #define AUDIO_SYNTH_MIDI 1 // MIDI port (MPU-401) #define AUDIO_SYNTH_KINDS 2 // FM register numbers passed to write: bank (0, or 1 on an OPL3) times // AUDIO_FM_BANK_REGS plus the register index. #define AUDIO_FM_BANK_REGS 0x100 // What a synth driver's open found. typedef struct { char description[AUDIO_DESC_MAX]; // for people, e.g. "OPL3 FM synthesizer at 388h" uint32_t voices; // FM: two-operator voices (9 on an OPL2, 18 on an OPL3) bool stereo; // FM: each voice can play left, right or both (OPL3) } AudioSynthDrvInfoT; // The descriptor every synth driver exports as AUDIO_SYNTH_SYMBOL. // hdr.name is a short name below AUDIO_NAME_MAX; a higher hdr.priority is // tried first. typedef struct { DvxDriverHeaderT hdr; int32_t kind; // AUDIO_SYNTH_* bool (*open)(const AudioDrvConfigT *cfg, AudioSynthDrvInfoT *info); void (*close)(void); void (*write)(uint32_t reg, uint8_t value); // FM: set a register bool (*put)(uint8_t byte); // MIDI: send a byte; false when the port is not ready yet } AudioSynthDriverT; // Write a synth driver's descriptor: AUDIO_SYNTH_DRIVER("x", priority, // .kind = ..., .open = ..., ...); #define AUDIO_SYNTH_DRIVER(drvName, drvPriority, ...) extern const AudioSynthDriverT audioSynth; const AudioSynthDriverT audioSynth = { .hdr = DVX_DRIVER_HDR(AudioSynthDriverT, drvName, drvPriority), __VA_ARGS__ } // Mix the next frames frames of output into dst in the device format // (AudioDrvInfoT bits and channels: 16-bit signed or 8-bit unsigned, // stereo interleaved). Interrupt-safe; never blocks. void audioMixRender(void *dst, uint32_t frames); // Drive the mixer from the timer service (AUDIO_CLOCK_HZ) for a device // that has no interrupt of its own to do it; the output is thrown away. // Tones still reach a hardware tone generator (AudioDrvInfoT tone). bool audioClockStart(void); void audioClockStop(void); // Pin (lock) or release the part of the calling module holding the // listed functions and data: from the lowest address to the end of the // highest object (a module's code comes before its data, so one span // covers every function listed). Returns false when pinning failed. bool audioLockSpan(const AudioLockItemT *items, int32_t count, bool lock); // Interrupts off / back as they were, around the few instructions that // must not be split by an interrupt (an FM chip's index/data pair, a // table an interrupt also changes). Inline, so code that runs at // interrupt time calls nothing outside its pinned memory. Nothing to do // on the host, which has no interrupts. static inline uint32_t audioIrqOff(void) { uint32_t flags = 0; #ifdef __DJGPP__ __asm__ __volatile__ ("pushfl\n\tpopl %0\n\tcli" : "=r"(flags) : : "memory"); #endif return flags; } static inline void audioIrqRestore(uint32_t flags) { #ifdef __DJGPP__ __asm__ __volatile__ ("pushl %0\n\tpopfl" : : "r"(flags) : "memory", "cc"); #else (void)flags; #endif } #endif // AUDIO_DRV_H