Updated the DOS port to use the new sound routines in the core. Also

switched to a new set of low-level Sound Blaster routines which appear
to provide better Sound Blaster support and the ability to auto-detect
sound card settings.


git-svn-id: svn://svn.code.sf.net/p/stella/code/trunk@142 8b62c5a3-ac7e-4cc8-8f21-d9a121418aba
This commit is contained in:
bwmott 2002-11-13 03:47:55 +00:00
parent b93d8d186d
commit f9c3e778c8
9 changed files with 1532 additions and 1403 deletions

View File

@ -13,7 +13,7 @@
## See the file "license" for information on usage and redistribution of
## this file, and for a DISCLAIMER OF ALL WARRANTIES.
##
## $Id: makefile,v 1.26 2002-11-11 22:01:28 stephena Exp $
## $Id: makefile,v 1.27 2002-11-13 03:47:55 bwmott Exp $
##============================================================================
##============================================================================
@ -185,7 +185,7 @@ dos:
OPTIONS+="$(OPTS.DOS)" \
LDFLAGS="" \
LDLIBS="" \
OBJS="mainDOS.o PCJoys.o SndDOS.o sbdrv.o"
OBJS="mainDOS.o PCJoys.o SndDOS.o dos_sb.o"
unix-x:
make stella.x11 \
@ -428,8 +428,8 @@ PCJoys.o: $(UI)/dos/PCJoys.cxx
SndDOS.o: $(UI)/dos/SndDOS.cxx
$(CXX) -c $(CXXFLAGS) $(OPTIONS) $(UI)/dos/SndDOS.cxx
sbdrv.o: $(UI)/dos/sbdrv.c
$(CXX) -c $(CXXFLAGS) $(OPTIONS) $(UI)/dos/sbdrv.c
dos_sb.o: $(UI)/dos/dos_sb.c
$(CXX) -c $(CXXFLAGS) $(OPTIONS) $(UI)/dos/dos_sb.c
TermX11.o: $(UI)/x11/TermX11.cxx
$(CXX) -c $(CXXFLAGS) $(OPTIONS) $(UI)/x11/TermX11.cxx

View File

@ -8,122 +8,257 @@
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-1998 by Bradford W. Mott
// Copyright (c) 1995-2002 by Bradford W. Mott
//
// See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: SndDOS.cxx,v 1.1.1.1 2001-12-27 19:54:32 bwmott Exp $
// $Id: SndDOS.cxx,v 1.2 2002-11-13 03:47:55 bwmott Exp $
//============================================================================
#include <unistd.h>
#include "SndDOS.hxx"
#include "TIASound.h"
#include "sbdrv.h"
/**
Compute the buffer size to use based on the given sample rate
@param The sample rate to compute the buffer size for
*/
static unsigned long computeBufferSize(int sampleRate)
{
int t;
for(t = 7; t <= 12; ++t)
{
if((1 << t) > (sampleRate / 60))
{
return (1 << (t - 1));
}
}
return 256;
}
#include "dos.h"
#include "dos_sb.h"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SoundDOS::SoundDOS()
SoundDOS::SoundDOS(bool activate)
: myCurrentVolume(100),
myFragmentSize(1024),
myIsInitializedFlag(false),
myUpdateLock(false),
myIsMuted(false),
mySampleRate(31400),
mySampleQueue(mySampleRate)
{
int sampleRate = 15700;
int DMABufferSize = computeBufferSize(sampleRate);
if(OpenSB(sampleRate, DMABufferSize))
if(activate)
{
myEnabled = true;
int bps = 8;
int stereo = 0;
int buffersize = myFragmentSize;
int playback_freq = mySampleRate;
// Initialize TIA Sound Library
Tia_sound_init(31400, sampleRate);
if(sb_init(&playback_freq, &bps, &buffersize, &stereo) < 0)
{
cerr << "WARNING: Couldn't initialize audio system! " << endl;
myIsInitializedFlag = false;
mySampleRate = 0;
return;
}
mySampleRate = playback_freq;
myFragmentSize = buffersize;
myIsInitializedFlag = true;
myIsMuted = false;
// Start playing audio
Start_audio_output(AUTO_DMA,
(void (*)(unsigned char*, short unsigned int))Tia_process);
sb_startoutput((sbmix_t)callback, (void*)this);
}
else
{
// Oops, couldn't open SB so we're not enabled :-(
myEnabled = false;
return;
myIsInitializedFlag = false;
myIsMuted = true;
mySampleRate = 0;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SoundDOS::~SoundDOS()
{
if(myEnabled)
{
CloseSB();
}
close();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundDOS::set(Sound::Register reg, uInt8 value)
uInt32 SoundDOS::getSampleRate() const
{
if(!myEnabled)
return;
return myIsInitializedFlag ? mySampleRate : 0;
}
switch(reg)
{
case AUDC0:
Update_tia_sound(0x15, value);
break;
case AUDC1:
Update_tia_sound(0x16, value);
break;
case AUDF0:
Update_tia_sound(0x17, value);
break;
case AUDF1:
Update_tia_sound(0x18, value);
break;
case AUDV0:
Update_tia_sound(0x19, value);
break;
case AUDV1:
Update_tia_sound(0x1A, value);
break;
default:
break;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool SoundDOS::isSuccessfullyInitialized() const
{
return myIsInitializedFlag;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundDOS::mute(bool state)
{
if(state)
if(!myIsInitializedFlag)
{
Stop_audio_output();
return;
}
// Ignore multiple calls to do the same thing
if(myIsMuted == state)
{
return;
}
myIsMuted = state;
if(myIsMuted)
{
sb_stopoutput();
}
else
{
Start_audio_output(AUTO_DMA,
(void (*)(unsigned char*, short unsigned int))Tia_process);
sb_startoutput((sbmix_t)callback, (void*)this);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundDOS::close()
{
if(myIsInitializedFlag)
{
sb_shutdown();
}
myIsInitializedFlag = false;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundDOS::setSoundVolume(uInt32 percent)
{
if(myIsInitializedFlag)
{
if((percent >= 0) && (percent <= 100))
{
// TODO: At some point we should support setting the volume...
myCurrentVolume = percent;
}
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundDOS::updateSound(MediaSource& mediaSource)
{
if(myIsInitializedFlag)
{
// Move all of the generated samples into our private sample queue
uInt8 buffer[4096];
while(mediaSource.numberOfAudioSamples() > 0)
{
uInt32 size = mediaSource.dequeueAudioSamples(buffer, 4096);
mySampleQueue.enqueue(buffer, size);
}
// Block until the sound interrupt has consumed all but 125 milliseconds
// of the available audio samples
uInt32 leave = mySampleRate / 8;
while(mySampleQueue.size() > leave)
{
}
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundDOS::callback(void* udata, void* stream, int len)
{
SoundDOS* sound = (SoundDOS*)udata;
if(!sound->myIsInitializedFlag)
{
return;
}
// Don't use samples unless there's at least 100 milliseconds worth of data
if(sound->mySampleQueue.size() < (sound->mySampleRate / 10))
{
return;
}
sound->mySampleQueue.dequeue((uInt8*)stream, len);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SoundDOS::SampleQueue::SampleQueue(uInt32 capacity)
: myCapacity(capacity),
myBuffer(0),
mySize(0),
myHead(0),
myTail(0)
{
myBuffer = new uInt8[myCapacity];
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SoundDOS::SampleQueue::~SampleQueue()
{
delete[] myBuffer;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundDOS::SampleQueue::clear()
{
myHead = myTail = mySize = 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 SoundDOS::SampleQueue::dequeue(uInt8* buffer, uInt32 size)
{
// We can only dequeue up to the number of items in the queue
if(size > mySize)
{
size = mySize;
}
if((myHead + size) < myCapacity)
{
memcpy((void*)buffer, (const void*)(myBuffer + myHead), size);
myHead += size;
}
else
{
uInt32 s1 = myCapacity - myHead;
uInt32 s2 = size - s1;
memcpy((void*)buffer, (const void*)(myBuffer + myHead), s1);
memcpy((void*)(buffer + s1), (const void*)myBuffer, s2);
myHead = (myHead + size) % myCapacity;
}
mySize -= size;
return size;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundDOS::SampleQueue::enqueue(uInt8* buffer, uInt32 size)
{
disable();
if((mySize + size) > myCapacity)
{
size = myCapacity - mySize;
}
enable();
if(size == 0)
{
return;
}
if((myTail + size) < myCapacity)
{
memcpy((void*)(myBuffer + myTail), (const void*)buffer, size);
myTail += size;
}
else
{
uInt32 s1 = myCapacity - myTail;
uInt32 s2 = size - s1;
memcpy((void*)(myBuffer + myTail), (const void*)buffer, s1);
memcpy((void*)myBuffer, (const void*)(buffer + s1), s2);
myTail = (myTail + size) % myCapacity;
}
disable();
mySize += size;
enable();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 SoundDOS::SampleQueue::size() const
{
return mySize;
}

View File

@ -8,34 +8,34 @@
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-1998 by Bradford W. Mott
// Copyright (c) 1995-2002 by Bradford W. Mott
//
// See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: SndDOS.hxx,v 1.1.1.1 2001-12-27 19:54:32 bwmott Exp $
// $Id: SndDOS.hxx,v 1.2 2002-11-13 03:47:55 bwmott Exp $
//============================================================================
#ifndef SOUNDDOS_HXX
#define SOUNDDOS_HXX
#include "bspf.hxx"
#include "Sound.hxx"
#include "MediaSrc.hxx"
/**
This class implements the sound API for the DOS operating system
using a sound-blaster card.
This class implements aa sound class for the DOS front-end. It supports
SoundBlaster compatible sound cards.
@author Bradford W. Mott
@version $Id: SndDOS.hxx,v 1.1.1.1 2001-12-27 19:54:32 bwmott Exp $
@author Bradford W. Mott
@version $Id: SndDOS.hxx,v 1.2 2002-11-13 03:47:55 bwmott Exp $
*/
class SoundDOS : public Sound
class SoundDOS
{
public:
/**
Create a new sound object
*/
SoundDOS();
SoundDOS(bool activate = true);
/**
Destructor
@ -44,23 +44,128 @@ class SoundDOS : public Sound
public:
/**
Set the value of the specified sound register
@param reg The sound register to set
@param val The new value for the sound registers
Closes the sound device
*/
virtual void set(Sound::Register reg, uInt8 val);
void close();
/**
Set the mute state of the sound object
@param state Mutes sound iff true
Return the playback sample rate for the sound device.
@return The playback sample rate
*/
virtual void mute(bool state);
uInt32 getSampleRate() const;
/**
Return true iff the sound device was successfully initialized.
@return true iff the sound device was successfully initialized
*/
bool isSuccessfullyInitialized() const;
/**
Set the mute state of the sound object.
@param state Mutes sound if true, unmute if false
*/
void mute(bool state);
/**
Sets the volume of the sound device to the specified level. The
volume is given as a precentage from 0 to 100.
@param volume The new volume for the sound device
*/
void setSoundVolume(uInt32 volume);
/**
Update the sound device using the audio sample from the specified
media source.
@param mediaSource The media source to get audio samples from.
*/
void updateSound(MediaSource& mediaSource);
private:
// Indicates if the sound system was initialized
bool myEnabled;
/**
A bounded queue class used to hold audio samples after they are
produced by the MediaSource.
*/
class SampleQueue
{
public:
/**
Create a new SampleQueue instance which can hold the specified
number of samples. If the queue ever reaches its capacity then
older samples are discarded.
*/
SampleQueue(uInt32 capacity);
/**
Destroy this SampleQueue instance.
*/
virtual ~SampleQueue();
public:
/**
Clear any samples stored in the queue.
*/
void clear();
/**
Dequeue the upto the specified number of samples and store them
in the buffer. Returns the actual number of samples removed from
the queue.
@return the actual number of samples removed from the queue.
*/
uInt32 dequeue(uInt8* buffer, uInt32 size);
/**
Enqueue the specified number of samples from the buffer.
*/
void enqueue(uInt8* buffer, uInt32 size);
/**
Answers the number of samples currently in the queue.
@return The number of samples in the queue.
*/
uInt32 size() const;
private:
const uInt32 myCapacity;
uInt8* myBuffer;
volatile uInt32 mySize;
volatile uInt32 myHead;
volatile uInt32 myTail;
};
private:
// Current volume
uInt32 myCurrentVolume;
// SDL fragment size
uInt32 myFragmentSize;
// Indicates if the sound device was successfully initialized
bool myIsInitializedFlag;
// Mutex
bool myUpdateLock;
bool myCallbackLock;
// Indicates if the sound is currently muted
bool myIsMuted;
// DSP sample rate
uInt32 mySampleRate;
// Queue which holds samples from the media source before they are played
SampleQueue mySampleQueue;
private:
// Callback function invoked by the sound library when it needs data
static void callback(void* udata, void* stream, int len);
};
#endif

1126
stella/src/ui/dos/dos_sb.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,54 @@
/*
** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com)
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of version 2 of the GNU Library General
** Public License as published by the Free Software Foundation.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** Library General Public License for more details. To obtain a
** copy of the GNU Library General Public License, write to the Free
** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**
** Any permitted reproduction of these routines, in whole or in part,
** must bear this legend.
**
** dos_sb.h
**
** DOS Sound Blaster header file
** $Id: dos_sb.h,v 1.1 2002-11-13 03:47:55 bwmott Exp $
*/
#ifndef DOS_SB_H
#define DOS_SB_H
/* Thanks, Allegro! */
#define BPS_TO_TIMER(x) (1193182L / (long)(x))
#define END_OF_FUNCTION(x) void x##_end(void) {}
#define END_OF_STATIC_FUNCTION(x) static void x##_end(void) {}
#define LOCK_VARIABLE(x) _go32_dpmi_lock_data((void*)&x, sizeof(x))
#define LOCK_FUNCTION(x) _go32_dpmi_lock_code(x, (long)x##_end - (long)x)
#define DISABLE_INTS() __asm__ __volatile__ ("cli")
#define ENABLE_INTS() __asm__ __volatile__ ("sti")
typedef void (*sbmix_t)(void *userdata, void *buffer, int size);
#ifdef __cplusplus
extern "C" {
#endif
extern int sb_init(int *sample_rate, int *bps, int *buf_size, int *stereo);
extern void sb_shutdown(void);
extern int sb_startoutput(sbmix_t fillbuf, void *userdata);
extern void sb_stopoutput(void);
extern void sb_setrate(int rate);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* DOS_SB_H */

View File

@ -13,7 +13,7 @@
// See the file "license" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//
// $Id: mainDOS.cxx,v 1.7 2002-04-28 18:06:56 bwmott Exp $
// $Id: mainDOS.cxx,v 1.8 2002-11-13 03:47:55 bwmott Exp $
//============================================================================
#include <go32.h>
@ -26,7 +26,7 @@
#include <fstream>
#include <iostream>
#include <strstream>
#include <sstream>
#include <assert.h>
#include <string.h>
#include <stdio.h>
@ -848,6 +848,7 @@ int main(int argc, char* argv[])
// Create a sound object for use with the console
SoundDOS sound;
// sound.setSoundVolume(settings->theDesiredVolume);
// Get just the filename of the file containing the ROM image
const char* filename = (!strrchr(file, '\\')) ?
@ -855,7 +856,7 @@ int main(int argc, char* argv[])
// Create the 2600 game console
theConsole = new Console(image, size, filename,
theEvent, propertiesSet, sound);
theEvent, propertiesSet, sound.getSampleRate());
// Free the image since we don't need it any longer
delete[] image;
@ -875,6 +876,7 @@ int main(int argc, char* argv[])
if(!thePauseIndicator)
{
theConsole->mediaSource().update();
sound.updateSound(theConsole->mediaSource());
}
/*
@ -912,6 +914,9 @@ int main(int argc, char* argv[])
// Get the ending time in case we need to print statistics
uclock_t endingTime = uclock();
// Close the sound device
sound.close();
uInt32 scanlines = theConsole->mediaSource().scanlines();
string cartName = theConsole->properties().get("Cartridge.Name");
string cartMD5 = theConsole->properties().get("Cartridge.MD5");

View File

@ -1,988 +0,0 @@
/*****************************************************************************/
/* */
/* Module: SBDRV */
/* Purpose: Sound Blaster DAC DMA Driver V1.3 */
/* Author(s): Ron Fries, Neil Bradley and Bradford Mott */
/* */
/* 02/20/97 - Initial Release */
/* */
/* 08/19/97 - V1.1 - Corrected problem with the auto-detect of older SB */
/* cards and problem with DSP shutdown which left the auto-init */
/* mode active. Required creating a function to reset the DSP. */
/* Also, added checks on the BLASTER settings to verify they */
/* are possible values for either SB or SB compatibles. */
/* Added several helpful information/error messages. These can */
/* be disabled by removing the SBDRV_SHOW_ERR definition. */
/* */
/* 12/24/97 - V1.2 - Added support for DJGPP (by Bradford Mott). */
/* */
/* 02/04/99 - V1.3 - Cleaned up DJGPP support, locking code and data. */
/* Fixed a bug with the reading of the BLASTER= environment */
/* variable, which caused a segfault if one was not set. Alst */
/* added a timeout to dsp_out() and some other minor */
/* modifications (Matthew Conte) */
/* */
/*****************************************************************************/
/* */
/* License Information and Copyright Notice */
/* ======================================== */
/* */
/* SBDrv is Copyright(c) 1997-1999 by Ron Fries, Neil Bradley and */
/* Bradford Mott */
/* */
/* This library is free software; you can redistribute it and/or modify it */
/* under the terms of version 2 of the GNU Library General Public License */
/* as published by the Free Software Foundation. */
/* */
/* This library is distributed in the hope that it will be useful, but */
/* WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library */
/* General Public License for more details. */
/* To obtain a copy of the GNU Library General Public License, write to the */
/* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
/* */
/* Any permitted reproduction of these routines, in whole or in part, must */
/* bear this legend. */
/* */
/*****************************************************************************/
#ifdef DJGPP
#include <go32.h>
#include <dpmi.h>
#include <sys/movedata.h>
/* Handy macros (care of Allegro) to lock code / data */
#define END_OF_FUNCTION(x) static void x##_end(void) {}
#define LOCK_VARIABLE(x) _go32_dpmi_lock_data((void*)&x,sizeof(x))
#define LOCK_FUNCTION(x) _go32_dpmi_lock_code(x,(long)x##_end-(long)x)
#endif
#include <dos.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <conio.h>
#include <time.h>
#include <stdio.h>
#include "sbdrv.h"
#define DSP_RESET 0x06
#define DSP_READ 0x0a
#define DSP_WRITE 0x0c
#define DSP_ACK 0x0e
#define DMA_BASE 0x00
#define DMA_COUNT 0x01
#define DMA_MASK 0x0a
#define DMA_MODE 0x0b
#define DMA_FF 0x0c
#define MASTER_VOLUME 0x22
#define LINE_VOLUME 0x2e
#define FM_VOLUME 0x26
/* declare local global variables */
#ifdef DJGPP
static int theDOSBufferSegment;
static int theDOSBufferSelector;
#endif
static uint8 *Sb_buffer;
static uint16 Sb_buf_size = 200;
static uint16 Sb_offset;
static uint16 Playback_freq;
static uint8 Sb_init = 0;
static uint8 Count_low;
static uint8 Count_high;
static uint16 IOaddr = 0x220;
static uint16 Irq = 7;
static uint16 Dma = 1;
static uint8 DMAmode = AUTO_DMA;
/*static*/ uint8 DMAcount;
static void (*FillBuffer)(uint8 *buf, uint16 buf_size);
#ifdef DJGPP
static _go32_dpmi_seginfo OldIntVectInfo;
static _go32_dpmi_seginfo NewIntVectInfo;
#else
static void (__interrupt *OldIntVect)(void);
#endif
/* function prototypes */
static void setNewIntVect (uint16 irq);
static void setOldIntVect (uint16 irq);
static void dsp_out (uint16 port, uint8 val);
static uint8 hextodec (char c);
static void logErr (char *st);
static uint8 getBlasterEnv (void);
static uint8 dsp_in (uint16 port);
/*****************************************************************************/
/* */
/* Module: newIntVect */
/* Purpose: The interrupt vector to handle the DAC DMAC completed interrupt */
/* Sends the next buffer to the SB and re-fills the current buffer. */
/* Author: Ron Fries */
/* Date: January 1, 1997 */
/* */
/*****************************************************************************/
#ifdef DJGPP
static void newIntVect(void)
#else
static void interrupt newIntVect (void)
#endif
{
uint16 addr;
if (DMAmode == STANDARD_DMA)
{
/* restart standard DMA transfer */
dsp_out (IOaddr + DSP_WRITE, 0x14);
dsp_out (IOaddr + DSP_WRITE, Count_low);
dsp_out (IOaddr + DSP_WRITE, Count_high);
}
DMAcount++;
/* acknowledge the DSP interrupt */
inp (IOaddr + DSP_ACK);
/* determine the current playback position */
addr = inp (DMA_BASE + (Dma << 1)); /* get low byte ptr */
addr |= inp (DMA_BASE + (Dma << 1)) << 8; /* and high byte ptr */
addr -= Sb_offset; /* subtract the offset */
/* if we're currently playing the first half of the buffer */
if (addr < Sb_buf_size)
{
/* reload the second half of the buffer */
FillBuffer(Sb_buffer + Sb_buf_size, Sb_buf_size);
#ifdef DJGPP
/* Copy data to DOS memory buffer */
dosmemput(Sb_buffer + Sb_buf_size, Sb_buf_size,
(theDOSBufferSegment << 4) + Sb_buf_size);
#endif
}
else
{
/* else reload the first half of the buffer */
FillBuffer(Sb_buffer, Sb_buf_size);
#ifdef DJGPP
/* Copy data to DOS memory buffer */
dosmemput(Sb_buffer, Sb_buf_size, theDOSBufferSegment << 4);
#endif
}
/* indicate end of interrupt */
outp (0x20, 0x20);
if (Irq > 7)
{
outp (0xa0, 0x20);
}
}
#ifdef DJGPP
END_OF_FUNCTION(newIntVect);
#endif
/*****************************************************************************/
/* */
/* Module: setNewIntVect */
/* Purpose: To set the specified interrupt vector to the sound output */
/* processing interrupt. */
/* Author: Ron Fries */
/* Date: January 1, 1997 */
/* */
/*****************************************************************************/
static void setNewIntVect (uint16 irq)
{
#ifdef DJGPP
/* Lock code / data */
LOCK_VARIABLE(DMAmode);
LOCK_VARIABLE(IOaddr);
LOCK_VARIABLE(Count_low);
LOCK_VARIABLE(DMAcount);
LOCK_VARIABLE(Dma);
LOCK_VARIABLE(Sb_offset);
LOCK_VARIABLE(Sb_buf_size);
LOCK_VARIABLE(theDOSBufferSegment);
LOCK_FUNCTION(newIntVect);
#endif
if (irq > 7)
{
#ifdef DJGPP
_go32_dpmi_get_protected_mode_interrupt_vector(irq + 0x68,
&OldIntVectInfo);
NewIntVectInfo.pm_selector = _my_cs();
NewIntVectInfo.pm_offset = (int)newIntVect;
_go32_dpmi_allocate_iret_wrapper(&NewIntVectInfo);
_go32_dpmi_set_protected_mode_interrupt_vector(irq + 0x68,
&NewIntVectInfo);
#else
OldIntVect = _dos_getvect (irq + 0x68);
_dos_setvect (irq + 0x68, newIntVect);
#endif
}
else
{
#ifdef DJGPP
_go32_dpmi_get_protected_mode_interrupt_vector(irq + 0x08,
&OldIntVectInfo);
NewIntVectInfo.pm_selector = _my_cs();
NewIntVectInfo.pm_offset = (int)newIntVect;
_go32_dpmi_allocate_iret_wrapper(&NewIntVectInfo);
_go32_dpmi_set_protected_mode_interrupt_vector(irq + 0x08,
&NewIntVectInfo);
#else
OldIntVect = _dos_getvect (irq + 0x08);
_dos_setvect (irq + 0x08, newIntVect);
#endif
}
}
/*****************************************************************************/
/* */
/* Module: setOldIntVect */
/* Purpose: To restore the original vector */
/* Author: Ron Fries */
/* Date: January 1, 1997 */
/* */
/*****************************************************************************/
static void setOldIntVect (uint16 irq)
{
if (irq > 7)
{
#ifdef DJGPP
_go32_dpmi_set_protected_mode_interrupt_vector(irq + 0x68,
&OldIntVectInfo);
_go32_dpmi_free_iret_wrapper(&NewIntVectInfo);
#else
_dos_setvect (irq + 0x68, OldIntVect);
#endif
}
else
{
#ifdef DJGPP
_go32_dpmi_set_protected_mode_interrupt_vector(irq + 0x08,
&OldIntVectInfo);
_go32_dpmi_free_iret_wrapper(&NewIntVectInfo);
#else
_dos_setvect (irq + 0x08, OldIntVect);
#endif
}
}
/*****************************************************************************/
/* */
/* Module: dsp_out */
/* Purpose: To send a byte to the SB's DSP */
/* Author: Ron Fries */
/* Date: September 10, 1996 */
/* */
/*****************************************************************************/
static void dsp_out(uint16 port, uint8 val)
{
uint32 timeout = 60000; /* set timeout */
/* wait for buffer to be free */
while((timeout--) && (inp(IOaddr + DSP_WRITE) & 0x80))
{
/* do nothing */
}
/* transmit the next byte */
outp(port,val);
}
/*****************************************************************************/
/* */
/* Module: dsp_in */
/* Purpose: To read a byte from the SB's DSP */
/* Author: Ron Fries */
/* Date: January 26, 1997 */
/* */
/*****************************************************************************/
static uint8 dsp_in(uint16 port)
{
uint16 x=10000; /* set timeout */
/* wait for buffer to be free */
while(((inp(IOaddr + 0x0E) & 0x80) == 0) && (x>0))
{
/* decrement the timeout */
x--;
}
if (x>0)
{
/* read the data byte */
return(inp(port));
}
else
{
return (0);
}
}
/*****************************************************************************/
/* */
/* Module: hextodec */
/* Purpose: Convert the input character to hex */
/* Author: Ron Fries */
/* Date: September 10, 1996 */
/* */
/*****************************************************************************/
uint8 hextodec (char c)
{
uint8 retval = 0;
c = toupper (c);
if ((c>='0') && (c<='9'))
{
retval = c - '0';
}
else if ((c>='A') && (c<='F'))
{
retval = c - 'A' + 10;
}
return (retval);
}
/*****************************************************************************/
/* */
/* Module: logErr */
/* Purpose: Displays an error message. */
/* Author: Ron Fries */
/* Date: September 24, 1996 */
/* */
/*****************************************************************************/
static void logErr (char *st)
{
#ifdef SBDRV_SHOW_ERR
printf ("%s",st);
#endif
}
/*****************************************************************************/
/* */
/* Module: getBlasterEnv */
/* Purpose: Read the BLASTER environment variable and set the local globals */
/* Author: Ron Fries */
/* Date: September 10, 1996 */
/* */
/*****************************************************************************/
static uint8 getBlasterEnv (void)
{
char *env;
char *ptr;
uint16 count = 0;
env = getenv("BLASTER");
/* if the environment variable exists */
if (env)
{
strupr(env);
/* search for the address setting */
ptr = strchr(env, 'A');
if (ptr)
{
/* if valid, read and convert the IO address */
IOaddr = (hextodec (ptr[1]) << 8) +
(hextodec (ptr[2]) << 4) +
(hextodec (ptr[3]));
/* verify the IO address is one of the possible SB settings */
switch (IOaddr)
{
case 0x210:
case 0x220:
case 0x230:
case 0x240:
case 0x250:
case 0x260:
case 0x280:
case 0x2A0:
case 0x2C0:
case 0x2E0:
/* IO address OK so indicate one more valid item found */
count++;
break;
default:
logErr ("Invalid Sound Blaster I/O address specified.\n");
logErr ("Possible values are: ");
logErr ("210, 220, 230, 240, 250, 260, 280, 2A0, 2C0, 2E0.\n");
}
}
else
{
logErr ("Unable to read Sound Blaster I/O address.\n");
}
/* search for the IRQ setting */
ptr = strchr(env, 'I');
if (ptr)
{
/* if valid, read and convert the IRQ */
/* if the IRQ has two digits */
if ((ptr[1] == '1') && ((ptr[2] >= '0') && (ptr[2] <='5')))
{
/* then convert accordingly (using decimal) */
Irq = hextodec (ptr[1]) * 10 + hextodec (ptr[2]);
}
else
{
/* else convert as a single hex digit */
Irq = hextodec (ptr[1]);
}
/* verify the IRQ setting is one of the possible SB settings */
switch (Irq)
{
case 2: /* two is actually the interrupt cascade for IRQs > 7 */
/* IRQ nine is the cascase for 2 */
Irq = 9;
/* IRQ OK so indicate one more valid item found */
count++;
break;
case 3:
case 4:
case 5:
case 7:
case 9:
case 10:
case 11:
case 12:
case 15:
/* IRQ OK so indicate one more valid item found */
count++;
break;
default:
logErr ("Invalid Sound Blaster IRQ specified.\n");
logErr ("Possible values are: ");
logErr ("2, 3, 4, 5, 7, 9, 10, 11, 12, 15.\n");
}
}
else
{
logErr ("Unable to read Sound Blaster IRQ.\n");
}
/* search for the DMA setting */
ptr = strchr(env, 'D');
if (ptr)
{
/* if valid, read and convert the DMA */
Dma = hextodec (ptr[1]);
/* verify the DMA setting is one of the possible 8-bit SB settings */
switch (Dma)
{
case 0:
case 1:
case 3:
/* DMA OK so indicate one more valid item found */
count++;
break;
default:
logErr ("Invalid Sound Blaster 8-bit DMA specified.\n");
logErr ("Possible values are: ");
logErr ("0, 1, 3.\n");
}
}
else
{
logErr ("Unable to read Sound Blaster DMA setting.\n");
}
}
else
{
logErr ("BLASTER enviroment variable not configured.");
}
return (count != 3);
}
/*****************************************************************************/
/* */
/* Module: low_malloc */
/* Purpose: To allocate memory in the first 640K of memory */
/* Author: Neil Bradley */
/* Date: December 16, 1996 */
/* */
/*****************************************************************************/
#ifdef __WATCOMC__
void dos_memalloc(unsigned short int para, unsigned short int *seg, unsigned short int *sel);
#pragma aux dos_memalloc = \
"push ecx" \
"push edx" \
"mov ax, 0100h" \
"int 31h" \
"pop ebx" \
"mov [ebx], dx" \
"pop ebx" \
"mov [ebx], ax" \
parm [bx] [ecx] [edx] \
modify [ax ebx ecx edx];
void dos_memfree(short int sel);
#pragma aux dos_memfree = \
"mov ax, 0101h" \
"int 31h" \
parm [dx] \
modify [ax dx];
void *low_malloc(int size)
{
unsigned short int seg;
unsigned short int i=0;
dos_memalloc((size >> 4) + 1, &seg, &i);
return((char *)(seg << 4));
}
#endif
/*****************************************************************************/
/* */
/* Module: Set_master_volume */
/* Purpose: To set the Sound Blaster's master volume */
/* Author: Neil Bradley */
/* Date: January 1, 1997 */
/* */
/*****************************************************************************/
void Set_master_volume(uint8 left, uint8 right)
{
/* if the SB was initialized */
if (Sb_init)
{
outp(IOaddr + 0x04, MASTER_VOLUME);
outp(IOaddr + 0x05, ((left & 0xf) << 4) + (right & 0x0f));
}
}
/*****************************************************************************/
/* */
/* Module: Set_line_volume */
/* Purpose: To set the Sound Blaster's line level volume */
/* Author: Neil Bradley */
/* Date: January 1, 1997 */
/* */
/*****************************************************************************/
void Set_line_volume(uint8 left, uint8 right)
{
/* if the SB was initialized */
if (Sb_init)
{
outp(IOaddr + 0x04, LINE_VOLUME);
outp(IOaddr + 0x05, ((left & 0xf) << 4) + (right & 0x0f));
}
}
/*****************************************************************************/
/* */
/* Module: Set_FM_volume */
/* Purpose: To set the Sound Blaster's FM volume */
/* Author: Neil Bradley */
/* Date: January 1, 1997 */
/* */
/*****************************************************************************/
void Set_FM_volume(uint8 left, uint8 right)
{
/* if the SB was initialized */
if (Sb_init)
{
outp(IOaddr + 0x04, FM_VOLUME);
outp(IOaddr + 0x05, ((left & 0xf) << 4) + (right & 0x0f));
}
}
/*****************************************************************************/
/* */
/* Module: ResetDSP */
/* Purpose: To reset the SB DSP. Returns a value of zero if unsuccessful. */
/* This function requires as input the SB base port address. */
/* Author: Ron Fries */
/* Date: August 5, 1997 */
/* */
/*****************************************************************************/
uint8 ResetDSP(uint16 ioaddr)
{
uint8 x;
uint16 y;
/* assume the init was not successful */
Sb_init = 0;
/* send a DSP reset to the SB */
outp (ioaddr + DSP_RESET, 1);
/* wait a few microsec */
x = inp(ioaddr + DSP_RESET);
x = inp(ioaddr + DSP_RESET);
x = inp(ioaddr + DSP_RESET);
x = inp(ioaddr + DSP_RESET);
/* clear the DSP reset */
outp (ioaddr + DSP_RESET,0);
/* wait a bit until the SB indicates good status */
y = 0;
do
{
x = inp (ioaddr + DSP_READ);
y++;
} while ((y < 1000) && (x != 0xaa));
/* if we were able to successfully reset the SB */
if (x == 0xaa)
{
/* turn on speaker */
dsp_out (ioaddr + DSP_WRITE, 0xd1);
/* read to make sure DSP register is clear */
dsp_in (ioaddr + DSP_READ);
/* set time constant */
dsp_out (ioaddr + DSP_WRITE, 0x40);
dsp_out (ioaddr + DSP_WRITE,
(unsigned char)(256 - 1000000L/Playback_freq));
/* indicate successful initialization */
Sb_init = 1;
}
return (Sb_init);
}
/*****************************************************************************/
/* */
/* Module: OpenSB */
/* Purpose: To reset the SB and prepare all buffers and other global */
/* global variables for sound output. Allows the user to select */
/* the playback frequency, number of buffers, and size of each */
/* buffer. Returns a value of zero if unsuccessful. */
/* Author: Ron Fries */
/* Date: January 1, 1997 */
/* */
/*****************************************************************************/
uint8 OpenSB(uint16 playback_freq, uint16 buffer_size)
{
/* initialize local globals */
if (buffer_size > 0)
{
Sb_buf_size = buffer_size;
}
Playback_freq = playback_freq;
/* assume the init was not successful */
Sb_init = 0;
/* attempt to read the Blaster Environment Variable */
if (getBlasterEnv() == 0)
{
/* if the DSP could be successfully reset */
if (ResetDSP(IOaddr) != 0 )
{
/* setup the DSP interrupt service routine */
setNewIntVect(Irq);
/* Enable the interrupt used */
if (Irq > 7)
{
outp (0xa1,inp(0xa1) & (~(1<<(Irq-8))));
}
else
{
outp (0x21,inp(0x21) & (~(1<<Irq)));
}
/* make sure interrupts are enabled */
_enable();
/* create a buffer to hold the data */
#ifdef __WATCOMC__
Sb_buffer = low_malloc (Sb_buf_size*2);
#elif defined(DJGPP)
Sb_buffer = (uint8 *)malloc(Sb_buf_size*2);
theDOSBufferSegment = __dpmi_allocate_dos_memory((Sb_buf_size*2+15) >> 4,
&theDOSBufferSelector);
#else
Sb_buffer = malloc (Sb_buf_size*2);
#endif
/* if we were unable to successfully allocate the buffer */
#ifdef DJGPP
if ((Sb_buffer == 0) || (theDOSBufferSegment == -1))
#else
if (Sb_buffer == 0)
#endif
{
logErr ("Unable to allocate buffer for audio output.\n");
/* close the SB */
CloseSB();
}
}
else
{
logErr ("Unable to initialize the Sound Card.\n");
}
}
return (Sb_init);
}
/*****************************************************************************/
/* */
/* Module: CloseSB */
/* Purpose: Closes the SB and disables the interrupts. */
/* Author: Ron Fries */
/* Date: January 1, 1997 */
/* */
/*****************************************************************************/
void CloseSB(void)
{
#ifdef __WATCOMC__
uint32 addr;
#endif
/* if the SB was initialized */
if (Sb_init)
{
/* stop all DMA transfer */
Stop_audio_output();
ResetDSP(IOaddr);
/* turn the speaker off */
dsp_out (IOaddr + DSP_WRITE, 0xd3);
/* indicate SB no longer active */
Sb_init = 0;
/* Disable the interrupt used */
if (Irq > 7)
{
outp (0xa1,inp(0xa1) | (1<<(Irq-8)));
}
else
{
outp (0x21,inp(0x21) | (1<<Irq));
}
/* restore the original interrupt routine */
setOldIntVect(Irq);
/* free any memory that had been allocated */
if (Sb_buffer != 0)
{
#ifdef __WATCOMC__
addr = (uint32) Sb_buffer;
dos_memfree((uint16)(addr >> 4));
#elif defined(DJGPP)
free(Sb_buffer);
__dpmi_free_dos_memory(theDOSBufferSelector);
#else
free (Sb_buffer);
#endif
}
}
}
/*****************************************************************************/
/* */
/* Module: Stop_audio_output */
/* Purpose: Stops the SB's DMA transfer. */
/* Author: Ron Fries */
/* Date: January 17, 1997 */
/* */
/*****************************************************************************/
void Stop_audio_output (void)
{
/* stop any transfer that may be in progress */
/* if the SB was initialized */
if (Sb_init)
{
/* halt DMA */
dsp_out (IOaddr + DSP_WRITE, 0xd0);
/* exit DMA operation*/
dsp_out (IOaddr + DSP_WRITE, 0xda);
/* halt DMA */
dsp_out (IOaddr + DSP_WRITE, 0xd0);
}
}
/*****************************************************************************/
/* */
/* Module: Start_audio_output */
/* Purpose: Fills all configured buffers and outputs the first. */
/* Author: Ron Fries */
/* Date: February 20, 1997 */
/* */
/*****************************************************************************/
uint8 Start_audio_output (uint8 dma_mode,
void (*fillBuffer)(uint8 *buf,uint16 n))
{
uint8 ret_val = 1;
static uint8 pagePort[8] = { 0x87, 0x83, 0x81, 0x82 };
uint8 offset_low;
uint8 offset_high;
uint8 page_no;
uint8 count_low;
uint8 count_high;
uint32 addr;
clock_t start_time;
/* if the SB initialized properly */
if (Sb_init)
{
/* set the fill buffer routine */
FillBuffer = fillBuffer;
/* keep track of the DMA selection */
DMAmode = dma_mode;
/* stop any transfer that may be in progress */
Stop_audio_output();
/* fill the buffer */
FillBuffer (Sb_buffer, Sb_buf_size*2);
#ifdef DJGPP
/* Copy data to DOS memory buffer */
dosmemput(Sb_buffer, Sb_buf_size * 2, theDOSBufferSegment << 4);
#endif
/* calculate high, low and page addresses of buffer */
#ifdef __WATCOMC__
addr = (uint32) Sb_buffer;
#elif defined(DJGPP)
addr = ((uint32) theDOSBufferSegment) << 4;
#else
addr = ((uint32)FP_SEG(Sb_buffer) << 4) +
(uint32)FP_OFF(Sb_buffer);
#endif
Sb_offset = (uint16)(addr & 0x0ffff);
offset_low = (uint8)(addr & 0x0ff);
offset_high = (uint8)((addr >> 8) & 0x0ff);
page_no = (uint8)(addr >> 16);
count_low = (uint8) ((Sb_buf_size*2)-1) & 0x0ff;
count_high = (uint8) (((Sb_buf_size*2)-1) >> 8) & 0x0ff;
/* program the DMAC for output transfer */
outp (DMA_MASK , 0x04 | Dma );
outp (DMA_FF , 0 );
/* select auto-initialize DMA mode */
outp (DMA_MODE , 0x58 | Dma );
outp (DMA_BASE + (Dma << 1) , offset_low );
outp (DMA_BASE + (Dma << 1) , offset_high );
outp (pagePort[Dma] , page_no );
outp (DMA_COUNT + (Dma << 1), count_low );
outp (DMA_COUNT + (Dma << 1), count_high );
outp (DMA_MASK , Dma );
/* calculate the high/low buffer size counts */
Count_low = (uint8) (Sb_buf_size-1) & 0x0ff;
Count_high = (uint8) ((Sb_buf_size-1) >> 8) & 0x0ff;
if (DMAmode == STANDARD_DMA)
{
/* start the standard DMA transfer */
dsp_out (IOaddr + DSP_WRITE, 0x14);
dsp_out (IOaddr + DSP_WRITE, Count_low);
dsp_out (IOaddr + DSP_WRITE, Count_high);
}
else
{
/* reset the DMA counter */
DMAcount = 0;
/* set the auto-initialize buffer size */
dsp_out (IOaddr + DSP_WRITE, 0x48);
dsp_out (IOaddr + DSP_WRITE, Count_low);
dsp_out (IOaddr + DSP_WRITE, Count_high);
/* and start the auto-initialize DMA transfer */
dsp_out (IOaddr + DSP_WRITE, 0x1c);
start_time = clock();
/* Delay for a bit and wait for DMAcount to change. */
/* Wait for the DMA to be called twice to make sure */
/* auto-init mode is working properly. */
while ((clock()-start_time < (int)(CLK_TCK/2)) && (DMAcount < 2))
{
/* This routine will wait for up to 1/2 second for DMAcount */
/* to change. The value in CLK_TCK is the number of times */
/* the clock will tick in one second. */
}
/* if the auto-init DMA is not active */
if (DMAcount < 2)
{
/* Reset the SB DSP */
ResetDSP(IOaddr);
/* then try again with STANDARD_DMA */
Start_audio_output (STANDARD_DMA, fillBuffer);
}
}
ret_val = 0;
}
return (ret_val);
}

View File

@ -1,90 +0,0 @@
/*****************************************************************************/
/* */
/* Module: SBDRV.H */
/* Purpose: Define function prototypes and structures required for the */
/* SB DRV routines, V1.3. */
/* Author(s): Ron Fries and Neil Bradley */
/* */
/* 01/30/97 - Initial Release */
/* 08/24/97 - V1.1 - Added defintion of SBDRV_SHOW_ERR to cause the SBDRV */
/* to display error messages. Comment line to supress */
/* 01/12/98 - V1.2 - Added support for DJGPP. */
/* 02/04/99 - V1.3 - Cleaned up DJGPP support, fixed a possible segfault */
/* in the reading of the BLASTER= env. variable, and */
/* added timeout to dsp_out(). */
/* */
/*****************************************************************************/
/* */
/* License Information and Copyright Notice */
/* ======================================== */
/* */
/* SBDrv is Copyright(c) 1997-1999 by Ron Fries, Neil Bradley and */
/* Bradford Mott */
/* */
/* This library is free software; you can redistribute it and/or modify it */
/* under the terms of version 2 of the GNU Library General Public License */
/* as published by the Free Software Foundation. */
/* */
/* This library is distributed in the hope that it will be useful, but */
/* WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library */
/* General Public License for more details. */
/* To obtain a copy of the GNU Library General Public License, write to the */
/* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
/* */
/* Any permitted reproduction of these routines, in whole or in part, must */
/* bear this legend. */
/* */
/*****************************************************************************/
#ifndef _SBDRV_H
#define _SBDRV_H
#ifndef _TYPEDEF_H
#define _TYPEDEF_H
#define SBDRV_SHOW_ERR /* delete line to supress error message printing */
/* define some data types to keep it platform independent */
#ifdef COMP16 /* if 16-bit compiler defined */
#define int8 char
#define int16 int
#define int32 long
#else /* else default to 32-bit compiler */
#define int8 char
#define int16 short
#define int32 int
#endif
#define uint8 unsigned int8
#define uint16 unsigned int16
#define uint32 unsigned int32
#endif
/* CONSTANT DEFINITIONS */
#define AUTO_DMA 0 /* selects auto-initialize DMA mode */
#define STANDARD_DMA 1 /* selects standard DMA mode */
/* global function prototypes */
#ifdef __cplusplus
extern "C" {
#endif
uint8 OpenSB(uint16 playback_freq, uint16 buffer_size);
void CloseSB(void);
uint8 Start_audio_output (uint8 dma_mode,
void (*fillBuffer)(uint8 *buf,uint16 n));
void Stop_audio_output (void);
void Set_master_volume(uint8 left, uint8 right);
void Set_line_volume(uint8 left, uint8 right);
void Set_FM_volume(uint8 left, uint8 right);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -1,218 +0,0 @@
SBDRV.C - SB DRIVER FUNCTIONS V1.3
==================================
NOTE: There is some important information in the COMPILER REQUIREMENTS
section.
The SBDRV.C file includes a set of simple routines for generating audio
output through a basic SB card, which should include all SB compatible cards.
The routines are designed to use the low-speed DMA output feature of the SB
card. The routines will automatically create the necessary buffers. The
audio processing/mixing function will automatically be called from the ISR
to provide a continuous stream of output.
These functions have been designed to work with multiple compilers.
They have already been tested with Borland C++ V3.1, MSVC V1.52,
WATCOM V10.6 and DJGPP. The Borland and MSVC compilers used were 16-bit,
while the WATCOM and DJGPP were 32-bit.
A header file 'SBDRV.H' has been included for use in other modules, and
provides the necessary function prototypes.
The routines are easy to use. Detailed descriptions on the function calls
are listed below.
FEATURES
--------
1) Full support for IRQs 0-15.
2) Ability to adjust the Master, FM and VOC/WAV volumes (note: not supported
by all cards).
2) Once started, the the sound IRQ automatically calls the audio processing
function. No additional calls are required.
3) The audio processing/mixing function is passed in as a initialization
parameter. This should allow the routines to be used in most applications
without modification.
4) The routines support AUTO-INITIALIZE and STANDARD DMA modes of operation.
This will allow good sound reproduction on the newer cards but still
support the old cards.
LIMITATIONS
-----------
The SB Driver routines have several limitations.
1) The routines can support any DMA channel from 0 to 3. If any other DMA
channel is needed, changes are required. This may need to be changed to
support all SB cards. This should not be much of a concern, though,
since DMA channels 4-7 are only used for 16-bit DMA operation.
2) The routines only support the low-speed DMA output function of the SB
card, which limits the maximum playback to approximately 22KHz.
3) The routines only support 8-bit mono audio output.
4) The routines require that the 'BLASTER' environment variable be configured.
This can be accomplished by using "SET BLASTER = A220 I7 D1" from the
command line. The 'A' value is the base address of the card, the 'I' value
is the configured interrupt line, and the 'D' value is the configured DMA
channel.
GENERAL OVERVIEW
----------------
On start-up of the system, a single call should be made to OpenSB.
This routine will reset the SB, setup the interrupts and prepare the
structures for sound output. This routine should not be called again
until after the CloseSB function is called.
On system shut-down, a single call should be made to CloseSB. This routine
will reset the SB, disable the interrupts and free the structures that were
created.
Once the SB has been initialized, calling the Start_audio_output function
will start the DMA operation. Once started, the sound output will be
continuous; no other calls are required. The sound output will continue
until either the Stop_audio_output or CloseSB functions are called.
The Stop_audio_output function can be used to pause the output temporarily.
COMPILER REQUIREMENTS
---------------------
*** Probably the most important compiler option is the selection of the data
segment/stack segment relationship. Because the IRQ will be calling the
fillBuffer function from within the IRQ, the data segment and stack segment
may not be equal. The compiler should be set to DS != SS. Without this
option, a stack overflow, system lockup or other unusual problem will occur.
Borland C++ 3.1 Notes
---------------------
The memory model must be set to LARGE and the COMP16 logical must be defined.
The DS/SS relationship can be left to 'default for memory model' since the
LARGE model automatically sets DS != SS, or this option can be set to never.
MSVC++ 1.5x
-----------
The memory model must be set to LARGE and the segment setup must be set to
'DS != SS, DS loaded on function entry'. Extern and unitialized data should
be assumed to be 'far'. Also, the COMP16 preprocessor symbol must be defined.
WATCOM 10.6
------------
The segment relationship must be set to DS != SS and stack probes must be
removed. This is accomplished by using the /zu and /s compiler options.
DJGPP
-----
Actually, I'm not sure what's required. I just compiled it as
GCC -o SBDRVTST.EXE SBDRVTST.C SBDRV.C and it worked. If you have problems
you may want to contact Bradford Mott as he added DJGPP support.
DETAILED FUNCTION DESCRIPTIONS
------------------------------
OpenSB (uint16 playback_freq, uint16 buffer_size)
-------------------------------------------------
This function resets and initializes the SB, initializes and enables the
interrupts, and creates and initializes all local global structures.
This function takes two parameters: the playback frequency and the size of
each playback buffer.
The playback frequency can be any unsigned integer from 1-65535, though
the maximum limit is currently set by the SB low-speed DMA audio routines
(approx. 22KHz).
The system will automatically allocate two playback buffers. The size of
each buffer can be any value from 1 to 65535. The actual size needed
will depend on the playback frequency and the DMA mode.
If Auto-Initialize DMA mode will be used, the size of the buffer can be set
as low as 35 or 40 with good output, though I don't recommend values lower
than 100.
If Standard DMA mode will be used, the minimum size that will produce clear
output varies based on the sample frequency. At maximum freq (22kHz), the
buffer size must be between 500-600 to produce clear output. At lower sample
frequencies, the size of the buffer can be much smaller.
If the call to OpenSB was unsuccessful, the function will return a value
of 0. Otherwise, the return value will be non-zero.
CloseSB (void)
--------------
This function disables all interrupts, frees all buffers and stops all
SB audio output. This function should be called before the program
exits. This function has no input or output parameters.
Set_FM_volume (uint8 left, uint8 right)
Set_line_volume (uint8 left, uint8 right)
Set_master_volume (uint8 left, uint8 right)
-------------------------------------------
These functions set the left and right volume levels for the FM, line and
master volume controls. These functions have no output parameters (void).
Start_audio_output (uint8 dma_mode, void (*fillBuffer)(uint8 *buf, uint16 n))
-----------------------------------------------------------------------------
This function starts the audio output. It takes two parameters, the dma mode
to be used and the name of the function that generates the audio.
The possible selections for the dma_mode are AUTO_DMA and STANDARD_DMA.
Normally, the AUTO_DMA option should always be used as the routines will
attempt to auto-detect whether the AUTO_DMA option is supported. If AUTO_DMA
is not supported, the routine will automatically switch to STANDARD_DMA mode.
It is possible (though unlikely) that the AUTO_DMA attempt will cause
problems. Because of this possibility, the system can be forced to use the
STANDARD_DMA mode by passing STANDARD_DMA to Start_audio_output. I recommend
providing the user the option to force STANDARD_DMA operation. Note that the
buffer size may need to be increased to support STANDARD_DMA (see OpenSB).
The fillBuffer function passed should be a function that takes two parameters,
a pointer to an unsigned 8-bit buffer and an unsigned 16-bit integer. The
fillBuffer routine should process the next 'n' bytes (specified by the 16-bit
integer) and place them in the buffer pointed to starting with byte 0. Any
function that meets these requirements can be used, which makes the SBDRV
routines generic.
License Information and Copyright Notice
========================================
SBDRV is Copyright(c) 1997-1999 by Ron Fries, Neil Bradley and Bradford Mott
This library is free software; you can redistribute it and/or modify it under
the terms of version 2 of the GNU Library General Public License as published
by the Free Software Foundation.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more
details.
To obtain a copy of the GNU Library General Public License, write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
Any permitted reproduction of these routines, in whole or in part, must bear
this legend.