diff --git a/src/soundblaster.c b/src/soundblaster.c new file mode 100644 index 0000000..285b1ff --- /dev/null +++ b/src/soundblaster.c @@ -0,0 +1,307 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "soundblaster.h" + +#define BYTE(val, byte) (((val) >> ((byte) * 8)) & 0xFF) + +static uint16_t *sampleBuffer; +static int sampleBufferSelector; +static uint16_t baseAddress; +static uint16_t irq; +static uint16_t dmaChannel; + +#define SAMPLE_BUFFER_SIZE (SOUNDBLASTER_SAMPLES_PER_BUFFER * sizeof(uint16_t) * 2) + +// SB16 port offsets +#define BLASTER_RESET_PORT 0x6 +#define BLASTER_READ_PORT 0xA +#define BLASTER_WRITE_PORT 0xC +#define BLASTER_READ_BUFFER_STATUS_PORT 0xE +#define BLASTER_INTERRUPT_ACKNOWLEDGE_8BIT 0xE +#define BLASTER_INTERRUPT_ACKNOWLEDGE_16BIT 0xF +#define BLASTER_MIXER_OUT_PORT 0x4 +#define BLASTER_MIXER_IN_PORT 0x5 +#define BLASTER_MIXER_INTERRUPT_STATUS 0x82 +#define BLASTER_16BIT_INTERRUPT 0x02 +#define BLASTER_READ_BUFFER_STATUS_AVAIL 0x80 +#define BLASTER_WRITE_BUFFER_STATUS_UNAVAIL 0x80 +#define BLASTER_READY_BYTE 0xAA + + +// SB16 command constants +#define BLASTER_SET_OUTPUT_SAMPLING_RATE 0x41 +#define BLASTER_PROGRAM_16BIT_IO_CMD 0xB0 +#define BLASTER_PROGRAM_16BIT_FLAG_FIFO 0x02 +#define BLASTER_PROGRAM_16BIT_FLAG_AUTO_INIT 0x04 +#define BLASTER_PROGRAM_16BIT_FLAG_INPUT 0x08 +#define BLASTER_PROGRAM_STEREO 0x20 +#define BLASTER_PROGRAM_SIGNED 0x10 +#define BLASTER_SPEAKER_ON_CMD 0xD1 + + +// PIC +#define PIC1_COMMAND 0x20 +#define PIC2_COMMAND 0xA0 +#define PIC1_DATA 0x21 +#define PIC2_DATA 0xA1 +#define PIC_EOI 0x20 +#define PIC_IRQ07_MAP 0x08 +#define PIC_IRQ8F_MAP 0x70 + + +// DMA Data +#define DMA_DIRECTION_READ_FROM_MEMORY 0x04 +#define DMA_TRANSFER_MODE_BLOCK 0x80 + + +static const struct { + uint8_t startAddressRegister; + uint8_t countRegister; + uint8_t singleChannelMaskRegister; + uint8_t modeRegister; + uint8_t flipFlopResetRegister; + uint8_t pageRegister; +} dmaRegisters[] = { + { 0x00, 0x01, 0x0A, 0x0B, 0x0C, 0x87 }, + { 0x02, 0x03, 0x0A, 0x0B, 0x0C, 0x83 }, + { 0x04, 0x05, 0x0A, 0x0B, 0x0C, 0x81 }, + { 0x06, 0x07, 0x0A, 0x0B, 0x0C, 0x82 }, + { 0xC0, 0xC2, 0xD4, 0xD6, 0xD8, 0x8F }, + { 0xC4, 0xC6, 0xD4, 0xD6, 0xD8, 0x8B }, + { 0xC8, 0xCA, 0xD4, 0xD6, 0xD8, 0x89 }, + { 0xCC, 0xCE, 0xD4, 0xD6, 0xD8, 0x8A } +}; + +// ISR data +static _go32_dpmi_seginfo oldBlasterHandler, newBlasterHandler; +int writePage = 0; + +static soundblaster_getSampleProc getSamples; + +static void writeDSP(uint8_t value) { + while((inportb(baseAddress + BLASTER_WRITE_PORT) & + BLASTER_WRITE_BUFFER_STATUS_UNAVAIL) != 0) + ; + + outportb(baseAddress + BLASTER_WRITE_PORT, value); +} + + +static int resetBlaster() { + for(int j = 0; j < 1000; ++j) { + outportb(baseAddress + BLASTER_RESET_PORT, 1); + delay(1); + outportb(baseAddress + BLASTER_RESET_PORT, 0); + uint8_t status; + + while(((status = inportb(baseAddress + BLASTER_READ_BUFFER_STATUS_PORT)) + & BLASTER_READ_BUFFER_STATUS_AVAIL) == 0) + ; + + if(inportb(baseAddress + BLASTER_READ_PORT) == BLASTER_READY_BYTE) + return 0; + } + return SOUNDBLASTER_RESET_ERROR; +} + + +static void soundblasterISR() { + outportb(baseAddress + BLASTER_MIXER_OUT_PORT, + BLASTER_MIXER_INTERRUPT_STATUS); + uint8_t status = inportb(baseAddress + BLASTER_MIXER_IN_PORT); + + if(status & BLASTER_16BIT_INTERRUPT) { + uint8_t* dst = (uint8_t*)(sampleBuffer) + + writePage * SAMPLE_BUFFER_SIZE / 2; + + memcpy(dst, getSamples(), SAMPLE_BUFFER_SIZE / 2); + + writePage = 1 - writePage; + inportb(baseAddress + BLASTER_INTERRUPT_ACKNOWLEDGE_16BIT); + } + + if(irq >= 8) { + outportb(PIC2_COMMAND, PIC_EOI); + } + outportb(PIC1_COMMAND, PIC_EOI); +} + + +static void setBlasterISR() { + // Map IRQ to interrupt number on the CPU + uint16_t interruptVector = irq + irq + (irq < 8) + ? PIC_IRQ07_MAP + : PIC_IRQ8F_MAP; + + // Wrap and install ISR + _go32_dpmi_get_protected_mode_interrupt_vector(interruptVector, + &oldBlasterHandler); + + newBlasterHandler.pm_offset = (int)soundblasterISR; + newBlasterHandler.pm_selector = _go32_my_cs(); + _go32_dpmi_chain_protected_mode_interrupt_vector(interruptVector, &newBlasterHandler); + + // PIC setup: unmask SB IRQ + if(irq < 8) { + uint8_t irqmask = inportb(PIC1_DATA); + outportb(PIC1_DATA, irqmask & ~(1<>4, &selectors[current]); + if(segment == -1) { + break; + } + + uint32_t bufferPhys = __djgpp_conventional_base + segment * 16; + + // The DMA buffer must not cross a 64k boundary + if(bufferPhys % 0x10000 < 0x10000 - SAMPLE_BUFFER_SIZE) { + sampleBuffer = (uint16_t*)bufferPhys; + memset(sampleBuffer, 0, SAMPLE_BUFFER_SIZE); + sampleBufferSelector = selectors[current]; + --current; + break; + } + } + + // Free misaligned buffers + for(; current > 0; --current) { + __dpmi_free_dos_memory(selectors[current]); + } + + if(sampleBuffer == NULL) { + return SOUNDBLASTER_ALLOC_ERROR; + } + + return 0; +} + + +static void turnSpeakerOn() { + writeDSP(BLASTER_SPEAKER_ON_CMD); +} + + +static void dmaSetupTransfer(int channel, + uint8_t direction, + bool autoReload, + bool down, + uint8_t mode, + uint32_t startAddress, + uint32_t count) { + + uint8_t modeByte = direction + | mode + | (uint8_t)autoReload << 4 + | (uint8_t)down << 5 + | (channel & 0x03); + + uint8_t maskEnable = (channel & 0x03) | 0x04; + + uint32_t offset = startAddress; + if(channel > 3) { + offset >>= 1; + count >>= 1; + } + + uint8_t page = BYTE(startAddress, 2); + + outportb(dmaRegisters[channel].singleChannelMaskRegister, maskEnable); + outportb(dmaRegisters[channel].flipFlopResetRegister, 0x00); + outportb(dmaRegisters[channel].modeRegister, modeByte); + outportb(dmaRegisters[channel].startAddressRegister, BYTE(offset, 0)); + outportb(dmaRegisters[channel].startAddressRegister, BYTE(offset, 1)); + outportb(dmaRegisters[channel].countRegister, BYTE(count-1, 0)); + outportb(dmaRegisters[channel].countRegister, BYTE(count-1, 1)); + outportb(dmaRegisters[channel].pageRegister, page); + outportb(dmaRegisters[channel].singleChannelMaskRegister, maskEnable & 0x03); +} + + +static void startDMAOutput() { + uint32_t offset = (uint32_t)sampleBuffer; + + uint32_t samples = SAMPLE_BUFFER_SIZE / sizeof(int16_t); + + dmaSetupTransfer(dmaChannel, DMA_DIRECTION_READ_FROM_MEMORY, true, false, + DMA_TRANSFER_MODE_BLOCK, offset, SAMPLE_BUFFER_SIZE); + + // SB16 setup + writeDSP(BLASTER_SET_OUTPUT_SAMPLING_RATE); + writeDSP(BYTE(22050, 1)); + writeDSP(BYTE(22050, 0)); + writeDSP(BLASTER_PROGRAM_16BIT_IO_CMD + | BLASTER_PROGRAM_16BIT_FLAG_AUTO_INIT + | BLASTER_PROGRAM_16BIT_FLAG_FIFO); + writeDSP(BLASTER_PROGRAM_SIGNED); + writeDSP(BYTE(samples/2-1, 0)); + writeDSP(BYTE(samples/2-1, 1)); +} + + +int soundblaster_init(soundblaster_getSampleProc getsamplesproc) { + if(!__djgpp_nearptr_enable()) { + return SOUNDBLASTER_DOS_ERROR; + } + + int err = parseBlasterSettings(); + if(err != 0) { + return err; + } + + err = resetBlaster(); + if(err != 0) { + return err; + } + + err = allocSampleBuffer(); + if(err != 0) { + return err; + } + + setBlasterISR(); + turnSpeakerOn(); + startDMAOutput(); + + getSamples = getsamplesproc; + return 0; +} + + +void soundblaster_deinit(void) { + +} diff --git a/src/soundblaster.h b/src/soundblaster.h new file mode 100644 index 0000000..e03fde1 --- /dev/null +++ b/src/soundblaster.h @@ -0,0 +1,20 @@ +#ifndef SOUNDBLASTER_H +#define SOUNDBLASTER_H + +#include + +// Error codes +#define SOUNDBLASTER_ENV_NOT_SET 1 +#define SOUNDBLASTER_ENV_INVALID 2 +#define SOUNDBLASTER_DOS_ERROR 3 +#define SOUNDBLASTER_RESET_ERROR 4 +#define SOUNDBLASTER_ALLOC_ERROR 5 + +#define SOUNDBLASTER_SAMPLES_PER_BUFFER 2048 + +typedef int16_t const* (*soundblaster_getSampleProc)(void); + +int soundblaster_init(soundblaster_getSampleProc sampleproc); +void soundblaster_deinit(void); + +#endif