1385 lines
44 KiB
C
1385 lines
44 KiB
C
/*
|
|
* $Id: awedrv.c 1.10 1996/09/13 17:00:08 chasan released $
|
|
* 1.11 1998/10/24 18:20:53 chasan released (Mixer API)
|
|
*
|
|
* Sound Blaster AWE32 audio driver.
|
|
*
|
|
* Copyright (C) 1996-1999 Carlos Hasan
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published
|
|
* by the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include "audio.h"
|
|
#include "drivers.h"
|
|
#include "msdos.h"
|
|
|
|
#ifndef TRUE
|
|
#define TRUE 1
|
|
#define FALSE 0
|
|
#endif
|
|
|
|
|
|
/*
|
|
* Macros to build a oscillator/register/port packed register
|
|
*/
|
|
#define AWEREG(nReg, nCnl, nPort) ((nCnl)|((nReg)<<5)|((nPort)<<8))
|
|
#define ALL 0
|
|
|
|
/*
|
|
* Sound Blaster AWE32 I/O ports offsets
|
|
*/
|
|
#define AWE32_DATA0 0x400 /* read and write of dword data */
|
|
#define AWE32_DATA1 0x800 /* read and write of word and dword */
|
|
#define AWE32_DATA2 0x802 /* read and write of word data */
|
|
#define AWE32_DATA3 0xC00 /* read and write of word data */
|
|
#define AWE32_POINTER 0xC02 /* read and write of register pointer */
|
|
|
|
/*
|
|
* Sound Blaster AWE32 voice indirect registers
|
|
*/
|
|
#define CPF AWEREG(0, ALL, 0) /* current pitch and fractional address */
|
|
#define PTRX AWEREG(1, ALL, 0) /* pitch target, reverb send, aux byte */
|
|
#define CVCF AWEREG(2, ALL, 0) /* current volume and filter cutoff */
|
|
#define VTFT AWEREG(3, ALL, 0) /* volume and filter cutoff targets */
|
|
#define PSST AWEREG(6, ALL, 0) /* pan send and loop start address */
|
|
#define CSL AWEREG(7, ALL, 0) /* chorus send and loop end address */
|
|
#define CCCA AWEREG(0, ALL, 1) /* Q, control bits, current address */
|
|
#define ENVVOL AWEREG(4, ALL, 1) /* volume envelope delay */
|
|
#define DCYSUSV AWEREG(5, ALL, 1) /* volume envelope sustain and decay */
|
|
#define ENVVAL AWEREG(6, ALL, 1) /* modulation envelope delay */
|
|
#define DCYSUS AWEREG(7, ALL, 1) /* modulation envelope sustain and decay */
|
|
#define ATKHLDV AWEREG(4, ALL, 2) /* volume envelope hold and attack */
|
|
#define LFO1VAL AWEREG(5, ALL, 2) /* LFO#1 delay */
|
|
#define ATKHLD AWEREG(6, ALL, 2) /* modulation envelope hold and attack */
|
|
#define LFO2VAL AWEREG(7, ALL, 2) /* LFO#2 delay */
|
|
#define IP AWEREG(0, ALL, 3) /* initial pitch */
|
|
#define IFATN AWEREG(1, ALL, 3) /* initial filter cutoff and attenuation */
|
|
#define PEFE AWEREG(2, ALL, 3) /* pitch and filter envelope heights */
|
|
#define FMMOD AWEREG(3, ALL, 3) /* vibrato and filter modulation from LFO#1 */
|
|
#define TREMFRQ AWEREG(4, ALL, 3) /* LFO#1 tremolo amount and frequency */
|
|
#define FM2FRQ2 AWEREG(5, ALL, 3) /* LFO#2 vibrato amount and frequency */
|
|
#define PROBE AWEREG(7, ALL, 3) /* dunno... not documented! */
|
|
|
|
/*
|
|
* Sound Blaster AWE32 global indirect registers
|
|
*/
|
|
#define HWCF4 AWEREG(1, 9, 1) /* configuration doubleword 4 */
|
|
#define HWCF5 AWEREG(1, 10, 1) /* configuration doubleword 5 */
|
|
#define HWCF6 AWEREG(1, 13, 1) /* configuration doubleword 6 */
|
|
#define SMALR AWEREG(1, 20, 1) /* SM address for left SM reads */
|
|
#define SMARR AWEREG(1, 21, 1) /* SM address for right SM reads */
|
|
#define SMALW AWEREG(1, 22, 1) /* SM address for left SM writes */
|
|
#define SMARW AWEREG(1, 23, 1) /* SM address for right SM writes */
|
|
#define SMLD AWEREG(1, 26, 1) /* sound memory left data */
|
|
#define SMRD AWEREG(1, 26, 2) /* sound memory right data */
|
|
#define WC AWEREG(1, 27, 2) /* sample counter */
|
|
#define HWCF1 AWEREG(1, 29, 1) /* configuration word 1 */
|
|
#define HWCF2 AWEREG(1, 30, 1) /* configuration word 2 */
|
|
#define HWCF3 AWEREG(1, 31, 1) /* configuration word 3 */
|
|
#define INIT1 AWEREG(2, ALL, 1) /* initialization array 1 */
|
|
#define INIT2 AWEREG(2, ALL, 2) /* initialization array 2 */
|
|
#define INIT3 AWEREG(3, ALL, 1) /* initialization array 3 */
|
|
#define INIT4 AWEREG(3, ALL, 2) /* initialization array 4 */
|
|
|
|
|
|
/*
|
|
* Sound Blaster AWE32 Sound Memory Manager Defines
|
|
*/
|
|
#define NODE_HEADER_SIZE 4
|
|
#define CLICKBUFSIZE 8
|
|
|
|
/*
|
|
* Sound Blaster AWE32 Chorus/Reverb/FilterQ default settings
|
|
*/
|
|
#define CHORUS 0 /*21*/
|
|
#define REVERB 0 /*58*/
|
|
#define FILTERQ 0
|
|
|
|
|
|
/*
|
|
* Sound Blaster AWE32 Initialization Arrays
|
|
*/
|
|
static WORD aInitTableA[4*32] = {
|
|
0x03FF, 0x0030, 0x07FF, 0x0130, 0x0BFF, 0x0230, 0x0FFF, 0x0330,
|
|
0x13FF, 0x0430, 0x17FF, 0x0530, 0x1BFF, 0x0630, 0x1FFF, 0x0730,
|
|
0x23FF, 0x0830, 0x27FF, 0x0930, 0x2BFF, 0x0A30, 0x2FFF, 0x0B30,
|
|
0x33FF, 0x0C30, 0x37FF, 0x0D30, 0x3BFF, 0x0E30, 0x3FFF, 0x0F30,
|
|
|
|
0x43FF, 0x0030, 0x47FF, 0x0130, 0x4BFF, 0x0230, 0x4FFF, 0x0330,
|
|
0x53FF, 0x0430, 0x57FF, 0x0530, 0x5BFF, 0x0630, 0x5FFF, 0x0730,
|
|
0x63FF, 0x0830, 0x67FF, 0x0930, 0x6BFF, 0x0A30, 0x6FFF, 0x0B30,
|
|
0x73FF, 0x0C30, 0x77FF, 0x0D30, 0x7BFF, 0x0E30, 0x7FFF, 0x0F30,
|
|
|
|
0x83FF, 0x0030, 0x87FF, 0x0130, 0x8BFF, 0x0230, 0x8FFF, 0x0330,
|
|
0x93FF, 0x0430, 0x97FF, 0x0530, 0x9BFF, 0x0630, 0x9FFF, 0x0730,
|
|
0xA3FF, 0x0830, 0xA7FF, 0x0930, 0xABFF, 0x0A30, 0xAFFF, 0x0B30,
|
|
0xB3FF, 0x0C30, 0xB7FF, 0x0D30, 0xBBFF, 0x0E30, 0xBFFF, 0x0F30,
|
|
|
|
0xC3FF, 0x0030, 0xC7FF, 0x0130, 0xCBFF, 0x0230, 0xCFFF, 0x0330,
|
|
0xD3FF, 0x0430, 0xD7FF, 0x0530, 0xDBFF, 0x0630, 0xDFFF, 0x0730,
|
|
0xE3FF, 0x0830, 0xE7FF, 0x0930, 0xEBFF, 0x0A30, 0xEFFF, 0x0B30,
|
|
0xF3FF, 0x0C30, 0xF7FF, 0x0D30, 0xFBFF, 0x0E30, 0xFFFF, 0x0F30
|
|
};
|
|
|
|
static WORD aInitTableB[4*32] = {
|
|
0x03FF, 0x8030, 0x07FF, 0x8130, 0x0BFF, 0x8230, 0x0FFF, 0x8330,
|
|
0x13FF, 0x8430, 0x17FF, 0x8530, 0x1BFF, 0x8630, 0x1FFF, 0x8730,
|
|
0x23FF, 0x8830, 0x27FF, 0x8930, 0x2BFF, 0x8A30, 0x2FFF, 0x8B30,
|
|
0x33FF, 0x8C30, 0x37FF, 0x8D30, 0x3BFF, 0x8E30, 0x3FFF, 0x8F30,
|
|
|
|
0x43FF, 0x8030, 0x47FF, 0x8130, 0x4BFF, 0x8230, 0x4FFF, 0x8330,
|
|
0x53FF, 0x8430, 0x57FF, 0x8530, 0x5BFF, 0x8630, 0x5FFF, 0x8730,
|
|
0x63FF, 0x8830, 0x67FF, 0x8930, 0x6BFF, 0x8A30, 0x6FFF, 0x8B30,
|
|
0x73FF, 0x8C30, 0x77FF, 0x8D30, 0x7BFF, 0x8E30, 0x7FFF, 0x8F30,
|
|
|
|
0x83FF, 0x8030, 0x87FF, 0x8130, 0x8BFF, 0x8230, 0x8FFF, 0x8330,
|
|
0x93FF, 0x8430, 0x97FF, 0x8530, 0x9BFF, 0x8630, 0x9FFF, 0x8730,
|
|
0xA3FF, 0x8830, 0xA7FF, 0x8930, 0xABFF, 0x8A30, 0xAFFF, 0x8B30,
|
|
0xB3FF, 0x8C30, 0xB7FF, 0x8D30, 0xBBFF, 0x8E30, 0xBFFF, 0x8F30,
|
|
|
|
0xC3FF, 0x8030, 0xC7FF, 0x8130, 0xCBFF, 0x8230, 0xCFFF, 0x8330,
|
|
0xD3FF, 0x8430, 0xD7FF, 0x8530, 0xDBFF, 0x8630, 0xDFFF, 0x8730,
|
|
0xE3FF, 0x8830, 0xE7FF, 0x8930, 0xEBFF, 0x8A30, 0xEFFF, 0x8B30,
|
|
0xF3FF, 0x8C30, 0xF7FF, 0x8D30, 0xFBFF, 0x8E30, 0xFFFF, 0x8F30
|
|
};
|
|
|
|
static WORD aInitTableC[4*32] = {
|
|
0x0C10, 0x8470, 0x14FE, 0xB488, 0x167F, 0xA470, 0x18E7, 0x84B5,
|
|
0x1B6E, 0x842A, 0x1F1D, 0x852A, 0x0DA3, 0x8F7C, 0x167E, 0xF254,
|
|
0x0000, 0x842A, 0x0001, 0x852A, 0x18E6, 0x8BAA, 0x1B6D, 0xF234,
|
|
0x229F, 0x8429, 0x2746, 0x8529, 0x1F1C, 0x86E7, 0x229E, 0xF224,
|
|
|
|
0x0DA4, 0x8429, 0x2C29, 0x8529, 0x2745, 0x87F6, 0x2C28, 0xF254,
|
|
0x383B, 0x8428, 0x320F, 0x8528, 0x320E, 0x8F02, 0x1341, 0xF264,
|
|
0x3EB6, 0x8428, 0x3EB9, 0x8528, 0x383A, 0x8FA9, 0x3EB5, 0xF294,
|
|
0x3EB7, 0x8474, 0x3EBA, 0x8575, 0x3EB8, 0xC4C3, 0x3EBB, 0xC5C3,
|
|
|
|
0x0000, 0xA404, 0x0001, 0xA504, 0x141F, 0x8671, 0x14FD, 0x8287,
|
|
0x3EBC, 0xE610, 0x3EC8, 0x8C7B, 0x031A, 0x87E6, 0x3EC8, 0x86F7,
|
|
0x3EC0, 0x821E, 0x3EBE, 0xD208, 0x3EBD, 0x821F, 0x3ECA, 0x8386,
|
|
0x3EC1, 0x8C03, 0x3EC9, 0x831E, 0x3ECA, 0x8C4C, 0x3EBF, 0x8C55,
|
|
|
|
0x3EC9, 0xC208, 0x3EC4, 0xBC84, 0x3EC8, 0x8EAD, 0x3EC8, 0xD308,
|
|
0x3EC2, 0x8F7E, 0x3ECB, 0x8219, 0x3ECB, 0xD26E, 0x3EC5, 0x831F,
|
|
0x3EC6, 0xC308, 0x3EC3, 0xB2FF, 0x3EC9, 0x8265, 0x3EC9, 0x8319,
|
|
0x1342, 0xD36E, 0x3EC7, 0xB3FF, 0x0000, 0x8365, 0x1420, 0x9570
|
|
};
|
|
|
|
static WORD aInitTableD[4*32] = {
|
|
0x0C10, 0x8470, 0x14FE, 0xB488, 0x167F, 0xA470, 0x18E7, 0x84B5,
|
|
0x1B6E, 0x842A, 0x1F1D, 0x852A, 0x0DA3, 0x0F7C, 0x167E, 0x7254,
|
|
0x0000, 0x842A, 0x0001, 0x852A, 0x18E6, 0x0BAA, 0x1B6D, 0x7234,
|
|
0x229F, 0x8429, 0x2746, 0x8529, 0x1F1C, 0x06E7, 0x229E, 0x7224,
|
|
|
|
0x0DA4, 0x8429, 0x2C29, 0x8529, 0x2745, 0x07F6, 0x2C28, 0x7254,
|
|
0x383B, 0x8428, 0x320F, 0x8528, 0x320E, 0x0F02, 0x1341, 0x7264,
|
|
0x3EB6, 0x8428, 0x3EB9, 0x8528, 0x383A, 0x0FA9, 0x3EB5, 0x7294,
|
|
0x3EB7, 0x8474, 0x3EBA, 0x8575, 0x3EB8, 0x44C3, 0x3EBB, 0x45C3,
|
|
|
|
0x0000, 0xA404, 0x0001, 0xA504, 0x141F, 0x0671, 0x14FD, 0x0287,
|
|
0x3EBC, 0xE610, 0x3EC8, 0x0C7B, 0x031A, 0x07E6, 0x3EC8, 0x86F7,
|
|
0x3EC0, 0x821E, 0x3EBE, 0xD208, 0x3EBD, 0x021F, 0x3ECA, 0x0386,
|
|
0x3EC1, 0x0C03, 0x3EC9, 0x031E, 0x3ECA, 0x8C4C, 0x3EBF, 0x0C55,
|
|
|
|
0x3EC9, 0xC208, 0x3EC4, 0xBC84, 0x3EC8, 0x0EAD, 0x3EC8, 0xD308,
|
|
0x3EC2, 0x8F7E, 0x3ECB, 0x0219, 0x3ECB, 0xD26E, 0x3EC5, 0x031F,
|
|
0x3EC6, 0xC308, 0x3EC3, 0x32FF, 0x3EC9, 0x0265, 0x3EC9, 0x8319,
|
|
0x1342, 0xD36E, 0x3EC7, 0x33FF, 0x0000, 0x8365, 0x1420, 0x9570
|
|
};
|
|
|
|
/*
|
|
* Sound Blaster AWE32 Linear Volume Table
|
|
*/
|
|
static BYTE aVolumeTable[65] = {
|
|
0xFF,
|
|
0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38, 0x37,
|
|
0x36, 0x35, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30,
|
|
0x2F, 0x2E, 0x2D, 0x2D, 0x2C, 0x2B, 0x2A, 0x29,
|
|
0x29, 0x28, 0x27, 0x26, 0x26, 0x25, 0x24, 0x23,
|
|
0x23, 0x22, 0x21, 0x21, 0x20, 0x1F, 0x1F, 0x1E,
|
|
0x1D, 0x1D, 0x1C, 0x1B, 0x1B, 0x1A, 0x19, 0x19,
|
|
0x18, 0x18, 0x17, 0x16, 0x16, 0x15, 0x15, 0x14,
|
|
0x13, 0x13, 0x12, 0x12, 0x11, 0x11, 0x10, 0x10
|
|
};
|
|
|
|
|
|
/*
|
|
* Sound Blaster AWE32 configuration state
|
|
*/
|
|
static struct {
|
|
WORD wPort;
|
|
BYTE nIrqLine;
|
|
BYTE nDmaChannel;
|
|
WORD wBasePort;
|
|
WORD wPointerPort;
|
|
WORD wDataPort[4];
|
|
DWORD dwTopOfMemory;
|
|
UINT nVoices;
|
|
DWORD aCurrentAddress[32];
|
|
DWORD aStartAddress[32];
|
|
DWORD aLoopStart[32];
|
|
DWORD aLoopEnd[32];
|
|
DWORD aFrequencyTable[32];
|
|
BYTE aVolumeTable[32];
|
|
BYTE aPanningTable[32];
|
|
LPFNAUDIOTIMER lpfnAudioTimer;
|
|
} AWE32;
|
|
|
|
static volatile BYTE nEmuIndex;
|
|
|
|
|
|
/*
|
|
* Low-level Sound Blaster AWE32 programming routines
|
|
*/
|
|
static VOID AWEPortW(WORD wIndex, WORD wData)
|
|
{
|
|
nEmuIndex = LOBYTE(wIndex);
|
|
/* get oscillator number (0-31) and register number (0-7) */
|
|
OUTW(AWE32.wPointerPort, LOBYTE(wIndex));
|
|
/* write the word data into the specified I/O data port (0-3) */
|
|
OUTW(AWE32.wDataPort[HIBYTE(wIndex)], wData);
|
|
}
|
|
|
|
static WORD AWEPortRW(WORD wIndex)
|
|
{
|
|
nEmuIndex = LOBYTE(wIndex);
|
|
OUTW(AWE32.wPointerPort, LOBYTE(wIndex));
|
|
return INW(AWE32.wDataPort[HIBYTE(wIndex)]);
|
|
}
|
|
|
|
static VOID AWEPortDW(WORD wIndex, DWORD dwData)
|
|
{
|
|
nEmuIndex = LOBYTE(wIndex);
|
|
/* get oscillator number (0-31) and register number (0-7) */
|
|
OUTW(AWE32.wPointerPort, LOBYTE(wIndex));
|
|
/* write the doubleword data into the specified I/O port (0-1) */
|
|
OUTW(AWE32.wDataPort[HIBYTE(wIndex)], LOWORD(dwData));
|
|
OUTW(AWE32.wDataPort[HIBYTE(wIndex)] + 2, HIWORD(dwData));
|
|
}
|
|
|
|
static DWORD AWEPortRDW(WORD wIndex)
|
|
{
|
|
DWORD dwData;
|
|
nEmuIndex = LOBYTE(wIndex);
|
|
OUTW(AWE32.wPointerPort, LOBYTE(wIndex));
|
|
dwData = (DWORD) INW(AWE32.wDataPort[HIBYTE(wIndex)]);
|
|
dwData |= ((DWORD) INW(AWE32.wDataPort[HIBYTE(wIndex)] + 2)) << 16;
|
|
return dwData;
|
|
}
|
|
|
|
static VOID AWEWait(UINT nTicks)
|
|
{
|
|
UINT nTarget, nTimeOut;
|
|
nTarget = (WORD) (AWEPortRW(WC) + nTicks);
|
|
for (nTimeOut = 0; nTimeOut < 0xC000; nTimeOut++)
|
|
if (AWEPortRW(WC) == nTarget) break;
|
|
}
|
|
|
|
static BOOL AWEProbe(WORD wBasePort)
|
|
{
|
|
/* set up AWE32 base I/O port addresses */
|
|
if (wBasePort > 0x300) wBasePort -= AWE32_DATA0;
|
|
AWE32.wDataPort[0] = wBasePort + AWE32_DATA0;
|
|
AWE32.wDataPort[1] = wBasePort + AWE32_DATA1;
|
|
AWE32.wDataPort[2] = wBasePort + AWE32_DATA2;
|
|
AWE32.wDataPort[3] = wBasePort + AWE32_DATA3;
|
|
AWE32.wPointerPort = wBasePort + AWE32_POINTER;
|
|
|
|
/* probe if a EMU8000 chip is there */
|
|
AWEPortRW(PROBE);
|
|
return ((AWEPortRW(PROBE) & 0x000f) == 0x000c &&
|
|
(AWEPortRW(HWCF1) & 0x007e) == 0x0058 &&
|
|
(AWEPortRW(HWCF2) & 0x0003) == 0x0003);
|
|
}
|
|
|
|
static BOOL AWEInitialize(VOID)
|
|
{
|
|
UINT n;
|
|
|
|
/* check if we have an EMU8000 chip */
|
|
if (!AWEProbe(AWE32.wBasePort))
|
|
return TRUE;
|
|
|
|
/* reset the hardware configuration */
|
|
AWEPortW(HWCF1, 0x0059);
|
|
AWEPortW(HWCF2, 0x0020);
|
|
|
|
/* initialize all the channels */
|
|
for (n = 0; n < 32; n++) {
|
|
/* turn off envelope engine */
|
|
AWEPortW(DCYSUSV + n, 0x0080);
|
|
}
|
|
for (n = 0; n < 32; n++) {
|
|
/* initialize all envelope and sound engine registers */
|
|
AWEPortW(ENVVOL + n, 0x0000);
|
|
AWEPortW(ENVVAL + n, 0x0000);
|
|
AWEPortW(DCYSUS + n, 0x0000);
|
|
AWEPortW(ATKHLDV + n, 0x0000);
|
|
AWEPortW(LFO1VAL + n, 0x0000);
|
|
AWEPortW(ATKHLD + n, 0x0000);
|
|
AWEPortW(LFO2VAL + n, 0x0000);
|
|
AWEPortW(IP + n, 0x0000);
|
|
AWEPortW(IFATN + n, 0x0000);
|
|
AWEPortW(PEFE + n, 0x0000);
|
|
AWEPortW(FMMOD + n, 0x0000);
|
|
AWEPortW(TREMFRQ + n, 0x0000);
|
|
AWEPortW(FM2FRQ2 + n, 0x0000);
|
|
AWEPortDW(PTRX + n, 0x00000000L);
|
|
AWEPortDW(VTFT + n, 0x00000000L);
|
|
AWEPortDW(PSST + n, 0x00000000L);
|
|
AWEPortDW(CSL + n, 0x00000000L);
|
|
AWEPortDW(CCCA + n, 0x00000000L);
|
|
}
|
|
for (n = 0; n < 32; n++) {
|
|
/* now initialize the "current" registers */
|
|
AWEPortDW(CPF + n, 0x00000000L);
|
|
AWEPortDW(CVCF + n, 0x00000000L);
|
|
}
|
|
|
|
/* initialize the sound memory DMA registers */
|
|
AWEPortW(SMALR, 0x0000);
|
|
AWEPortW(SMARR, 0x0000);
|
|
AWEPortW(SMALW, 0x0000);
|
|
AWEPortW(SMARW, 0x0000);
|
|
|
|
/* set up the initialization arrays */
|
|
|
|
/* 1. copy the first set of initialization arrays */
|
|
for (n = 0; n < 32; n++)
|
|
AWEPortW(INIT1 + n, aInitTableA[0x00 + n]);
|
|
for (n = 0; n < 32; n++)
|
|
AWEPortW(INIT2 + n, aInitTableA[0x20 + n]);
|
|
for (n = 0; n < 32; n++)
|
|
AWEPortW(INIT3 + n, aInitTableA[0x40 + n]);
|
|
for (n = 0; n < 32; n++)
|
|
AWEPortW(INIT4 + n, aInitTableA[0x60 + n]);
|
|
|
|
/* 2. wait 1024 sample periods (24 msec) */
|
|
AWEWait(1024);
|
|
|
|
/* 3. copy second set of initialization arrays */
|
|
for (n = 0; n < 32; n++)
|
|
AWEPortW(INIT1 + n, aInitTableB[0x00 + n]);
|
|
for (n = 0; n < 32; n++)
|
|
AWEPortW(INIT2 + n, aInitTableB[0x20 + n]);
|
|
for (n = 0; n < 32; n++)
|
|
AWEPortW(INIT3 + n, aInitTableB[0x40 + n]);
|
|
for (n = 0; n < 32; n++)
|
|
AWEPortW(INIT4 + n, aInitTableB[0x60 + n]);
|
|
|
|
/* 4. copy third set of initialization arrays */
|
|
for (n = 0; n < 32; n++)
|
|
AWEPortW(INIT1 + n, aInitTableC[0x00 + n]);
|
|
for (n = 0; n < 32; n++)
|
|
AWEPortW(INIT2 + n, aInitTableC[0x20 + n]);
|
|
for (n = 0; n < 32; n++)
|
|
AWEPortW(INIT3 + n, aInitTableC[0x40 + n]);
|
|
for (n = 0; n < 32; n++)
|
|
AWEPortW(INIT4 + n, aInitTableC[0x60 + n]);
|
|
|
|
/* 5. set the harware configuration registers */
|
|
AWEPortDW(HWCF4, 0x00000000L);
|
|
AWEPortDW(HWCF5, 0x00000083L);
|
|
AWEPortDW(HWCF6, 0x00008000L);
|
|
|
|
/* 6. copy fourth set of initialization arrays */
|
|
for (n = 0; n < 32; n++)
|
|
AWEPortW(INIT1 + n, aInitTableD[0x00 + n]);
|
|
for (n = 0; n < 32; n++)
|
|
AWEPortW(INIT2 + n, aInitTableD[0x20 + n]);
|
|
for (n = 0; n < 32; n++)
|
|
AWEPortW(INIT3 + n, aInitTableD[0x40 + n]);
|
|
for (n = 0; n < 32; n++)
|
|
AWEPortW(INIT4 + n, aInitTableD[0x60 + n]);
|
|
|
|
/* enable audio output */
|
|
AWEPortW(HWCF3, 0x0004);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static DWORD AWEDetectMemory(VOID)
|
|
{
|
|
DWORD dwAddr;
|
|
UINT nChannel, nTimeOut;
|
|
|
|
/* allocate 15/15 channels in left read/write DMA mode */
|
|
for (nChannel = 0; nChannel < 30; nChannel++) {
|
|
AWEPortW(DCYSUSV + nChannel, 0x0080);
|
|
AWEPortDW(VTFT + nChannel, 0x00000000L);
|
|
AWEPortDW(CVCF + nChannel, 0x00000000L);
|
|
AWEPortDW(PTRX + nChannel, 0x40000000L);
|
|
AWEPortDW(CPF + nChannel, 0x40000000L);
|
|
AWEPortDW(PSST + nChannel, 0x00000000L);
|
|
AWEPortDW(CSL + nChannel, 0x00000000L);
|
|
/* select left read/write and enter in DMA mode */
|
|
AWEPortDW(CCCA + nChannel, nChannel & 1 ? 0x06000000L : 0x04000000L);
|
|
}
|
|
|
|
/* wait until the empty and full bits of the DMA streams are clear */
|
|
for (nTimeOut = 0; nTimeOut < 0x8000; nTimeOut++)
|
|
if (!(AWEPortRDW(SMALR) & 0x80000000L) &&
|
|
!(AWEPortRDW(SMALW) & 0x80000000L)) break;
|
|
|
|
/* write pattern at start of sound memory address space */
|
|
AWEPortDW(SMALW, 0x00200000L);
|
|
AWEPortW(SMLD, 0xAAAA);
|
|
AWEPortW(SMLD, 0x5555);
|
|
|
|
/* read it back to check if we have sound memory */
|
|
AWEPortDW(SMALR, 0x00200000L);
|
|
AWEPortRW(SMLD);
|
|
if (AWEPortRW(SMLD) == 0xAAAA &&
|
|
AWEPortRW(SMLD) == 0x5555) {
|
|
|
|
/* check how much sound memory we have (up to 28MB) */
|
|
for (dwAddr = 0x00220000L; dwAddr < 0xFE0000L; dwAddr += 0x10000L) {
|
|
|
|
/* write pattern at sound memory step location */
|
|
AWEPortDW(SMALW, dwAddr);
|
|
AWEPortW(SMLD, 0x1234);
|
|
AWEPortW(SMLD, 0x5678);
|
|
|
|
/* check first sound memory location for mirrorring */
|
|
AWEPortDW(SMALR, 0x00200000L);
|
|
AWEPortRW(SMLD);
|
|
if (AWEPortRW(SMLD) != 0xAAAA) break;
|
|
|
|
/* now, check if the pattern was actually written */
|
|
AWEPortDW(SMALR, dwAddr);
|
|
AWEPortDW(SMALR, dwAddr);
|
|
AWEPortRW(SMLD);
|
|
if (AWEPortRW(SMLD) != 0x1234 ||
|
|
AWEPortRW(SMLD) != 0x5678) break;
|
|
}
|
|
dwAddr -= 0x00200000L;
|
|
}
|
|
else {
|
|
/* we do not have sound memory */
|
|
dwAddr = 0L;
|
|
}
|
|
|
|
/* wait until the empty and full bits of the DMA streams are clear */
|
|
for (nTimeOut = 0; nTimeOut < 0x8000; nTimeOut++)
|
|
if (!(AWEPortRDW(SMALR) & 0x80000000L) &&
|
|
!(AWEPortRDW(SMALW) & 0x80000000L)) break;
|
|
|
|
/* deallocate 30 channels */
|
|
for (nChannel = 0; nChannel < 30; nChannel++) {
|
|
AWEPortDW(CCCA + nChannel, 0x00000000L);
|
|
AWEPortW(DCYSUSV + nChannel, 0x807F);
|
|
}
|
|
|
|
return dwAddr;
|
|
}
|
|
|
|
static VOID AWEUploadData(BYTE nFirstChannel, DWORD dwAddress,
|
|
LPWORD lpData, UINT nCount)
|
|
{
|
|
UINT nTimeOut, nChannel;
|
|
|
|
/* allocate channels in left write DMA mode */
|
|
for (nChannel = nFirstChannel; nChannel < 32; nChannel++) {
|
|
AWEPortW(DCYSUSV + nChannel, 0x0080);
|
|
AWEPortDW(VTFT + nChannel, 0x00000000L);
|
|
AWEPortDW(CVCF + nChannel, 0x00000000L);
|
|
AWEPortDW(PTRX + nChannel, 0x40000000L);
|
|
AWEPortDW(CPF + nChannel, 0x40000000L);
|
|
AWEPortDW(PSST + nChannel, 0x00000000L);
|
|
AWEPortDW(CSL + nChannel, 0x00000000L);
|
|
AWEPortDW(CCCA + nChannel, 0x06000000L);
|
|
}
|
|
|
|
/* wait until the full bit of the DMA stream is clear */
|
|
for (nTimeOut = 0; nTimeOut < 0x8000; nTimeOut++)
|
|
if (!(AWEPortRDW(SMALW) & 0x80000000L)) break;
|
|
|
|
/* set start address for writting data */
|
|
AWEPortDW(SMALW, dwAddress);
|
|
|
|
/* upload 16-bit signed words to sound memory */
|
|
while (nCount--) {
|
|
AWEPortW(SMLD, *lpData++);
|
|
}
|
|
|
|
/* wait until the full bit of the DMA stream is clear */
|
|
for (nTimeOut = 0; nTimeOut < 0x8000; nTimeOut++)
|
|
if (!(AWEPortRDW(SMALW) & 0x80000000L)) break;
|
|
|
|
/* deallocate channels */
|
|
for (nChannel = nFirstChannel; nChannel < 32; nChannel++) {
|
|
AWEPortDW(CCCA + nChannel, 0x00000000L);
|
|
AWEPortW(DCYSUSV + nChannel, 0x807F);
|
|
AWEPortW(DCYSUSV + nChannel, 0x0080);
|
|
AWEPortDW(VTFT + nChannel, 0x00000000L);
|
|
AWEPortDW(CVCF + nChannel, 0x00000000L);
|
|
AWEPortDW(PTRX + nChannel, 0x00000000L);
|
|
AWEPortDW(CPF + nChannel, 0x00000000L);
|
|
}
|
|
}
|
|
|
|
static VOID AWEUploadData8(BYTE nFirstChannel, DWORD dwAddress,
|
|
LPBYTE lpData, UINT nCount)
|
|
{
|
|
UINT nTimeOut, nChannel;
|
|
|
|
/* allocate channels in left write DMA mode */
|
|
for (nChannel = nFirstChannel; nChannel < 32; nChannel++) {
|
|
AWEPortW(DCYSUSV + nChannel, 0x0080);
|
|
AWEPortDW(VTFT + nChannel, 0x00000000L);
|
|
AWEPortDW(CVCF + nChannel, 0x00000000L);
|
|
AWEPortDW(PTRX + nChannel, 0x40000000L);
|
|
AWEPortDW(CPF + nChannel, 0x40000000L);
|
|
AWEPortDW(PSST + nChannel, 0x00000000L);
|
|
AWEPortDW(CSL + nChannel, 0x00000000L);
|
|
AWEPortDW(CCCA + nChannel, 0x06000000L);
|
|
}
|
|
|
|
/* wait until the full bit of the DMA stream is clear */
|
|
for (nTimeOut = 0; nTimeOut < 0x8000; nTimeOut++)
|
|
if (!(AWEPortRDW(SMALW) & 0x80000000L)) break;
|
|
|
|
/* set start address for writting data */
|
|
AWEPortDW(SMALW, dwAddress);
|
|
|
|
/* upload 8-bit signed bytes to sound memory */
|
|
while (nCount--) {
|
|
AWEPortW(SMLD, *lpData++ << 8);
|
|
}
|
|
|
|
/* wait until the full bit of the DMA stream is clear */
|
|
for (nTimeOut = 0; nTimeOut < 0x8000; nTimeOut++)
|
|
if (!(AWEPortRDW(SMALW) & 0x80000000L)) break;
|
|
|
|
/* deallocate channel */
|
|
for (nChannel = nFirstChannel; nChannel < 32; nChannel++) {
|
|
AWEPortDW(CCCA + nChannel, 0x00000000L);
|
|
AWEPortW(DCYSUSV + nChannel, 0x807F);
|
|
AWEPortW(DCYSUSV + nChannel, 0x0080);
|
|
AWEPortDW(VTFT + nChannel, 0x00000000L);
|
|
AWEPortDW(CVCF + nChannel, 0x00000000L);
|
|
AWEPortDW(PTRX + nChannel, 0x00000000L);
|
|
AWEPortDW(CPF + nChannel, 0x00000000L);
|
|
}
|
|
}
|
|
|
|
static VOID AWEDownloadData(BYTE nFirstChannel, DWORD dwAddress,
|
|
LPWORD lpData, UINT nCount)
|
|
{
|
|
UINT nTimeOut, nChannel;
|
|
|
|
/* allocate channels in left read DMA mode */
|
|
for (nChannel = nFirstChannel; nChannel < 32; nChannel++) {
|
|
AWEPortW(DCYSUSV + nChannel, 0x0080);
|
|
AWEPortDW(VTFT + nChannel, 0x00000000L);
|
|
AWEPortDW(CVCF + nChannel, 0x00000000L);
|
|
AWEPortDW(PTRX + nChannel, 0x40000000L);
|
|
AWEPortDW(CPF + nChannel, 0x40000000L);
|
|
AWEPortDW(PSST + nChannel, 0x00000000L);
|
|
AWEPortDW(CSL + nChannel, 0x00000000L);
|
|
AWEPortDW(CCCA + nChannel, 0x04000000L);
|
|
}
|
|
|
|
/* wait until the empty bit of the DMA stream is clear */
|
|
for (nTimeOut = 0; nTimeOut < 0x8000; nTimeOut++)
|
|
if (!(AWEPortRDW(SMALR) & 0x80000000L)) break;
|
|
|
|
/* set start address for reading data */
|
|
AWEPortDW(SMALR, dwAddress);
|
|
AWEPortRW(SMLD);
|
|
|
|
/* download 16-bit signed words from sound memory */
|
|
while (nCount--) {
|
|
*lpData++ = AWEPortRW(SMLD);
|
|
}
|
|
|
|
/* wait until the empty bit of the DMA stream is clear */
|
|
for (nTimeOut = 0; nTimeOut < 0x8000; nTimeOut++)
|
|
if (!(AWEPortRDW(SMALR) & 0x80000000L)) break;
|
|
|
|
/* deallocate channel */
|
|
for (nChannel = nFirstChannel; nChannel < 32; nChannel++) {
|
|
AWEPortDW(CCCA + nChannel, 0x00000000L);
|
|
AWEPortW(DCYSUSV + nChannel, 0x807F);
|
|
AWEPortW(DCYSUSV + nChannel, 0x0080);
|
|
AWEPortDW(VTFT + nChannel, 0x00000000L);
|
|
AWEPortDW(CVCF + nChannel, 0x00000000L);
|
|
AWEPortDW(PTRX + nChannel, 0x00000000L);
|
|
AWEPortDW(CPF + nChannel, 0x00000000L);
|
|
}
|
|
}
|
|
|
|
static VOID AWEPrimeNote(BYTE nChannel, WORD wPitch,
|
|
BYTE nVolume, BYTE nPanning, BYTE nChorusSend, BYTE nFilterQ,
|
|
DWORD dwStartAddress, DWORD dwLoopStart, DWORD dwLoopEnd)
|
|
{
|
|
/* first the channel must be silent and idle */
|
|
AWEPortW(DCYSUSV + nChannel, 0x0080);
|
|
AWEPortDW(VTFT + nChannel, 0x00000000L);
|
|
AWEPortDW(CVCF + nChannel, 0x00000000L);
|
|
AWEPortDW(PTRX + nChannel, 0x00000000L);
|
|
AWEPortDW(CPF + nChannel, 0x00000000L);
|
|
|
|
/* now set the envelope engine parameters */
|
|
AWEPortW(ENVVOL + nChannel, 0x8000);
|
|
AWEPortW(ENVVAL + nChannel, 0x8000);
|
|
AWEPortW(DCYSUS + nChannel, 0x7F7F);
|
|
AWEPortW(ATKHLDV + nChannel, 0x7F7F);
|
|
AWEPortW(LFO1VAL + nChannel, 0x8000);
|
|
AWEPortW(ATKHLD + nChannel, 0x7F7F);
|
|
AWEPortW(LFO2VAL + nChannel, 0x8000);
|
|
AWEPortW(IP + nChannel, wPitch);
|
|
AWEPortW(IFATN + nChannel, 0xFF00 | nVolume);
|
|
AWEPortW(PEFE + nChannel, 0x0000);
|
|
AWEPortW(FMMOD + nChannel, 0x0000);
|
|
AWEPortW(TREMFRQ + nChannel, 0x0010);
|
|
AWEPortW(FM2FRQ2 + nChannel, 0x0010);
|
|
|
|
/* decrease memory addresses (due to interpolator offset) */
|
|
dwStartAddress--;
|
|
dwLoopStart--;
|
|
dwLoopEnd--;
|
|
|
|
/* set the loop start and loop end addresses */
|
|
AWEPortDW(PSST + nChannel, ((DWORD) nPanning << 24) | dwLoopStart);
|
|
AWEPortDW(CSL + nChannel, ((DWORD) nChorusSend << 24) | dwLoopEnd);
|
|
|
|
/* set filter Q of channel and audio start address */
|
|
AWEPortDW(CCCA + nChannel, ((DWORD) nFilterQ << 28) | dwStartAddress);
|
|
}
|
|
|
|
static VOID AWEStartNote(BYTE nChannel)
|
|
{
|
|
/* start the note (do it all as close to simultaneously as possible) */
|
|
AWEPortDW(VTFT + nChannel, 0x0000FFFFL);
|
|
AWEPortDW(CVCF + nChannel, 0x0000FFFFL);
|
|
AWEPortW(DCYSUSV + nChannel, 0x7F7F);
|
|
AWEPortDW(PTRX + nChannel, 0x40000000L | (REVERB << 8));
|
|
AWEPortDW(CPF + nChannel, 0x40000000L);
|
|
}
|
|
|
|
static VOID AWEStopNote(BYTE nChannel)
|
|
{
|
|
/* turn off the envolope engine and take the volume to zero */
|
|
AWEPortW(DCYSUSV + nChannel, 0x0080);
|
|
AWEPortDW(VTFT + nChannel, 0x0000FFFFL);
|
|
AWEPortDW(CVCF + nChannel, 0x0000FFFFL);
|
|
}
|
|
|
|
static VOID AWEReleaseNote(BYTE nChannel, BYTE nReleaseRate)
|
|
{
|
|
/* enter the envelope in release mode */
|
|
AWEPortW(DCYSUSV + nChannel, 0x8000 | nReleaseRate);
|
|
}
|
|
|
|
static VOID AWESetNoteOffset(BYTE nChannel, DWORD dwStartAddress, BYTE nFilterQ)
|
|
{
|
|
/* set filter Q of channel and audio start address */
|
|
AWEPortDW(CCCA + nChannel, ((DWORD) nFilterQ << 28) | dwStartAddress);
|
|
}
|
|
|
|
static DWORD AWEGetNoteOffset(BYTE nChannel)
|
|
{
|
|
DWORD dwCCCA;
|
|
dwCCCA = AWEPortRDW(CCCA + nChannel);
|
|
return (dwCCCA & 0x00FFFFFFL);
|
|
}
|
|
|
|
static VOID AWESetNotePitch(BYTE nChannel, WORD wPitch)
|
|
{
|
|
/* change the initial pitch register on the fly */
|
|
AWEPortW(IP + nChannel, wPitch);
|
|
}
|
|
|
|
static VOID AWESetNoteVolume(BYTE nChannel, BYTE nVolume)
|
|
{
|
|
/* change the volume bit field of IFATN and set filter cutoff to 8 kHz */
|
|
AWEPortW(IFATN + nChannel, 0xFF00 | nVolume);
|
|
}
|
|
|
|
static VOID AWESetNotePanning(BYTE nChannel, BYTE nPanning, DWORD dwLoopStart)
|
|
{
|
|
/* change the voice panning position and loop start address */
|
|
AWEPortDW(PSST + nChannel, ((DWORD) nPanning << 24) | dwLoopStart);
|
|
}
|
|
|
|
static WORD AWEGetPitchShift(DWORD dwFrequency)
|
|
{
|
|
UINT n, nPitch;
|
|
|
|
/* convert frequency to logarithmic pitch shift */
|
|
|
|
if (dwFrequency <= (44100 >> 14))
|
|
return 0x0000;
|
|
if (dwFrequency >= (44100 << 2))
|
|
return 0xFFFF;
|
|
|
|
dwFrequency <<= 14;
|
|
dwFrequency /= 44100;
|
|
|
|
nPitch = 0x0000;
|
|
for (n = 16; n--; ) {
|
|
if (dwFrequency >= (1 << n)) {
|
|
nPitch = n << 12;
|
|
dwFrequency <<= 14;
|
|
dwFrequency >>= n;
|
|
break;
|
|
}
|
|
}
|
|
for (n = 12; n--; ) {
|
|
dwFrequency = (dwFrequency * dwFrequency) >> 14;
|
|
if (dwFrequency >= (2 << 14)) {
|
|
nPitch += 1 << n;
|
|
dwFrequency >>= 1;
|
|
}
|
|
}
|
|
return (WORD) nPitch;
|
|
}
|
|
|
|
|
|
/*
|
|
* Sound Blaster AWE32 Sound Memory Manager
|
|
*/
|
|
static VOID AWEPokeDW(DWORD dwAddress, DWORD dwValue)
|
|
{
|
|
AWEUploadData(AWE32.nVoices, dwAddress, (LPWORD) &dwValue, 2);
|
|
}
|
|
|
|
static DWORD AWEPeekDW(DWORD dwAddress)
|
|
{
|
|
DWORD dwValue;
|
|
AWEDownloadData(AWE32.nVoices, dwAddress, (LPWORD) &dwValue, 2);
|
|
return dwValue;
|
|
}
|
|
|
|
static BOOL AWEInitMemory(VOID)
|
|
{
|
|
DWORD dwMemorySize;
|
|
|
|
/* get amount of AWE32 memory size (16-bit words) */
|
|
if ((dwMemorySize = AWEDetectMemory()) <= 2*NODE_HEADER_SIZE)
|
|
return TRUE;
|
|
|
|
/* set up heap memory blocks */
|
|
AWE32.dwTopOfMemory = 0x200000L + dwMemorySize;
|
|
AWEPokeDW(0x200000L + 0, 0x200000L + NODE_HEADER_SIZE);
|
|
AWEPokeDW(0x200000L + 2, NODE_HEADER_SIZE);
|
|
AWEPokeDW(0x200000L + NODE_HEADER_SIZE + 0, 0x000000L);
|
|
AWEPokeDW(0x200000L + NODE_HEADER_SIZE + 2, dwMemorySize - NODE_HEADER_SIZE);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static DWORD AWEMemAvail(VOID)
|
|
{
|
|
DWORD dwNode, dwPrevNode, dwSize;
|
|
|
|
dwSize = 0;
|
|
dwPrevNode = 0x200000L;
|
|
while ((dwNode = AWEPeekDW(dwPrevNode + 0)) != 0x000000L) {
|
|
dwSize += AWEPeekDW(dwNode + 2);
|
|
dwPrevNode = dwNode;
|
|
}
|
|
|
|
/* return number of 8-bit bytes availables */
|
|
return dwSize << 1;
|
|
}
|
|
|
|
static DWORD AWEMemAlloc(DWORD dwSize)
|
|
{
|
|
DWORD dwNode, dwPrevNode, dwNodeSize;
|
|
|
|
if ((dwSize += NODE_HEADER_SIZE) != 0L) {
|
|
dwSize = (dwSize + NODE_HEADER_SIZE - 1) & -NODE_HEADER_SIZE;
|
|
dwPrevNode = 0x200000L;
|
|
while ((dwNode = AWEPeekDW(dwPrevNode + 0)) != 0L) {
|
|
if ((dwNodeSize = AWEPeekDW(dwNode + 2)) >= dwSize) {
|
|
if (dwNodeSize == dwSize) {
|
|
AWEPokeDW(dwPrevNode + 0, AWEPeekDW(dwNode + 0));
|
|
}
|
|
else {
|
|
dwNodeSize -= dwSize;
|
|
AWEPokeDW(dwNode + 2, dwNodeSize);
|
|
dwNode += dwNodeSize;
|
|
AWEPokeDW(dwNode + 2, dwSize);
|
|
}
|
|
AWEPokeDW(dwNode + 0, ~dwSize);
|
|
return (dwNode + NODE_HEADER_SIZE);
|
|
}
|
|
dwPrevNode = dwNode;
|
|
}
|
|
}
|
|
return 0L;
|
|
}
|
|
|
|
static VOID AWEMemFree(DWORD dwAddr)
|
|
{
|
|
DWORD dwNode, dwNextNode, dwNodeSize;
|
|
|
|
if ((dwAddr -= NODE_HEADER_SIZE) < AWE32.dwTopOfMemory &&
|
|
AWEPeekDW(dwAddr + 0) == ~AWEPeekDW(dwAddr + 2)) {
|
|
dwNode = 0x200000L;
|
|
while (dwNode != AWE32.dwTopOfMemory) {
|
|
if ((dwNextNode = AWEPeekDW(dwNode + 0)) == 0x000000L)
|
|
dwNextNode = AWE32.dwTopOfMemory;
|
|
if (dwAddr > dwNode && dwAddr < dwNextNode)
|
|
break;
|
|
dwNode = dwNextNode;
|
|
}
|
|
if (dwNode != AWE32.dwTopOfMemory) {
|
|
if (dwNextNode == AWE32.dwTopOfMemory)
|
|
dwNextNode = 0x00000L;
|
|
dwNodeSize = AWEPeekDW(dwAddr + 2);
|
|
if (dwAddr + dwNodeSize == dwNextNode) {
|
|
AWEPokeDW(dwAddr + 2, dwNodeSize + AWEPeekDW(dwNextNode + 2));
|
|
AWEPokeDW(dwAddr + 0, AWEPeekDW(dwNextNode + 0));
|
|
}
|
|
else {
|
|
AWEPokeDW(dwAddr + 0, dwNextNode);
|
|
}
|
|
dwNodeSize = AWEPeekDW(dwNode + 2);
|
|
if (dwNode + dwNodeSize == dwAddr) {
|
|
AWEPokeDW(dwNode + 2, dwNodeSize + AWEPeekDW(dwAddr + 2));
|
|
AWEPokeDW(dwNode + 0, AWEPeekDW(dwAddr + 0));
|
|
}
|
|
else {
|
|
AWEPokeDW(dwNode + 0, dwAddr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Sound Blaster AWE32 DSP-based timer routines
|
|
*/
|
|
static VOID SB16PortB(BYTE bData)
|
|
{
|
|
UINT nTimeOut;
|
|
|
|
for (nTimeOut = 0; nTimeOut < 0x8000; nTimeOut++)
|
|
if (!(INB(AWE32.wPort + 0x0C) & 0x80))
|
|
break;
|
|
OUTB(AWE32.wPort + 0x0C, bData);
|
|
}
|
|
|
|
static UINT SB16Reset(VOID)
|
|
{
|
|
UINT n, nTimeOut;
|
|
|
|
for (n = 0; n < 8; n++) {
|
|
/* first reset the DSP processor */
|
|
OUTB(AWE32.wPort + 0x06, 0x01);
|
|
for (nTimeOut = 0; nTimeOut < 8; nTimeOut++)
|
|
INB(AWE32.wPort + 0x06);
|
|
OUTB(AWE32.wPort + 0x06, 0x00);
|
|
|
|
/* now wait to get back the DSP acknowledge */
|
|
for (nTimeOut = 0; nTimeOut < 0x8000; nTimeOut++)
|
|
if (INB(AWE32.wPort + 0x0E) & 0x80)
|
|
break;
|
|
if (INB(AWE32.wPort + 0x0A) == 0xAA)
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
return AUDIO_ERROR_NODEVICE;
|
|
}
|
|
|
|
static VOID AWEInterruptHandler(VOID)
|
|
{
|
|
BYTE nSaveIndex;
|
|
|
|
if (AWE32.lpfnAudioTimer != NULL) {
|
|
nSaveIndex = nEmuIndex;
|
|
AWE32.lpfnAudioTimer();
|
|
nEmuIndex = nSaveIndex;
|
|
OUTW(AWE32.wPointerPort, nEmuIndex);
|
|
}
|
|
INB(AWE32.wPort + 0x0E);
|
|
}
|
|
|
|
static UINT AWEInitTimer(VOID)
|
|
{
|
|
LPBYTE lpBuffer;
|
|
|
|
/* reset the DSP processor */
|
|
if (SB16Reset())
|
|
return AUDIO_ERROR_NODEVICE;
|
|
|
|
/* turn off the DSP speaker */
|
|
SB16PortB(0xD3);
|
|
|
|
/* set output sample rate to 44100 Hz */
|
|
SB16PortB(0x41);
|
|
SB16PortB(HIBYTE(44100));
|
|
SB16PortB(LOBYTE(44100));
|
|
|
|
/* allocate and clear small DMA buffer */
|
|
if (DosAllocChannel(AWE32.nDmaChannel, 4))
|
|
return AUDIO_ERROR_NOMEMORY;
|
|
|
|
if ((lpBuffer = DosLockChannel(AWE32.nDmaChannel)) != NULL) {
|
|
memset(lpBuffer, 0x80, 4);
|
|
DosUnlockChannel(AWE32.nDmaChannel);
|
|
}
|
|
|
|
/* setup the DMA channel parameters */
|
|
DosSetupChannel(AWE32.nDmaChannel, DMA_WRITE | DMA_AUTOINIT, 0);
|
|
|
|
/* setup our IRQ interrupt handler */
|
|
DosSetVectorHandler(AWE32.nIrqLine, AWEInterruptHandler);
|
|
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
|
|
static VOID AWEDoneTimer(VOID)
|
|
{
|
|
/* reset the DSP processor */
|
|
SB16Reset();
|
|
|
|
/* turn off DSP speaker */
|
|
SB16PortB(0xD3);
|
|
|
|
/* restore the interrupt handler */
|
|
DosSetVectorHandler(AWE32.nIrqLine, NULL);
|
|
|
|
/* reset and release DMA buffer */
|
|
DosDisableChannel(AWE32.nDmaChannel);
|
|
DosFreeChannel(AWE32.nDmaChannel);
|
|
}
|
|
|
|
static VOID AWESetTimerRate(UINT nTicks)
|
|
{
|
|
/* start 8-bit mono high-speed autoinit DMA transfer */
|
|
SB16PortB(0xC6);
|
|
SB16PortB(0x00);
|
|
nTicks--;
|
|
SB16PortB(LOBYTE(nTicks));
|
|
SB16PortB(HIBYTE(nTicks));
|
|
}
|
|
|
|
|
|
/*
|
|
* Sound Blaster AWE32 driver API interface
|
|
*/
|
|
static UINT AIAPI GetAudioCaps(LPAUDIOCAPS lpCaps)
|
|
{
|
|
static AUDIOCAPS Caps = {
|
|
AUDIO_PRODUCT_AWE32, "Sound Blaster AWE32",
|
|
AUDIO_FORMAT_4S16,
|
|
};
|
|
memcpy(lpCaps, &Caps, sizeof(AUDIOCAPS));
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
|
|
static UINT AIAPI PingAudio(VOID)
|
|
{
|
|
LPSTR lpszBlaster;
|
|
UINT n;
|
|
|
|
if ((lpszBlaster = DosGetEnvironment("BLASTER")) != NULL) {
|
|
AWE32.wPort = 0x220;
|
|
AWE32.nIrqLine = 5;
|
|
AWE32.nDmaChannel = 1;
|
|
AWE32.wBasePort = 0x620;
|
|
n = DosParseString(lpszBlaster, TOKEN_CHAR);
|
|
while (n != 0) {
|
|
switch (n) {
|
|
case 'A':
|
|
case 'a':
|
|
AWE32.wPort = DosParseString(NULL, TOKEN_HEX);
|
|
break;
|
|
case 'I':
|
|
case 'i':
|
|
AWE32.nIrqLine = DosParseString(NULL, TOKEN_DEC);
|
|
break;
|
|
case 'D':
|
|
case 'd':
|
|
AWE32.nDmaChannel = DosParseString(NULL, TOKEN_DEC);
|
|
break;
|
|
case 'E':
|
|
case 'e':
|
|
AWE32.wBasePort = DosParseString(NULL, TOKEN_HEX);
|
|
break;
|
|
}
|
|
n = DosParseString(NULL, TOKEN_CHAR);
|
|
}
|
|
if (AWEProbe(AWE32.wBasePort) && AWEDetectMemory())
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
return AUDIO_ERROR_NODEVICE;
|
|
}
|
|
|
|
static UINT AIAPI OpenAudio(LPAUDIOINFO lpInfo)
|
|
{
|
|
/* initialize the AWE32 driver for playback */
|
|
memset(&AWE32, 0, sizeof(AWE32));
|
|
if (PingAudio())
|
|
return AUDIO_ERROR_NODEVICE;
|
|
if (AWEInitialize())
|
|
return AUDIO_ERROR_NODEVICE;
|
|
if (AWEInitMemory())
|
|
return AUDIO_ERROR_NODRAMMEMORY;
|
|
if (AWEInitTimer())
|
|
return AUDIO_ERROR_NODEVICE;
|
|
|
|
/* set caller playback format fields */
|
|
lpInfo->wFormat = AUDIO_FORMAT_16BITS | AUDIO_FORMAT_STEREO;
|
|
lpInfo->nSampleRate = 44100;
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
|
|
static UINT AIAPI CloseAudio(VOID)
|
|
{
|
|
UINT nVoice;
|
|
|
|
/* reset and release AWE32 timer resources */
|
|
AWEDoneTimer();
|
|
|
|
/* reset EMU8000 synthesizer and exit */
|
|
for (nVoice = 0; nVoice < 31; nVoice++)
|
|
AWEStopNote(nVoice);
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
|
|
static UINT AIAPI UpdateAudio(VOID)
|
|
{
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
|
|
static UINT AIAPI SetAudioMixerValue(UINT nChannel, UINT nValue)
|
|
{
|
|
if (nChannel != AUDIO_MIXER_MASTER_VOLUME &&
|
|
nChannel != AUDIO_MIXER_TREBLE &&
|
|
nChannel != AUDIO_MIXER_BASS &&
|
|
nChannel != AUDIO_MIXER_CHORUS &&
|
|
nChannel != AUDIO_MIXER_REVERB)
|
|
return AUDIO_ERROR_NOTSUPPORTED;
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
|
|
static UINT AIAPI OpenVoices(UINT nVoices)
|
|
{
|
|
if (nVoices < AUDIO_MAX_VOICES && nVoices < 31) {
|
|
AWE32.nVoices = nVoices;
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
return AUDIO_ERROR_INVALPARAM;
|
|
}
|
|
|
|
static UINT AIAPI CloseVoices(VOID)
|
|
{
|
|
UINT nVoice;
|
|
for (AWE32.nVoices = nVoice = 0; nVoice < 31; nVoice++)
|
|
AWEStopNote(nVoice);
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
|
|
static UINT AIAPI SetAudioTimerProc(LPFNAUDIOTIMER lpfnAudioTimer)
|
|
{
|
|
AWE32.lpfnAudioTimer = lpfnAudioTimer;
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
|
|
static UINT AIAPI SetAudioTimerRate(UINT nBPM)
|
|
{
|
|
UINT nTicks;
|
|
|
|
if (nBPM >= 0x20 && nBPM <= 0xFF) {
|
|
nTicks = (44100 * 5) / (2 * nBPM);
|
|
AWESetTimerRate(nTicks);
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
return AUDIO_ERROR_INVALPARAM;
|
|
}
|
|
|
|
static LONG AIAPI GetAudioDataAvail(VOID)
|
|
{
|
|
return AWEMemAvail();
|
|
}
|
|
|
|
static UINT AIAPI CreateAudioData(LPAUDIOWAVE lpWave)
|
|
{
|
|
if (lpWave != NULL) {
|
|
if (lpWave->wFormat & AUDIO_FORMAT_16BITS)
|
|
lpWave->dwHandle = AWEMemAlloc((lpWave->dwLength >> 1) + CLICKBUFSIZE);
|
|
else
|
|
lpWave->dwHandle = AWEMemAlloc(lpWave->dwLength + CLICKBUFSIZE);
|
|
return lpWave->dwHandle ? AUDIO_ERROR_NONE : AUDIO_ERROR_NODRAMMEMORY;
|
|
}
|
|
return AUDIO_ERROR_INVALHANDLE;
|
|
}
|
|
|
|
static UINT AIAPI DestroyAudioData(LPAUDIOWAVE lpWave)
|
|
{
|
|
if (lpWave != NULL && lpWave->dwHandle != 0) {
|
|
AWEMemFree(lpWave->dwHandle);
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
return AUDIO_ERROR_INVALHANDLE;
|
|
}
|
|
|
|
static UINT AIAPI WriteAudioData(LPAUDIOWAVE lpWave,
|
|
DWORD dwOffset, UINT nCount)
|
|
{
|
|
static WORD aClickBuffer[CLICKBUFSIZE];
|
|
|
|
if (lpWave != NULL && lpWave->dwHandle != 0) {
|
|
if (dwOffset + nCount <= lpWave->dwLength) {
|
|
if (lpWave->wFormat & AUDIO_FORMAT_16BITS) {
|
|
AWEUploadData(AWE32.nVoices, lpWave->dwHandle + (dwOffset >> 1),
|
|
(LPWORD) (lpWave->lpData + dwOffset), nCount >> 1);
|
|
}
|
|
else {
|
|
AWEUploadData8(AWE32.nVoices, lpWave->dwHandle + dwOffset,
|
|
lpWave->lpData + dwOffset, nCount);
|
|
}
|
|
|
|
if (lpWave->wFormat & (AUDIO_FORMAT_LOOP | AUDIO_FORMAT_BIDILOOP)) {
|
|
if (dwOffset <= lpWave->dwLoopEnd &&
|
|
dwOffset + nCount >= lpWave->dwLoopEnd) {
|
|
if (lpWave->wFormat & AUDIO_FORMAT_16BITS) {
|
|
AWEDownloadData(AWE32.nVoices,
|
|
lpWave->dwHandle + (lpWave->dwLoopStart >> 1),
|
|
aClickBuffer, CLICKBUFSIZE);
|
|
AWEUploadData(AWE32.nVoices,
|
|
lpWave->dwHandle + (lpWave->dwLoopEnd >> 1),
|
|
aClickBuffer, CLICKBUFSIZE);
|
|
}
|
|
else {
|
|
AWEDownloadData(AWE32.nVoices,
|
|
lpWave->dwHandle + lpWave->dwLoopStart,
|
|
aClickBuffer, CLICKBUFSIZE);
|
|
AWEUploadData(AWE32.nVoices,
|
|
lpWave->dwHandle + lpWave->dwLoopEnd,
|
|
aClickBuffer, CLICKBUFSIZE);
|
|
}
|
|
}
|
|
}
|
|
else if (dwOffset + nCount >= lpWave->dwLength) {
|
|
memset(aClickBuffer, 0, sizeof(aClickBuffer));
|
|
if (lpWave->wFormat & AUDIO_FORMAT_16BITS) {
|
|
AWEUploadData(AWE32.nVoices,
|
|
lpWave->dwHandle + (lpWave->dwLength >> 1),
|
|
aClickBuffer, CLICKBUFSIZE);
|
|
}
|
|
else {
|
|
AWEUploadData(AWE32.nVoices,
|
|
lpWave->dwHandle + lpWave->dwLength,
|
|
aClickBuffer, CLICKBUFSIZE);
|
|
}
|
|
}
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
return AUDIO_ERROR_INVALPARAM;
|
|
}
|
|
return AUDIO_ERROR_INVALHANDLE;
|
|
}
|
|
|
|
static UINT AIAPI PrimeVoice(UINT nVoice, LPAUDIOWAVE lpWave)
|
|
{
|
|
DWORD dwLength, dwLoopStart, dwLoopEnd;
|
|
|
|
if (nVoice < 31 && lpWave != NULL && lpWave->dwHandle != 0) {
|
|
AWE32.aStartAddress[nVoice] =
|
|
AWE32.aCurrentAddress[nVoice] = lpWave->dwHandle;
|
|
dwLength = lpWave->dwLength;
|
|
dwLoopStart = lpWave->dwLoopStart;
|
|
dwLoopEnd = lpWave->dwLoopEnd;
|
|
if (lpWave->wFormat & AUDIO_FORMAT_16BITS) {
|
|
dwLength >>= 1;
|
|
dwLoopStart >>= 1;
|
|
dwLoopEnd >>= 1;
|
|
}
|
|
if (lpWave->wFormat & (AUDIO_FORMAT_LOOP | AUDIO_FORMAT_BIDILOOP)) {
|
|
AWE32.aLoopStart[nVoice] = dwLoopStart;
|
|
AWE32.aLoopEnd[nVoice] = dwLoopEnd;
|
|
}
|
|
else {
|
|
AWE32.aLoopStart[nVoice] = dwLength + 2;
|
|
AWE32.aLoopEnd[nVoice] = dwLength + CLICKBUFSIZE - 2;
|
|
}
|
|
|
|
AWEPrimeNote(nVoice,
|
|
AWEGetPitchShift(AWE32.aFrequencyTable[nVoice]),
|
|
aVolumeTable[AWE32.aVolumeTable[nVoice]],
|
|
0xFF - AWE32.aPanningTable[nVoice],
|
|
CHORUS, FILTERQ,
|
|
AWE32.aStartAddress[nVoice],
|
|
AWE32.aStartAddress[nVoice] + AWE32.aLoopStart[nVoice],
|
|
AWE32.aStartAddress[nVoice] + AWE32.aLoopEnd[nVoice]);
|
|
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
return AUDIO_ERROR_INVALHANDLE;
|
|
}
|
|
|
|
static UINT AIAPI StartVoice(UINT nVoice)
|
|
{
|
|
if (nVoice < 31) {
|
|
if (AWE32.aCurrentAddress[nVoice]) {
|
|
AWESetNoteOffset(nVoice, AWE32.aCurrentAddress[nVoice], FILTERQ);
|
|
AWEStartNote(nVoice);
|
|
AWE32.aCurrentAddress[nVoice] = 0;
|
|
}
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
return AUDIO_ERROR_INVALHANDLE;
|
|
}
|
|
|
|
static UINT AIAPI StopVoice(UINT nVoice)
|
|
{
|
|
if (nVoice < 31) {
|
|
/* release the note at 240 usec/dB (faster rate) */
|
|
if (!AWE32.aCurrentAddress[nVoice]) {
|
|
AWE32.aCurrentAddress[nVoice] = AWEGetNoteOffset(nVoice);
|
|
AWEReleaseNote(nVoice, 0x7F);
|
|
}
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
return AUDIO_ERROR_INVALHANDLE;
|
|
}
|
|
|
|
static UINT AIAPI SetVoicePosition(UINT nVoice, LONG dwPosition)
|
|
{
|
|
if (nVoice < 31) {
|
|
if (dwPosition >= 0 && dwPosition <= AWE32.aLoopEnd[nVoice]) {
|
|
AWESetNoteOffset(nVoice,
|
|
AWE32.aStartAddress[nVoice] + dwPosition, 0);
|
|
if (AWE32.aCurrentAddress[nVoice])
|
|
AWE32.aCurrentAddress[nVoice] = AWEGetNoteOffset(nVoice);
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
return AUDIO_ERROR_INVALPARAM;
|
|
}
|
|
return AUDIO_ERROR_INVALHANDLE;
|
|
}
|
|
|
|
static UINT AIAPI SetVoiceFrequency(UINT nVoice, LONG dwFrequency)
|
|
{
|
|
if (nVoice < 31) {
|
|
if (dwFrequency >= AUDIO_MIN_FREQUENCY &&
|
|
dwFrequency <= AUDIO_MAX_FREQUENCY) {
|
|
AWE32.aFrequencyTable[nVoice] = dwFrequency;
|
|
AWESetNotePitch(nVoice, AWEGetPitchShift(dwFrequency));
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
return AUDIO_ERROR_INVALPARAM;
|
|
}
|
|
return AUDIO_ERROR_INVALHANDLE;
|
|
}
|
|
|
|
static UINT AIAPI SetVoiceVolume(UINT nVoice, UINT nVolume)
|
|
{
|
|
if (nVoice < 31) {
|
|
if (nVolume <= AUDIO_MAX_VOLUME) {
|
|
AWE32.aVolumeTable[nVoice] = nVolume;
|
|
AWESetNoteVolume(nVoice, aVolumeTable[nVolume]);
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
return AUDIO_ERROR_INVALPARAM;
|
|
}
|
|
return AUDIO_ERROR_INVALHANDLE;
|
|
}
|
|
|
|
static UINT AIAPI SetVoicePanning(UINT nVoice, UINT nPanning)
|
|
{
|
|
if (nVoice < 31) {
|
|
if (nPanning <= AUDIO_MAX_PANNING) {
|
|
AWE32.aPanningTable[nVoice] = nPanning;
|
|
AWESetNotePanning(nVoice, 0xFF - nPanning,
|
|
AWE32.aStartAddress[nVoice] + AWE32.aLoopStart[nVoice]);
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
return AUDIO_ERROR_INVALPARAM;
|
|
}
|
|
return AUDIO_ERROR_INVALHANDLE;
|
|
}
|
|
|
|
static UINT AIAPI GetVoicePosition(UINT nVoice, LPLONG lpdwPosition)
|
|
{
|
|
if (nVoice < 31) {
|
|
if (lpdwPosition != NULL) {
|
|
*lpdwPosition = AWEGetNoteOffset(nVoice) -
|
|
AWE32.aStartAddress[nVoice];
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
return AUDIO_ERROR_INVALPARAM;
|
|
}
|
|
return AUDIO_ERROR_INVALHANDLE;
|
|
}
|
|
|
|
static UINT AIAPI GetVoiceFrequency(UINT nVoice, LPLONG lpdwFrequency)
|
|
{
|
|
if (nVoice < 31) {
|
|
if (lpdwFrequency != NULL) {
|
|
*lpdwFrequency = AWE32.aFrequencyTable[nVoice];
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
return AUDIO_ERROR_INVALPARAM;
|
|
}
|
|
return AUDIO_ERROR_INVALHANDLE;
|
|
}
|
|
|
|
static UINT AIAPI GetVoiceVolume(UINT nVoice, LPUINT lpnVolume)
|
|
{
|
|
if (nVoice < 31) {
|
|
if (lpnVolume != NULL) {
|
|
*lpnVolume = AWE32.aVolumeTable[nVoice];
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
return AUDIO_ERROR_INVALPARAM;
|
|
}
|
|
return AUDIO_ERROR_INVALHANDLE;
|
|
}
|
|
|
|
static UINT AIAPI GetVoicePanning(UINT nVoice, LPUINT lpnPanning)
|
|
{
|
|
if (nVoice < 31) {
|
|
if (lpnPanning != NULL) {
|
|
*lpnPanning = AWE32.aPanningTable[nVoice];
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
return AUDIO_ERROR_INVALPARAM;
|
|
}
|
|
return AUDIO_ERROR_INVALHANDLE;
|
|
}
|
|
|
|
static UINT AIAPI GetVoiceStatus(UINT nVoice, LPBOOL lpbStatus)
|
|
{
|
|
if (nVoice < 31) {
|
|
if (lpbStatus != NULL) {
|
|
*lpbStatus = (AWE32.aLoopStart[nVoice] + CLICKBUFSIZE - 4 == AWE32.aLoopEnd[nVoice]) &&
|
|
(AWEGetNoteOffset(nVoice) >=
|
|
AWE32.aStartAddress[nVoice] + AWE32.aLoopStart[nVoice]);
|
|
return AUDIO_ERROR_NONE;
|
|
}
|
|
return AUDIO_ERROR_INVALPARAM;
|
|
}
|
|
return AUDIO_ERROR_INVALHANDLE;
|
|
}
|
|
|
|
|
|
/*
|
|
* Sound Blaster AWE32 driver public interface
|
|
*/
|
|
AUDIOSYNTHDRIVER SoundBlaster32SynthDriver =
|
|
{
|
|
GetAudioCaps, PingAudio, OpenAudio, CloseAudio,
|
|
UpdateAudio, OpenVoices, CloseVoices,
|
|
SetAudioTimerProc, SetAudioTimerRate, SetAudioMixerValue,
|
|
GetAudioDataAvail, CreateAudioData, DestroyAudioData,
|
|
WriteAudioData, PrimeVoice, StartVoice, StopVoice,
|
|
SetVoicePosition, SetVoiceFrequency, SetVoiceVolume,
|
|
SetVoicePanning, GetVoicePosition, GetVoiceFrequency,
|
|
GetVoiceVolume, GetVoicePanning, GetVoiceStatus
|
|
};
|
|
|
|
AUDIODRIVER SoundBlaster32Driver =
|
|
{
|
|
NULL, &SoundBlaster32SynthDriver
|
|
};
|