www.delorie.com/archives/browse.cgi   search  
Mail Archives: djgpp/1994/05/06/05:51:00

Date: Fri, 6 May 1994 10:18:55 --100
From: grzegorz AT kmm-lx DOT p DOT lod DOT edu DOT pl (Grzegorz Jablonski)
To: djgpp AT sun DOT soe DOT clarkson DOT edu, dmb AT ai DOT mit DOT edu
Subject: Sound Blaster program - working version

Dear Dave

I've modified your Sound Blaster program so it doesn't hang now.
But it is still not perfect - all the pages have to be in memory
during interrupt handling - paging causes exception. In this version
this is enforced by initializing blaster after loading the sound -
if the sample is too big, there's no place for callback. But if
we reverse the order - we are back in DOS after exception.
So happens even with 64k DMA buffer. The callback/wrapper
procedures are not reentrant and - I think - the delay before paging-in
is to big. Decreasing the sampling rate helps - I can see the disk LED
blinking and the hear the sound. Maybe some day go32 will have 32 bit 
disk access in protected mode sharing the permanent swap file with windows...
I was able to playback 6 MB sample in 2MB of memory at 7kHz after allocating
the rest of memory and disabling smartdrive (without smartdrive loading
time was about 5 minutes). Another problem is, that go32 incorrectly
frees base memory and after return to Dos system hangs.
Compile this code with djgpp111maint5. In previous version the routine
setting real mode interrupt doesn't know about interrupt controller
base relocation, while protected mode interrupt setting routine does,
so instead of fixed number 8 you have to use _go32_info_block.
master_interrupt_controller_base. In maint5 your code works incorrectly,
because after servicing interrupt in protected mode you service it again-
you chain interrupt instead of setting, so your handler passes control
to real mode.
  However, the real mode handler is never called - you can check it
if you #define indicator. So I don't know why it didn't work before and
works now.


/*
 * Play digitized sound sample on soundblaster DAC using DMA.
 * This source code is in the public domain.
 * 
 * Modification History
 *
 *  9-Nov-93    David Baggett           Wrote it based on Sound Blaster
 *              <dmb AT ai DOT mit DOT edu>        Freedom project and Linux code.
 *
 *  6-May-94    Grzegorz W. Jablonski        Fixed bugs: play buffer len;
 *              <grzegorz AT kmm-lx DOT p DOT lodz DOT pl>  Set interrupt instead of
 *                                           chain interrupt;
 *                                           modified reset code.
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <go32.h>
#include <dos.h>
#include <dpmi.h>
#include <string.h>
#include <pc.h>

#include "sb.h"
#define _PROTO_(x) x

#include "proto.h"


#ifdef indicator
char* adr=0xe00b8000;
#endif
/*
 * Define TEST to make an executable (i.e., compile main).
 */
#define TEST

/*
 * GO32 DPMI structs for accessing DOS memory.
 */
static _go32_dpmi_seginfo dosmem;       /* DOS (conventional) memory buffer */

static _go32_dpmi_seginfo oldirq_rm;    /* original real mode IRQ */
static _go32_dpmi_registers rm_regs;
static _go32_dpmi_seginfo rm_si;        /* real mode interrupt segment info */

static _go32_dpmi_seginfo oldirq_pm;    /* original prot-mode IRQ */
static _go32_dpmi_seginfo pm_si;        /* prot-mode interrupt segment info */

/*
 * Card parameters
 */
static unsigned int     sb_ioaddr;
static unsigned int     sb_irq;
static unsigned int     sb_dmachan;

/*
 * Is a sound currently playing?
 */
static volatile int     sb_sound_playing = 0;

/*
 * Conventional memory buffers for DMA.
 */
static volatile int     sb_bufnum = 0;
static char             *sb_buf[2];
static unsigned int     sb_buflen[2];

/*
 * Info about current sample
 */
static unsigned char    *sb_curdata;    /* pointer to next bit of data */
static unsigned long    sb_curlength;   /* total length length left to play */

/*
 * DMA chunk size, in bytes.
 *
 * This parameter determines how big our DMA buffers are.  We play
 * the sample by piecing together chunks that are this big.  This
 * means that we don't have to copy the entire sample down into
 * conventional memory before playing it.  (A nice side effect of
 * this is that we can play samples that are longer than 64K.)
 *
 * Setting this is tricky.  If it's too small, we'll get lots
 * of interrupts, and slower machines might not be able to keep
 * up.  Furthermore, the smaller this is, the more grainy the
 * sound will come out.
 *
 * On the other hand, if we make it too big there will be a noticeable
 * delay between a call to sb_play and when the sound actually starts
 * playing, which is unacceptable for things like games where sound
 * effects should be "instantaneous".
 *
 */
#define DMA_CHUNK (512)

/*
 * Define replacements for DOS enable and disable.
 * Be careful about inlining these -- GCC has a tendency to move
 * them around even if you declare them volatile.  (This is definitely
 * true before 2.5.2; may be fixed in 2.5.2.)
 */
void
disable()
{
	__asm__ __volatile__ ("cli");
}
void
enable()
{
	__asm__ __volatile__ ("sti");
}

/*
 * Interrupt handler
 *
 * This is called in both protected mode and in real mode -- this means
 * we don't have to switch modes when we service the interrupt.
 */
void
sb_intr(_go32_dpmi_registers *reg)
{

	register unsigned n = sb_bufnum;        /* buffer we just played */
	outportb(0x20,0x20);
	enable();

	/*
	 * Acknowledge soundblaster
	 */
	inportb(sb_ioaddr + SB_DSP_DATA_AVAIL);
#ifdef indicator
	*(adr++)='0'+n;
	*(adr++)=7;
#endif
	/*
	 * Start next buffer player
	 */
	sb_play_buffer(1 - n);
	
	/*
	 * Fill this buffer for next time around
	 */
	sb_fill_buffer(n);
	/*
	 * Acknowledge the interrupt
	 */
}
#ifdef indicator
void
sb_intr2(_go32_dpmi_registers *reg)
{
	register unsigned n = sb_bufnum;        /* buffer we just played */
	outportb(0x20,0x20);
	enable();
	/*
	 * Acknowledge soundblaster
	 */
	inportb(sb_ioaddr + SB_DSP_DATA_AVAIL);

	*(adr++)='2'+n;
	*(adr++)=7;

	/*
	 * Start next buffer player
	 */
	sb_play_buffer(1 - n);
	
	/*
	 * Fill this buffer for next time around
	 */
	sb_fill_buffer(n);
	/*
	 * Acknowledge the interrupt
	 */
}
#endif

/*
 * Fill buffer n with the next data.
 */
void
sb_fill_buffer(register unsigned n)
{
	if (sb_curlength > DMA_CHUNK) {
		sb_buflen[n] = DMA_CHUNK;
		dosmemput(sb_curdata, DMA_CHUNK, (unsigned long) sb_buf[n]);
		sb_curlength -= DMA_CHUNK;
		sb_curdata += DMA_CHUNK;
	}
	else if (sb_curlength == 0) {
		sb_buflen[n] = 0;
		sb_curlength = 0;
	}
	else {
		sb_buflen[n] = sb_curlength;
		dosmemput(sb_curdata, sb_curlength, (unsigned long) sb_buf[n]);
		sb_curdata += sb_curlength;
		sb_curlength = 0;
	}
}

void
sb_play_buffer(register unsigned n)
{
	int             t;
	unsigned char   im, tm;
	static int initted=0;
	/*
	 * See if we're already done
	 */
	if (sb_buflen[n] == 0) {
		sb_sound_playing = 0;
		return;
	}
	
	disable();
if(!initted)
{
initted=0;
	/*
	 * Enable interrupts on PIC
	 */
	im = inportb(0x21);
	tm = ~(1 << sb_irq);
	outportb(0x21,im & tm);

	/*
	 * Set DMA mode
	 */
	outportb(SB_DMA_MASK, 5);
	outportb(SB_DMA_FF, 0);
	outportb(SB_DMA_MODE, 0x49);
};
	/*
	 * Set transfer address
	 */
	sb_bufnum = n;
	t = (int) ((unsigned long) sb_buf[n] >> 16);
	outportb(SB_DMAPAGE + 3, t);
	t = (int) ((unsigned long) sb_buf[n] & 0xFFFF);
	outportb(SB_DMA + 2 * sb_dmachan, t & 0xFF);
	outportb(SB_DMA + 2 * sb_dmachan, t >> 8);

	/*
	 * Set transfer length byte count
	 */
	outportb(SB_DMA + 2 * sb_dmachan + 1, (sb_buflen[n]-1) & 0xFF);
	outportb(SB_DMA + 2 * sb_dmachan + 1, (sb_buflen[n]-1) >> 8);

	/*
	 * Unmask DMA channel
	 */
	outportb(SB_DMA_MASK, sb_dmachan);
	

	sb_writedac(SB_DMA_8_BIT_DAC); /* command byte for DMA DAC transfer */

	/* sb_write length */
	sb_writedac((sb_buflen[n]-1) & 0xFF);
	sb_writedac((sb_buflen[n]-1) >> 8);

	enable();
	/*
	 * A sound is playing now.
	 */
	sb_sound_playing = 1;
}

