Skip to content

Instantly share code, notes, and snippets.

@vananasun
Created August 17, 2022 17:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vananasun/a0343b864b950e2e5f6a8f25a61ee363 to your computer and use it in GitHub Desktop.
Save vananasun/a0343b864b950e2e5f6a8f25a61ee363 to your computer and use it in GitHub Desktop.
E_AUDIO.C
#include "E.H"
#include "AUDIO/E_AUDIO.H"
#include "AUDIO/SB16.H"
#include "E_FILE.H"
#include "E_MATH.H"
#include "E_PLAYER.H"
#include <stdlib.h>
#define SAMPLE_RATE 44100
#define MAX_PRELOAD_SIZE (6LU * 1024LU*1024LU) //(64*1024LU)
#define MAX_SOUNDS 16
#define MAX_VOICES 8
struct Voice {
int16* readPtr;
uint32 samplesRemaining;
uint8 active;
uint8 soundId;
uint8 loop;
int16 volL;
int16 volR;
};
static struct Voice s_Voices[MAX_VOICES];
static uint8 s_SoundCount = 0;
static Sound s_Sounds[MAX_SOUNDS];
/*******************************************************************************
*
* Positional audio math
*
******************************************************************************/
#define MIN_PAN_VOL 0.2f // between 0.0 and 1.0
void computePositionality(float objX, float objY, int16* L, int16* R) {
// Distance computation
Player* p = &g_Player;
float relX = objX - p->x;
float relY = objY - p->y;
float dist = Math_AbsFloat(sqrt_fast((relX*relX) + (relY*relY)));
float volDistMult = maxf(0.0f, 1.0f - (dist / MAX_HEAR_DIST));
// Panning computation
float pan;
pan = dot(relX, relY, p->planeX, p->planeY);
pan = Math_ClampF(pan, MIN_PAN_VOL, 1.0f - MIN_PAN_VOL);
// Calculate volumes
*L = MAX_VOLUME * (1.0f - pan) * volDistMult;
*R = MAX_VOLUME * pan * volDistMult;
}
/*******************************************************************************
*
* Mixer loop; This is where we mush all the sounds together.
*
******************************************************************************/
/** Abuses signed integer overflow flag to clamp sample volume */
static void mixAndClamp(int16* bufferSample, int16 voiceSample);
#pragma aux mixAndClamp = \
"mov ax, [edi]" \
"add cx, ax" \
"jno end" \
"js signed_overflow" \
"mov cx, -32768" \
"jmp end" \
"signed_overflow:" \
"mov cx, 32767" \
"end:" \
"mov [edi], cx" \
parm[edi] [cx] modify[ax];
/**
* NEVER use I/O in this function because it is called from an interrupt!
*/
static void mixVoice(int16* buffer, int numSamples, struct Voice* voice) {
uint32 samplesToRead = MIN(voice->samplesRemaining, numSamples);
for (int i = 0; i < samplesToRead; i += 2) {
int32 voiceSampleL = (int32)(*voice->readPtr++) * voice->volL / MAX_VOLUME;
int32 voiceSampleR = (int32)(*voice->readPtr++) * voice->volR / MAX_VOLUME;
mixAndClamp(&buffer[i ], voiceSampleL);
mixAndClamp(&buffer[i+1], voiceSampleR);
}
voice->samplesRemaining -= samplesToRead;
// Are we done playing the sound?
if (0 == voice->samplesRemaining) {
if (voice->loop) {
// Restart playback from the beginning of the sound
voice->readPtr = s_Sounds[voice->soundId].samples;
voice->samplesRemaining = s_Sounds[voice->soundId].length;
// Mix remaining samples
samplesToRead = numSamples - samplesToRead;
for (int i = 0; i < samplesToRead; i += 2) {
int32 voiceSampleL = (int32)(*voice->readPtr++) * voice->volL / MAX_VOLUME;
int32 voiceSampleR = (int32)(*voice->readPtr++) * voice->volR / MAX_VOLUME;
mixAndClamp(&buffer[i ], voiceSampleL);
mixAndClamp(&buffer[i+1], voiceSampleR);
}
voice->samplesRemaining -= samplesToRead;
} else {
voice->active = FALSE;
}
}
}
/**
* NEVER use I/O in this function because it is called from an interrupt!
*/
static void mix(int16* buffer, int numSamples) {
// Write silence
for (int s = 0; s < numSamples; s++) {
buffer[s] = 0;
}
for (int v = 0; v < MAX_VOICES; v++) {
struct Voice* voice = &s_Voices[v];
if (FALSE == voice->active)
continue;
mixVoice(buffer, numSamples, voice);
}
}
/*******************************************************************************
*
* Global Audio class functions
*
******************************************************************************/
void Audio_Init() {
SB16_Init(&mix, 44100);
}
void Audio_Cleanup() {
for (int i = 0; i < s_SoundCount; i++) {
Audio_ReleaseSound(i);
}
SB16_Cleanup();
}
void Audio_Play(uint8 id, uint8 loop) {
// Check whether a loaded sound is associated with the given ID
if (id >= s_SoundCount || !s_Sounds[id].samples) {
Log("Trying to play invalid sound of ID %i", id);
return;
}
// Look for a free voice to play the sound on
for (uint8 voice = 0; voice < MAX_VOICES; voice++) {
if (FALSE == s_Voices[voice].active) {
s_Voices[voice].active = TRUE;
s_Voices[voice].readPtr = s_Sounds[id].samples;
s_Voices[voice].samplesRemaining = s_Sounds[id].length;
s_Voices[voice].soundId = id;
s_Voices[voice].loop = loop;
s_Voices[voice].volL = MAX_VOLUME;
s_Voices[voice].volR = MAX_VOLUME;
return;
}
}
}
void Audio_Play3D(uint8 id, uint8 loop, float x, float y) {
// Check whether a loaded sound is associated with the given ID
if (id >= s_SoundCount || !s_Sounds[id].samples) {
Log("Trying to play invalid sound of ID %i", id);
return;
}
// Look for a free voice to play the sound on
for (uint8 voice = 0; voice < MAX_VOICES; voice++) {
if (FALSE == s_Voices[voice].active) {
int16 volL, volR;
computePositionality(x,y, &volL, &volR);
s_Voices[voice].active = TRUE;
s_Voices[voice].readPtr = s_Sounds[id].samples;
s_Voices[voice].samplesRemaining = s_Sounds[id].length;
s_Voices[voice].soundId = id;
s_Voices[voice].loop = loop;
s_Voices[voice].volL = volL;
s_Voices[voice].volR = volR;
return;
}
}
}
uint8 Audio_IsSoundPlaying(uint8 id) {
for (int v = 0; v < MAX_VOICES; v++) {
if (s_Voices[v].active && s_Voices[v].soundId == id)
return TRUE;
}
return FALSE;
}
#define WAV_ID_RIFF 0x46464952
#define WAV_ID_WAVE 0x45564157
#define WAV_ID_FMT 0x20746d66
#define WAV_ID_DATA 0x61746164
typedef struct {
uint32 riffID;
uint32 riffFileSize;
uint32 waveID;
uint32 fmtID;
uint32 fmtLength;
uint16 fmtType; // 1 = PCM
uint16 fmtChannels;
uint32 fmtSampleRate;
uint32 fmtByteRate;
uint16 fmtBlockAlign;
uint16 fmtBitsPerSample;
uint32 dataID;
uint32 dataSize;
} WAVHeader;
uint8 Audio_PreloadWAV(const char* filename) {
if (s_SoundCount >= MAX_SOUNDS)
Error("Maximum number of sounds loaded.");
FILE* f = fopen(filename, "rb");
if (!f) Error("Failed opening sound file \"%s\".", filename);
WAVHeader header = { 0 };
fread(&header, sizeof(WAVHeader), 1, f);
if (header.riffID != WAV_ID_RIFF
|| header.waveID != WAV_ID_WAVE
|| header.fmtID != WAV_ID_FMT
|| header.dataID != WAV_ID_DATA) {
Error("Invalid WAV header \"%s\".", filename);
}
// Check size
uint32 length = header.dataSize;
if (length > MAX_PRELOAD_SIZE) {
uint32 maxLengthMB = MAX_PRELOAD_SIZE / 1024 / 1024;
uint32 lengthMB = length / 1024 / 1024;
Error("WAV file \"%s\" is too large, maximum length is %luMB, but the sound is %luMB.", filename, maxLengthMB, lengthMB);
}
// Additional format checks
if (header.fmtBitsPerSample != 16 || header.fmtChannels != 2) {
Error("WAV file \"%s\" must be in 16-bit stereo format.", filename);
}
s_Sounds[s_SoundCount].samples = malloc(length);
fseek(f, sizeof(WAVHeader), SEEK_SET);
fread(s_Sounds[s_SoundCount].samples, length, 1, f);
fclose(f);
s_Sounds[s_SoundCount].length = length / 2; // sample count
return s_SoundCount++;
}
void Audio_ReleaseSound(uint8 id) {
SAFE_DELETE_FAR_PTR(s_Sounds[id].samples);
for (int v = 0; v < MAX_VOICES; v++) {
s_Voices[v].active = FALSE;
}
}
void Audio_Stop(uint8 id, uint8 atNextLoop) {
for (int v = 0; v < MAX_VOICES; v++) {
if (s_Voices[v].active && s_Voices[v].soundId == id) {
if (atNextLoop) {
s_Voices[v].loop = NOLOOP;
} else {
s_Voices[v].active = FALSE;
}
}
}
}
#include "E.H"
#include "E_DOS.H"
#include "AUDIO/SB16.H"
#include "AUDIO/INTR.H"
#include <dos.h>
#include <conio.h>
#include <string.h>
#include <stdlib.h>
// Crap needed for SoundBlaster 16 setup
#define LOBYTE(l)((uint8)(l))
#define HIBYTE(l)((uint8)((l) >> 8))
#define IODELAY() outp(0x80, 0) // delay for about 1us
#define REG_MIXPORT (s_SBBase + 0x4)
#define REG_MIXDATA (s_SBBase + 0x5)
#define REG_RESET (s_SBBase + 0x6)
#define REG_RDATA (s_SBBase + 0xA)
#define REG_WDATA (s_SBBase + 0xC)
#define REG_WSTAT (s_SBBase + 0xC)
#define REG_RSTAT (s_SBBase + 0xE)
#define REG_INTACK (s_SBBase + 0xE)
#define REG_INT16ACK (s_SBBase + 0xF)
#define INTSTAT_DMA16 0x02
#define WSTAT_BUSY 0x80
#define RSTAT_RDY 0x80
static const uint8 PORT_ADDR[] = { 0x00, 0x02, 0x04, 0x06, 0xc0, 0xc4, 0xc8, 0xcc };
static const uint8 PORT_COUNT[] = { 0x01, 0x03, 0x05, 0x07, 0xc2, 0xc6, 0xca, 0xce };
static const uint8 PORT_PAGE[] = { 0x87, 0x83, 0x81, 0x82, 0x8f, 0x8b, 0x89, 0x8a };
static const uint8 PORT_MASK[] = { 0x0a, 0x0a, 0x0a, 0x0a, 0xd4, 0xd4, 0xd4, 0xd4 };
static const uint8 PORT_MODE[] = { 0x0b, 0x0b, 0x0b, 0x0b, 0xd6, 0xd6, 0xd6, 0xd6 };
static const uint8 PORT_CLRFF[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0xd8, 0xd8, 0xd8, 0xd8 };
// SoundBlaster configuration
static int16 s_SBBase; // default 220h
static int8 s_SBIRQ = 7; // default 7
static int8 s_SBDMA16 = 5; // HDMA default 5
// DMA (Direct Memory Access) buffer
#define DMA_BUFFER_SIZE (16384) // Maximum actually
#define SB_BLOCK_LENGTH ((DMA_BUFFER_SIZE/2/2)-1)
#define NUM_SAMPLES (DMA_BUFFER_SIZE / 2)
static int16* s_DMABuffer = 0;
static uint32 s_MixPtr; // pointer to one half of s_DMABuffer
static int s_SampleRate;
SB16_MixCallback s_Callback;
/*******************************************************************************
*
* DSP functions
*
******************************************************************************/
static uint8 resetDSP(int16 basePort) {
s_SBBase = basePort;
outp(REG_RESET, 1);
for (int i = 0; i < 3; i++) IODELAY();
outp(REG_RESET, 0);
for(int i = 0; i < 128; i++) {
if (inp(REG_RSTAT) & RSTAT_RDY)
if (inp(REG_RDATA) == 0xAA)
return TRUE;
}
return FALSE;
}
static uint8 readDSP() {
while (inp(REG_RSTAT) & RSTAT_RDY);
return inp(REG_RDATA);
}
static void writeDSP(uint8 command) {
while (inp(REG_WSTAT) & WSTAT_BUSY);
outp(REG_WDATA, command);
}
static uint8 readMix(int reg) {
outp(REG_MIXPORT, reg);
return inp(REG_MIXDATA);
}
static void writeMix(int reg, uint8 val) {
outp(REG_MIXPORT, reg);
outp(REG_MIXDATA, val);
}
/*******************************************************************************
*
* IRQ functions
*
******************************************************************************/
static void interrupt (*s_OldIRQ)();
static void interrupt sbIRQHandler() {
// Mix and swap buffer
s_Callback((int16*)s_MixPtr, NUM_SAMPLES / 2);
s_MixPtr ^= (DMA_BUFFER_SIZE / 2);
outp(s_SBBase + 0x4, 0x82);
uint8 istat = inp(s_SBBase + 0x5);
if (istat & INTSTAT_DMA16)
inp(REG_INT16ACK);
if (s_SBIRQ > 7)
outp(PIC2_CMD, OCW2_EOI);
outp(PIC1_CMD, OCW2_EOI);
}
static void initIRQ() {
_disable();
if (!s_OldIRQ)
s_OldIRQ = _dos_getvect(IRQ_TO_INTR(s_SBIRQ));
_dos_setvect(IRQ_TO_INTR(s_SBIRQ), sbIRQHandler);
_enable();
unmaskIRQ(s_SBIRQ);
}
static void releaseIRQ() {
writeDSP(0xD3); // DSP DISABLE OUTPUT
maskIRQ(s_SBIRQ);
_disable();
_dos_setvect(IRQ_TO_INTR(s_SBIRQ), s_OldIRQ);
_enable();
writeDSP(0xD9); // DSP END DMA 16
}
/*******************************************************************************
*
* Initialization functions
*
******************************************************************************/
static uint8 detectBlasterSettings() {
uint8 i, len;
// Possible values: 210, 220, 230, 240, 260, 260, 280
for (i = 1; i < 9; i++) {
if ( (i != 7) && (resetDSP(0x200 + (i << 4))) ) {
break;
}
}
if (9 == i) return FALSE;
// When there's no BLASTER environment variable, we will have to guess the
// IRQ and DMA values.
char* BLASTER = getenv("BLASTER");
if (0 == BLASTER) {
Log("BLASTER unset! Defaulting to H5 I7.");
return TRUE;
}
// Look for 16-bit DMA
len = strlen(BLASTER);
for (i = 0; i < len; i++) {
if ((BLASTER[i] | 32) == 'h') {
s_SBDMA16 = BLASTER[i + 1] - '0';
break;
}
}
// Look for IRQ
for (i = 0; i < len; i++) {
if ((BLASTER[i] | 32) == 'i') {
s_SBIRQ = BLASTER[i + 1] - '0';
if (BLASTER[i + 2] >= '0' && BLASTER[i + 2] <= '9')
s_SBIRQ = s_SBIRQ * 10 + BLASTER[i + 2] - '0';
break;
}
}
return TRUE;
}
// Allocate a buffer in low memory that doesn't cross 64k boundaries
static void allocDMABuffer() {
if (s_DMABuffer) return;
uint16 pmsel;
uint16 seg = DPMI_Alloc(DMA_BUFFER_SIZE * 2 / 16, &pmsel);
if (!seg) {
Error("Failed to allocate DMA buffer");
}
uint32 addr = (uint32)seg << 4;
uint32 next64k = (addr + 0x10000) & 0xFFFF0000;
if (next64k - addr < DMA_BUFFER_SIZE) {
addr = next64k;
}
s_DMABuffer = (int16*)addr;
s_MixPtr = (uint32)s_DMABuffer;
}
static void initDMAOutput() {
outp(PORT_MASK[s_SBDMA16], (s_SBDMA16 & 3) | 0x04); // mask
outp(PORT_CLRFF[s_SBDMA16], 0);
outp(PORT_MODE[s_SBDMA16], 0x78 | (s_SBDMA16 & 3));
uint8 page = (((uint32)s_DMABuffer) >> 16) & 0xFF;
uint32 realAddr = ((uint32)s_DMABuffer) >> 1;
uint32 size = DMA_BUFFER_SIZE >> 1;
outp(PORT_ADDR[s_SBDMA16], LOBYTE(realAddr));
outp(PORT_ADDR[s_SBDMA16], HIBYTE(realAddr));
outp(PORT_PAGE[s_SBDMA16], page);
size--;
outp(PORT_COUNT[s_SBDMA16], LOBYTE(size));
outp(PORT_COUNT[s_SBDMA16], HIBYTE(size));
outp(PORT_MASK[s_SBDMA16], (s_SBDMA16 & 3)); // unmask
}
static void enableAutoInit() {
for (int s = 0; s < NUM_SAMPLES; s++)
s_DMABuffer[s] = 0; // mix silence
writeDSP(0x41); // set playback frequency
writeDSP(HIBYTE(s_SampleRate));
writeDSP(LOBYTE(s_SampleRate));
writeDSP(0xB6); // 16-bit, Autoinit, FIFO
writeDSP(0x30); // 0x10 if mono, 0x30 if stereo
writeDSP(LOBYTE(SB_BLOCK_LENGTH));
writeDSP(HIBYTE(SB_BLOCK_LENGTH));
writeDSP(0xD1); // DSP_ENABLE_OUTPUT
}
/*******************************************************************************
*
* Public methods
*
******************************************************************************/
void SB16_Init(SB16_MixCallback callback, int sampleRate) {
if (!detectBlasterSettings())
Error("SoundBlaster failed to initialize.");
#ifdef DEBUG
Log("SoundBlaster found at A%x I%u H%i", s_SBBase, s_SBIRQ, s_SBDMA16);
#endif
s_Callback = callback;
s_SampleRate = sampleRate;
allocDMABuffer();
initIRQ();
initDMAOutput();
enableAutoInit();
SB16_SetVolume(255);
}
void SB16_Cleanup() {
// if (s_DMABuffer) DPMI_Free(s_DMA)
releaseIRQ();
}
void SB16_SetVolume(uint8 volume) {
uint8 val = volume & 0xF8;
writeMix(0x30, val); // master L
writeMix(0x31, val); // master R
// writeMix(0x32, val); // voice L
// writeMix(0x33, val); // voice R
}
void SB16_Pause() {
writeDSP(0xD5);
}
void SB16_Resume() {
writeDSP(0xD6);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment