-
-
Save vananasun/a0343b864b950e2e5f6a8f25a61ee363 to your computer and use it in GitHub Desktop.
E_AUDIO.C
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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