/*
 * Set sampling/playback rate.
 * Parameter is rate in Hz (samples per second).
 */
void
sb_set_sample_rate(unsigned int rate)
{
    unsigned char tc = (unsigned char) (256 - 1000000/rate);

    sb_writedac(SB_TIME_CONSTANT);      /* Command byte for sample rate */
    sb_writedac(tc);            /* Sample rate time constant */
}

void
sb_voice(int state)
{
    sb_writedac(state ? SB_SPEAKER_ON : SB_SPEAKER_OFF);
}

/*
 * Read soundblaster card parameters from BLASTER enivronment variable.
 */
void
sb_getparams()
{
	char *t, *blaster;

	/*
	 * Set arguments to Soundblaster defaults
	 */
	sb_ioaddr = 0x220;
	sb_irq = 7;
	sb_dmachan = 1;

	t = getenv("BLASTER");
	if (!t)
		return;

	/*
	 * Get a copy
	 */
	blaster = strdup(t);

	/*
	 * Parse the BLASTER variable
	 */
	t = strtok(blaster, " \t");
	while (t) {
		switch (t[0]) {
			case 'A':
			case 'a':
				/* I/O address */
				sscanf(t + 1, "%x", &sb_ioaddr);
				break;
			case 'I':
			case 'i':
				/* IRQ */
				sb_irq = atoi(t + 1);
				break;
			case 'D':
			case 'd':
				/* DMA channel */
				sb_dmachan = atoi(t + 1);
				break;
			case 'T':
			case 't':
				/* what is this? */
				break;
				
			default:
				printf("Unknown BLASTER option %c\n",t[0]);
				break;
		}
		t = strtok(NULL," \t");
	}

	free(blaster);  
	return;
}

/*
 * Init the soundblaster card.
 */
void
sb_initcard()
{
	outportb(sb_ioaddr + SB_DSP_RESET, 1);
	
	/*
	 * Kill some time
	 */
	inportb(sb_ioaddr + SB_DSP_RESET);
	inportb(sb_ioaddr + SB_DSP_RESET);
	inportb(sb_ioaddr + SB_DSP_RESET);
	inportb(sb_ioaddr + SB_DSP_RESET);
	
	outportb(sb_ioaddr + SB_DSP_RESET, 0);

	while(!(inportb(sb_ioaddr+SB_DSP_DATA_AVAIL)&0x80))
		;
	/*
	 * Need to add a timeout here!
	 */
	while (inportb(sb_ioaddr + SB_DSP_READ_DATA) != 0xAA)
		;       
}

/*
 * Install our interrupt as the real mode interrupt handler for 
 * the IRQ the soundblaster is on.
 *
 * We accomplish this by have GO32 allocate a real mode callback for us.
 * The callback packages our protected mode code up in a real mode wrapper.
 */
void
sb_install_rm_interrupt()
{
	int     ret;
#ifdef indicator
	rm_si.pm_offset = (int) sb_intr2;
#else
	rm_si.pm_offset = (int) sb_intr;
#endif
	ret = _go32_dpmi_allocate_real_mode_callback_iret(&rm_si, &rm_regs);
	if (ret != 0) {
		printf("cannot allocate real mode callback, error=%04x\n",ret);
		exit(1);
	}

#ifdef  TEST
	printf("real mode callback is at %04x:%04x\n",
	       rm_si.rm_segment, rm_si.rm_offset);
#endif

	/*
	 * Install our real mode interrupt handler
	 */
	disable();
	_go32_dpmi_get_real_mode_interrupt_vector(8 + sb_irq, &oldirq_rm);
	_go32_dpmi_set_real_mode_interrupt_vector(8 + sb_irq, &rm_si);
	enable();
}

/*
 * Remove our real mode interrupt handler.
 */
void
sb_cleanup_rm_interrupt()
{
	disable();
	_go32_dpmi_set_real_mode_interrupt_vector(8 + sb_irq, &oldirq_rm);
	_go32_dpmi_free_real_mode_callback(&rm_si);
	enable();
}

/*
 * Install our interrupt as the protected mode interrupt handler for 
 * the IRQ the soundblaster is on.
 */
