232 lines
11 KiB
C
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
|