DVX_GUI/src/libs/kpunch/audio/audioDrv.h
2026-09-29 17:03:51 -05:00

232 lines
11 KiB
C

// 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 <name>.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