void
sb_install_pm_interrupt()
{
	int ret;
	disable();

	pm_si.pm_offset = (int) sb_intr;
	ret = _go32_dpmi_allocate_iret_wrapper(&pm_si);
	if (ret != 0) {
		printf("cannot allocate protected mode wrapper, error=%04x\n",ret);
		exit(1);
	}
	pm_si.pm_selector=_go32_my_cs();
	_go32_dpmi_get_protected_mode_interrupt_vector(8 + sb_irq, &oldirq_pm);
	_go32_dpmi_set_protected_mode_interrupt_vector(8 + sb_irq, &pm_si);
	enable();
}

/*
 * Remove our protected mode interrupt handler.
 */
void
sb_cleanup_pm_interrupt()
{

	disable();
	_go32_dpmi_free_iret_wrapper(&pm_si);
	_go32_dpmi_set_protected_mode_interrupt_vector(8 + sb_irq, &oldirq_pm);
	enable();

}

/*
 * Allocate conventional memory for our DMA buffers.
 * Each DMA buffer must be aligned on a 64K boundary in physical memory.
 */
void
sb_init_buffers()
{
	dosmem.size = 65536*3/16;
	if (_go32_dpmi_allocate_dos_memory(&dosmem)) {
		printf("Unable to allocate dos memory - max size is %d\n", dosmem.size);
		exit(1);
	}

#ifdef  TEST
	printf("dos buffer at 0x%04x:0\n", dosmem.rm_segment);
#endif
	
	(unsigned long) sb_buf[0] = dosmem.rm_segment * 16;
	(unsigned long) sb_buf[0] += 0x0FFFFL;
	(unsigned long) sb_buf[0] &= 0xFFFF0000L;
	(unsigned long) sb_buf[1] = (unsigned long) sb_buf[0] + 0x10000;
	
#ifdef  TEST
	printf("DMA buffers at physical 0x%0x and 0x%0x\n",
	       (unsigned int) sb_buf[0], (unsigned int) sb_buf[1]);
#endif
}

/*
 * Initliaze our internal buffers and the card itself to prepare
 * for sample playing.
 *
 * Call this once per program, not once per sample.
 */
void 
sb_init()
{
	/*
	 * Card card params and initialize card.
	 */
	sb_getparams();
	sb_initcard();
	
	/*
	 * Install our interrupt handlers
	 */
	sb_install_rm_interrupt();
	sb_install_pm_interrupt();
	
	/*
	 * Allocate buffers in conventional memory for double-buffering
	 */
	sb_init_buffers();
}

/*
 * Restore card and system to sane state before exiting.
 */
void
sb_cleanup()
{
	/*
	 * Remove our interrupt handlers
	 */
	sb_cleanup_rm_interrupt();
	sb_cleanup_pm_interrupt();
}

/*
 * Play a sample through the DAC using DMA.
 */
void
sb_play(unsigned char *data, unsigned long length)
{
	/*
	 * Prime the buffers
	 */
	sb_curdata = data;
	sb_curlength = length;
	sb_fill_buffer(0);
	sb_fill_buffer(1);
	
	/*
	 * Start the first buffer playing.
	 */
	sb_play_buffer(0);
}

#ifdef  TEST
void
main(argc, argv)
	int     argc;
	char    **argv;
{
	unsigned long   length;
	unsigned char   *data;
	FILE            *fp;
	struct stat     statbuf;

	if (argc < 3) {
		printf("usage: sb sample.sam sample-rate\n");
		printf("sample-rate is in hertz (e.g., 11000)\n");
		exit(0);
	}

	if (stat(argv[1], &statbuf) < 0) {
		printf("%s: can't stat %s\n", argv[0], argv[1]);
		exit(1);
	}
	length = statbuf.st_size;
	
	data = calloc(length, 1);
	if (!data) {
		printf("%s: out of memory\n", argv[0]);
		exit(1);
	}
	
	fp = fopen(argv[1], "rb");
	if (!fp) {
		printf("%s: can't open %s\n", argv[0], argv[1]);
		exit(1);
	}
	
	if (fread(data, 1, length, fp) < length) {
		printf("%s: error reading %s\n", argv[0], argv[1]);
		exit(1);
	}
	
	sb_init();
		
	printf("I/O addr = %x, IRQ = %d, DMA channel = %d\n",
	       sb_ioaddr, sb_irq, sb_dmachan);
	
	sb_voice(1);
	sb_set_sample_rate(atoi(argv[2]));
	sb_play(data, length);
	while (sb_sound_playing && (!kbhit()))
		;
	if(kbhit()) getxkey();
	sb_cleanup();
	
	exit(0);
}
#endif




				Grzegorz W. Jablonski
				grzegorz AT kmm-lx DOT p DOT lodz DOT pl

- Raw text -


  webmaster     delorie software   privacy  
  Copyright © 2019   by DJ Delorie     Updated Jul 2019