Skip to content

Instantly share code, notes, and snippets.

@lynxluna
Created June 10, 2024 16:12
Show Gist options
  • Save lynxluna/1eb0cd4c50c6747606082a67ebcdb400 to your computer and use it in GitHub Desktop.
Save lynxluna/1eb0cd4c50c6747606082a67ebcdb400 to your computer and use it in GitHub Desktop.
Playing Sample Using Single Cycle Sound Blaster 16
#include <dos.h>
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <string.h>
#include <io.h>
#include <fcntl.h>
#define KB 1024
#define SZ_16K (16 * KB)
#define _LO(x) ((x) & 0xFF)
#define _HI(x) (((x) >> 8) & 0xFF)
#define _LOW(x) ((x) & 0xFFFF)
#define _HIW(x) (((x) >> 16) & 0xFFFF)
#define SZ_32K (32 * KB)
#define SZ_64K (64 * KB)
#define KHZ_8 8000
#define KHZ_11 11025
#define KHZ_22 22050
#define KHZ_44 44100
/* DMA OPERATIONS */
#define DMA_MASK 0x0A
#define DMA_MODE 0x0B
#define DMA_FLIPFLOP 0x0C
#define DMA_PAGE 0x87
#define DMA_8BIT_SINGLE_CYCLE 0x48
/* SOUND BLASTER OPERATIONS */
#define SB_BASE_DETECTION 0x200
#define SB_DSP_RESET 0x06
#define SB_DSP_READ_DATA 0x0A
#define SB_DSP_WRITE_DATA 0x0C
#define SB_DSP_READ_DATA_STATUS 0x0E
#define SB_DSP_CMD_SINGLE_CYCLE 0x14
#define SB_DSP_CMD_SET_FREQ 0x40
#define SB_DSP_CMD_SPEAKER_ON 0xD1
#define SB_DSP_CMD_SPEAKER_OFF 0xD3
#define SB_DSP_CMD_GET_VERSION 0xE1
#define SB_DSP_READY 0xAA
/* Global Types */
/* Sound Blaster version */
struct sb_version_t {
unsigned char major;
unsigned char minor;
};
struct sb_irq_param_t {
unsigned char vector;
unsigned char port;
unsigned char irq;
};
struct sb_dma_buffer_t {
unsigned char *buffer;
unsigned char *boundary;
unsigned short size;
unsigned short cap;
unsigned long linear_addr;
unsigned short page;
unsigned short offset;
};
/* Sound Blaster IRQ interrupt handler */
typedef void (__interrupt __far *interrupt_handler_t)(void);
interrupt_handler_t sb_old_irq_handler = NULL;
static void sb_irq_init();
static void sb_irq_shutdown();
/* Global sb routines */
static void sb_get_version(struct sb_version_t *version);
static int sb_detect(void);
/* Global sb variables */
short sb_base; /* base address of the Sound Blaster card, default is 0x220 */
short sb_irq; /* IRQ of the Sound Blaster card, default is 5 */
short sb_dma; /* DMA channel of the Sound Blaster card, default is 1 */
static int init_dma_buffer(struct sb_dma_buffer_t *dma_buf, unsigned short size) {
unsigned long linear_address;
unsigned int segment, offset;
unsigned long sz_total = size * 2;
if (dma_buf == NULL) {
return 0;
}
/* allocate twice as needed for the buffer */
memset(dma_buf, 0, sizeof(struct sb_dma_buffer_t));
dma_buf->buffer = (unsigned char *)malloc(sz_total);
dma_buf->size = 0;
dma_buf->cap = size * 2;
if (dma_buf->buffer == NULL) {
/* sometimes failure happen because of wrong memory model */
memset(dma_buf, 0, sizeof(struct sb_dma_buffer_t));
return 0;
}
segment = FP_SEG(dma_buf->buffer);
offset = FP_OFF(dma_buf->buffer);
// Calculate the linear address
linear_address = ((unsigned long)segment << 4) + (unsigned long)offset;
dma_buf->linear_addr = linear_address;
/* calculate the sb page boundary */
dma_buf->boundary = (unsigned char *)((linear_address + size - 1) & 0xFFFF0000);
dma_buf->page = _HIW(linear_address);
dma_buf->offset = _LOW(linear_address);
return 1;
}
static void free_dma_buffer(struct sb_dma_buffer_t *dma_buf) {
if (dma_buf == NULL) {
return;
}
if (dma_buf->buffer != NULL) {
free(dma_buf->buffer);
dma_buf->buffer = NULL;
}
dma_buf->size = 0;
dma_buf->cap = 0;
dma_buf->page = 0;
dma_buf->offset = 0;
}
void __interrupt __far sb_irq_handler(void) {
outp(0x20, 0x20); /* send EOI to the PIC */
outp(sb_base + SB_DSP_WRITE_DATA, 0x20); /* acknowledge the interrupt */
puts("> Buffer ENDS <");
}
static void _build_irq_param(struct sb_irq_param_t *param) {
unsigned char irq = sb_irq;
/* default to low IRQs */
unsigned char irq_port = 0x21;
unsigned char irq_base = 0x08;
if (irq == 2) {
irq = 9;
}
if (irq >= 9) {
irq_base = 0x71 - 9;
irq_port = 0xA1;
}
param->vector = irq_base + irq;
param->port = irq_port;
param->irq = irq;
}
static void sb_irq_shutdown() {
struct sb_irq_param_t param;
unsigned char interrupt_mask;
_build_irq_param(&param);
/* set the interrupt vector */
_dos_setvect(param.vector , sb_old_irq_handler);
/* disable the interrupt */
interrupt_mask = inp(param.port);
interrupt_mask |= (1 << (param.irq % 8));
outp(param.port, interrupt_mask);
}
/* IRQ interrupt handler */
static void sb_irq_init(struct sb_irq_param_t *out_param) {
struct sb_irq_param_t param;
unsigned char interrupt_mask;
_build_irq_param(&param);
/* set the interrupt vector */
sb_old_irq_handler = _dos_getvect(param.vector);
_dos_setvect(param.vector, sb_irq_handler);
/* enable the interrupt */
interrupt_mask = inp(param.port);
interrupt_mask &= ~(1 << (param.irq % 8));
outp(param.port, interrupt_mask);
if (out_param != NULL) {
*out_param = param;
}
}
/* I/O port functions */
__inline static void dsp_write_port(unsigned short port, unsigned char data) {
while ((inp(port + SB_DSP_WRITE_DATA) & 0x80) != 0); /* wait for the DSP to be ready, bit 7 must be clear */
outp(port + SB_DSP_WRITE_DATA, data);
}
__inline static unsigned char dsp_read_port(unsigned short port) {
while ((inp(port + SB_DSP_READ_DATA_STATUS) & 0x80) == 0); /* wait for the DSP to be ready, bit 7 must be set */
return inp(port + SB_DSP_READ_DATA);
}
static int dsp_reset_port(unsigned short port) {
outp(port + SB_DSP_RESET, 1);
delay(3);
outp(port + SB_DSP_RESET, 0);
delay(100);\
if (dsp_read_port(port) != SB_DSP_READY) {
return 0;
}
return 1;
}
__inline static void dsp_write(unsigned char data) {
dsp_write_port(sb_base, data);
}
__inline static unsigned char dsp_read(void) {
return dsp_read_port(sb_base);
}
__inline static void dsp_reset(void) {
dsp_reset_port(sb_base);
}
__inline static void sb_turn_on_speaker(void) {
dsp_write(SB_DSP_CMD_SPEAKER_ON);
}
__inline static void sb_turn_off_speaker(void) {
dsp_write(SB_DSP_CMD_SPEAKER_OFF);
}
static void sb_get_version(struct sb_version_t *version) {
if (version == NULL) {
return;
}
dsp_write(SB_DSP_CMD_GET_VERSION);
version->major = dsp_read();
version->minor = dsp_read();
}
static int sb_detect(void) {
unsigned short i, port;
char *blaster_env, c, c1, c2;
unsigned int blaster_env_len;
/* check for Sound Blaster at 210, 220, 230, 240, 250, 260, 280 */
for ( i = 1; i < 9; ++i) {
if (i == 7) {
continue; /* skip 270 */
}
port = SB_BASE_DETECTION + (i << 4);
if (dsp_reset_port(port)) {
sb_base = port;
break;
}
}
if ( i == 9 ) {
return 0;
}
/* Read IRQ and DMA settings from the Sound Blaster card */
blaster_env = getenv("BLASTER");
if (blaster_env == NULL) {
return 0;
}
blaster_env_len = strlen(blaster_env);
for ( i = 0; i < blaster_env_len; ++i) {
c = blaster_env[i];
if ( i + 1 >= blaster_env_len ) {
break;
}
c1 = blaster_env[i + 1];
if ( i + 2 < blaster_env_len ) {
c2 = blaster_env[i + 2];
} else {
c2 = 0;
}
if ( (c | 32) == 'i' ) {
sb_irq = c1 - '0';
if (c2 >= '0' && c2 <= '9') {
sb_irq = sb_irq * 10 + c2 - '0';
}
}
if ( (c | 32) == 'd' ) {
sb_dma = c1 - '0';
if (c2 >= '0' && c2 <= '9') {
sb_dma = sb_dma * 10 + c2 - '0';
}
}
}
return 1;
}
static int load_raw_to_dma(char const *file_name, struct sb_dma_buffer_t *dma_buf, unsigned long *out_size) {
FILE *file;
unsigned char *buffer;
unsigned long file_size;
unsigned long read_size;
if (file_name == NULL || dma_buf == NULL) {
return 0;
}
file = fopen(file_name, "rb");
fseek(file, 0, SEEK_END);
file_size = ftell(file);
fseek(file, 0, SEEK_SET);
if (file_size > dma_buf->cap) {
fclose(file);
return 0;
}
buffer = dma_buf->buffer;
read_size = fread(buffer, 1, file_size, file);
if (read_size != file_size) {
fclose(file);
return 0;
}
fclose(file);
dma_buf->size = file_size;
if (out_size != NULL) {
*out_size = file_size;
}
return 1;
}
static void usage(void) {
puts("Usage: boop [file]");
}
void print_dma_status(struct sb_dma_buffer_t *dma_buf) {
if (dma_buf == NULL) {
return;
}
printf("DMA buffer: %04X:%04X "
"size %u, cap %u\n"
"linear: 0x%lX boundary: 0x%X "
"page: 0x%X offset 0x%X\n",
FP_SEG(dma_buf->buffer), FP_OFF(dma_buf->buffer),
dma_buf->size, dma_buf->cap,
dma_buf->linear_addr, dma_buf->boundary,
dma_buf->page, dma_buf->offset);
}
#define DMA_DISABLE_CHANNEL 0x04
#define DMA_ENABLE_CHANNEL 0x00
/* setup DMA transfer */
void setup_dma_transfer(struct sb_dma_buffer_t *dma_buf) {
unsigned char dma_channel = sb_dma;
unsigned short dma_channel_address = dma_channel << 1;
unsigned short dma_channel_count = dma_channel_address + 1;
unsigned char dma_page = dma_buf->page;
unsigned short dma_size = dma_buf->size - 1;
unsigned short dma_offset = dma_buf->offset;
unsigned char dma_page_addr;
/* mask DMA channel (disable) */
outp(DMA_MASK, dma_channel | DMA_DISABLE_CHANNEL);
/* clear byte pointer flip-flop */
outp(DMA_FLIPFLOP, 0x00);
/* set DMA mode */
outp(DMA_MODE, DMA_8BIT_SINGLE_CYCLE);
/* set count */
outp(dma_channel_count, _LO(dma_size));
outp(dma_channel_count, _HI(dma_size));
/* set address */
outp(dma_channel_address, _LO(dma_offset));
outp(dma_channel_address, _HI(dma_offset));
/* select address for 8-bit DMA */
switch (dma_channel) {
case 0:
dma_page_addr = 0x87;
break;
case 1:
dma_page_addr = 0x83;
break;
case 3:
dma_page_addr = 0x82;
break;
default:
dma_page_addr = 0x83;
break;
}
/* set page */
outp(dma_page_addr, _LO(dma_page));
outp(dma_page_addr, _HI(dma_page));
/* unmask DMA channel */
outp(DMA_MASK, dma_channel | DMA_ENABLE_CHANNEL);
}
__inline unsigned short calculate_time_constant(unsigned short rate, unsigned char nchannel) {
return 0xFFFF - (1000000 / rate / nchannel);
}
static void setup_playback( unsigned short rate, unsigned short size ) {
unsigned short time_constant = 1000000 / rate;
unsigned short count = size - 1;
sb_turn_on_speaker();
/* set the playback frequency */
dsp_write(SB_DSP_CMD_SET_FREQ);
dsp_write(calculate_time_constant(rate, 1));
/* start the playback */
dsp_write(SB_DSP_CMD_SINGLE_CYCLE);
dsp_write(_LO(count));
dsp_write(_HI(count));
sb_turn_off_speaker();
}
int main(int argc, char **argv) {
struct sb_version_t version = {0, 0};
struct sb_irq_param_t param = {0, 0, 0};
struct sb_dma_buffer_t dma_buf = {0};
int dma_success;
unsigned long file_size;
/* parse if the first argument is a file */
if (argc < 2) {
usage();
return 1;
}
/* check if file exists and valid */
if (_access(argv[1], 0) != 0) {
puts("File not found\n");
return 1;
}
if (!sb_detect()) {
puts("Sound Blaster not found\n");
return 1;
}
sb_get_version(&version);
printf(
"Sound Blaster FOUND!\n"
"Port %x, IRQ %d, DMA %d\n"
"DSP Version: %d.%02d\n\n", sb_base, sb_irq, sb_dma, version.major, version.minor);
puts("Setting up IRQ handler");
sb_irq_init(&param);
printf("IRQ handler vector: %02XH, port %02XH, IRQ %d \n", param.vector, param.port, param.irq);
dma_success = init_dma_buffer(&dma_buf, SZ_16K);
if (!dma_success) {
puts("Failed to allocate DMA buffer. Maybe wrong memory model? We're only supporting small model for now.");
goto cleanup;
}
print_dma_status(&dma_buf);
if (!load_raw_to_dma(argv[1], &dma_buf, &file_size)) {
puts("Failed to load file to DMA buffer");
goto cleanup_dma;
}
printf("File %s loaded to DMA buffer: %lu bytes\n", argv[1], file_size);
print_dma_status(&dma_buf);
setup_dma_transfer(&dma_buf);
puts("Playing sound...");
setup_playback(KHZ_8, dma_buf.size);
puts("Please press any key to stop the sound");
getch();
cleanup_dma:
free_dma_buffer(&dma_buf);
cleanup:
puts("Shutting down IRQ handler");
sb_irq_shutdown();
return 0;
}
@lynxluna
Copy link
Author

lynxluna commented Jun 10, 2024

Use OpenWatcom to build.

This Code is Licensed as MIT

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment