TURTLE BEACH MULTISOUND PINNACLE/FIJI SOFTWARE DEVELOPER'S KIT 4/23/98 Introduction This manual is provided as a technical reference for DOS based development using Turtle Beach Systems MultiSound Pinnacle or Fiji audio cards. When we reference the MultiSound in this document, we are referring to this latest generation of cards. It gives the developer the capability to access all the features of the MultiSound card through the DOS environment. It provides the assembled code for the DSP, and fully explains how to interact with that code. It provides descriptions of all the registers accessible from the HOST, and how to use them. This is accomplished by first going over the basics of how the card operates. Then the reader is walked through examples to show how the HOST is used to control the various features of the card. Note: This SDK is provided to the public as is. Turtle Beach will not provide customer support for this SDK, ie. do not expect us to answer detailed technical questions regarding the content of this document. If you find any of the information to be incorrect or have any suggestions regarding possible future corrections or enhancements please send a note to mailto:sdk@tbeach.com. The zip file of the developer's kit contains the following items: 1. The source for the examples and the DSP code for the 56002 2. This Manual Overview This introduction provides useful information which you should review before attempting to use this manual. This overview covers the following topics: + What you should know before starting + What you need to have + What is contained in this manual + Notes on the sample applications What you should know before starting To start using this tool kit you will need to have an understanding of the following: + Advanced knowledge of the C programming language This manual assumes that the reader is experienced with programming in C. This tool kit uses extensive port writing, IRQ handling, structures, and pointers. If you are not confident in your C, we recommend that you review it before starting. + Knowledge of digital audio and MIDI This manual assumes a certain amount of knowledge on these topics. Although the discussion will not get too in depth, we make no attempt to explain MIDI and digital concepts in this manual. For more information, please refer to the many reference works publicly available. What you need to have To effectively use this tool kit to create applications for the MultiSound card you will need the following items: + An IBM-PC Compatible MultiSound is designed for the ISA bus standard on IBM-PC compatibles. You will need one of these to use it. You will need to use the card on a 486/66 or higher. + The Pinnacle or Fiji Audio Card We assume you have one if you obtained this kit. + A C Compiler All the sample source code was written to be compiled using Microsoft C++ 1.5. Although we recommend using this compiler, any adept C compiler will work with modest modifications to the code. Please note that the output is a DOS executable. This may need to be set in your compiler options. + A line level audio source To go through the tutorial and test the code, you will need a line level audio source (tape or CD player, mixer output, etc.). This will allow you to have something to check the recording with, as well as check the AUX input. + A MIDI Keyboard To fully explore the capabilities of the MultiSound, it is suggested that the user have an external MIDI keyboard. A MIDI cable will also be required. What is contained in this manual This manual is broken up into five sections. + Introduction The introduction provides an overview of the tool kit. It tells the user what he will need to use the tool kit, as well as what knowledge he can expect to gain from it. + CHAPTER 1 - System Overview This chapter goes over the hardware and software basics necessary to understand programming the MultiSound card. It also explains the data formats used in the demo code. + CHAPTER 2 - Initializing the Hardware This chapter covers how to initialize the MultiSound. This will be used in every application you write for the card. + CHAPTER 3 - Using the MultiSound Queues This chapter gives the reader an overview of the event driven queue based software architecture used for programming the MultiSound. + CHAPTER 4 - Using Digital Audio This chapter covers digital audio on the MultiSound. It takes the reader through examples for both playing and recording. + CHAPTER 5 - Using MIDI This chapter covers MIDI usage on the MultiSound. Covered topics include receiving MIDI data, sending MIDI data, and directing MIDI data. Notes on the sample applications All the source for the tutorial examples are contained in the zip file of this kit. The file MCONTROL.C is linked with every sample application. This file contains a series of functions and structures which are used to access the MultiSound. It should be remembered that these source files show only one approach to interfacing with the card. As with any programming task there is no absolute right. The important thing to understand when going through this manual is the low level interaction between the DSP and the HOST (IBM-PC compatible platform). When doing your own development you can use the functions contained in MCONTROL.C as we do in the examples. If this format does not suit your needs, there is enough information in this manual for you to be able to write your own low level code for the interaction with the hardware. Files included: MCONTROL.C A library of functions written in Microsoft C which allow easy access to the features of the MultiSound. MCONTROL.H Definitions and structures specific to the functional implementation in MCONTROL.C. PCONTROL.H Definitions and structures to control shared memory of the DSP. MSND_DSP.H Definitions and constants specific to the MultiSound hardware. DSPCODE.OBJ The assembled code for the DSP. This must be linked with the application and uploaded to the DSP. DSPCODE.ASM The PC-assembler compileable source for DSPCODE.OBJ. PNDSPERM.ASM & The output of the 56002 cross assembler converted to PNDSPINI.ASM assembler files. These are included files in DSPCODE.ASM MAKEFILE \ The Microsoft C MAKEFILE and the PC source C code for a AUXVOL.C simple auxiliary volume adjustment example. MAKEFILE \ A simple record input volume adjustment example. INVOL.C MAKEFILE \ Example code for recording audio to the hard disk. RECORD.C MAKEFILE \ Example code for playing audio from the hard disk. PLAY.C MAKEFILE \ A simple mic in record input volume adjustment example. MICVOL.C MAKEFILE \ Example code demonstrating receiving external MIDI In and MIDIIN.C sending it to the daughtercard header and main synth. Redistribution of Turtle Beach's Software This developer's kit does not provide you with a license to distribute any of the contained software, including, but not limited to DSPCODE.OBJ, MCONTROL.C. Developers wishing to use these in specific applications can do so by requesting and returning the "MultiSound DSP OEM Licensing Agreement". Turtle Beach's policy on redistribution is simple. For a license fee of $1, we allow developers to distribute the above code modules along with their products, provided that MultiSound hardware is included in the target product. Since DSP_CODE.OBJ only works with MultiSound hardware, this is the only logical use of the code. CHAPTER 1 - A System Overview Introduction Hardware Overview + I/O Port Usage + Memory Addressing PC + DSP + Memory Map ( SMA ) Software Overview + Structs/Unions, typedefs, and MACROS used Introduction This chapter deals with the ground floor of development for the MultiSound. It will take you through the hardware, explaining how you will communicate with the card. Then it goes over the registers accessible from the PC. Finally it takes you through the steps of initializing the card, and ends with a short application for adjusting the input volumes. Hardware Overview Pinnacle/Fiji MultiSound is a full length, 16 bit, AT compatible feature card that allows real-time digital signal processing to be performed on a PC. The DSP used by the MultiSound is the Motorola DSP 56002, running with a 45.1584 Mhz clock. In this speed, the DSP performs 22.5792 million instructions per second. The onboard synthesizer (Pinnacle only) is a Kurzweil MA-1 synth. There is also provision for connecting a MIDI daughter card to the audio card. Externally the card has three analog inputs, one analog output, a MIDI port, and a game port. The MultiSound interfaces to the host PC system via three hardware resources: one or two I/O port address blocks, Interrupt, and 32 KB window of shared memory. The location and usage of these resources is covered in the following paragraphs. In addition, the Kurzweil interfaces via a standard MPU-401 compatible interface with a separate I/O address and a separate IRQ. I/O Port Usage This section covers the I/O port usage. It gives the names and locations of the registers. Note that the names given to the registers are defined in MSND_DSP.H using the same syntax. The I/O port address options and settings are covered in the MultiSound User's Guide under 'Installing the Hardware'. The DSP device occupies only 8 consecutive I/O ports which can be located anywhere throughout the ISA address space from 0x100 through 0x3F0 with the base address aligned at a boundary that is a multiple of 0x010. Configuration is possible either through Plug and Play or, if the card is in legacy mode, through configuration registers. These configuration registers occupy three additional consecutive I/O ports and are based on a block of 2 on-board jumpers. The three possible base addresses are 0x250, 0x260, and 0x270. Unlike the previous MultiSound cards, the control registers used for configuration have been replaced by the standard PnP registers or the configuration registers, respectively. The remaining control registers and the information registers have been moved into the DSP host interface. In this document, the I/O base address will be referred to as "xx0", where xx is the first two numbers of your base address setting. So, HP_CVR would be referred to at xx1h, regardless of the base port selected. For example, if your DSP port address is 290h, the HP_CVR would be 291h. Equates for I/O Ports PU = Power-up State, R/W = Read / Write, RO = Read Only, WO = Write Only DSP Host Port Interface Registers: HP_ICR xx0H Interrupt Control Register (R/W) HP_CVR xx1H Command Vector Register (R/W) HP_ISR xx2H Interrupt Status Register (RO) HP_IVR xx3H Interrupt Vector Register (R/W) HP_INFO xx4H Board Information (RO) HP_MEMM xx4H Control Register (WO) HP_DSPR xx4H Control Register (WO) HP_BLKS xx4H Control Register (WO) HP_RXH xx5H Rx Data High (RO) HP_RXM xx6H Rx Data Middle (RO) HP_RXL xx7H Rx Data Low (RO) HP_TXH xx5H Tx Data High (WO) HP_TXM xx6H Tx Data Middle (WO) HP_TXL xx7H Tx Data Low (WO) For more information on the use of these registers consult the DSP56000/1/2 Digital Signal Processor User's Manual from Motorola. Chapter 10 of the Motorola manual is a gold mine of information about the 56000 series host port. DSP Host Port Interface Registers The following section will cover the use of the Host Port registers. The Host Interface(HI) appears to the host processor as 8 bytes of static RAM. The HOST may access the interface asynchronously by using polling or interrupt-based techniques. The separate transmit and receive registers are double buffered to allow efficient data transfers to occur between the DSP CPU and the HOST CPU. The HI appears to the HOST as an eight BYTE memory mapped peripheral device. The registers, listed above, will now be described in more detail. Interrupt Control Register (base+0h) R/W The ICR is an 8 bit read/write register used by the HOST to control the HI interrupts and flags. 7 6 5 4 3 2 1 0 INIT MC1 MC0 RES HF1 HF0 TREQ RREQ INIT This bit is used to initialize the host interface hardware based on the value of the mode control bits. MC1-MC0 - Mode Control These bits will always be set to zero (interrupt mode). The other values are used to enable various DMA modes. RES This bit is reserved. HF[1:0] - Host Flags These bits indicate the status of the host flags in the Host Control Register. These can only be set by the DSP. TREQ - Transmit Interrupt Enable The TREQ bit controls the !HREQ pin for host transmit data transfers. TREQ is used to enable interrupt requests via the !HREQ pin when the transmit data register empty (TXDE) status bit is set in HP_ISR. When TREQ is cleared, these interrupts are disabled. RREQ - Receive Interrupt Enable The RREQ bit controls the !HREQ pin for host receive data transfers. RREQ is used to enable interrupt requests when the receive data register full (RXDF) status bit is set in HP_ISR. When RREQ is cleared, RXDF interrupts are disabled. Command Vector Register (base+1h) R/W HP_CVR is used by the host to cause the DSP to execute one of several possible vectored interrupts. The executable exceptions are listed in MCONTROL.H. 7 6 5 4 3 2 1 0 HC RES HV5 HV4 HV3 HV2 HV1 HV0 HC - Host Command This bit is used by the host to handshake command exception execution. The host sets this bit high to request a host exception. The hardware clears this bit to acknowledge the receipt of the command. HV[5:0] - Host Command Vector The DSP will execute the exception handler defined by number written into these bits. Interrupt Status Register (base+2h) RO The ISR is an 8 bit read only register which is used by the HOST to read the status bits and flags of the HI. 7 6 5 4 3 2 1 0 HREQ DMAS RES HF3 HF2 TRDY TXDE RXDF HREQ The HREQ bit indicates the status of the external host request output pin !HREQ. When this bit is cleared it indicates that no host interrupts are being requested. When this bit is set it indicates that the external HREQ pin is set, and the DSP is currently interrupting the host processor. DMAS - DMA Status This bit indicates the status of the DMA. It will always be set to zero indicating that DMA is disabled. RES This bit is reserved HF[3:2] - Host Flags This bits indicate the status of the host flags in the Host Control Register. These can only be set by the DSP. TRDY - Transmitter Ready This bit indicates that both the transmit (TXH,TXM,TXL) and the receive (RXH,RXM,RXL) registers are empty. When this bit is set, a data transfer from the host is guaranteed to be immediately transferred to the DSP side of the host interface. TXDE - Transmit Data Register Empty The TXDE bit indicates that the host transmit registers (TXH,TXM,TXL) are empty, and can be written to by the host. TXDE is cleared when the TXL register is written to by the host. RXDF - Receive Data Register Full The RXDF bit indicates that valid data is present in the host receive data registers (RXH, RXM, RXL) and can be read by the host. This bit is cleared when the RXL register is read by the HOST. Interrupt Vector Register (base+3h) R/W 7 6 5 4 3 2 1 0 HI7 HI6 HI5 HI4 HI3 HI2 HI1 HI0 HI[7:0] - Interrupt Vector This register contains the exception command when the DSP interrupts the host. Board Information Register (base+4h) RO This register contains the Xilinx code and board level revisions when the DSP is in RESET. Note: This register is available with Xilinx code rev 1.18 and higher only. If an earlier version of the Xilinx code is used this register is reserved and always reads 0xFF. 7 6 5 4 3 2 1 0 XVER3 XVER2 XVER1 XVER0 RES BTYPE BVER1 BVER0 XVER[7:4] - Xilinx Version This value contains the Xilinx code revision. 1:1:1:1 - Xilinx version 1.15 or earlier 0:0:0:0 - not defined 0:0:0:1 - Xilinx version 1.18 (reported as 1.2) 0:0:1:0 - Xilinx version 1.3x 0:0:1:1 - Xilinx version 1.4x 0:1:0:0..1:1:1:0 - possible future revisions RES This bit is reserved and always reads as 1. BTYPE - Board Type This value contains the board type. 1 - Fiji [DSP + game port] (or early board without hardware information) 0 - Pinnacle [DSP + MPU-401 + game port + EIDE port] BVER[1:0] - Board Version This value contains the board level revision. The possible combinations of BTYPE and BVER are defined as follows: 1:1:1 - Fiji Rev A-B or Pinnacle Rev A-E (early boards without hardware information) 0:0:1 - Pinnacle Rev F (or modified early Pinnacles) 0:1:0 - Pinnacle Rev G 0:1:1 - Pinnacle Rev H 0:0:0 - Pinnacle Rev I 1:0:1 - Fiji Rev C 1:1:0 - Fiji Rev D 1:0:0 - Fiji Rev E Control Register (base+4h) WO This register contains various bits that were previously implemented in the AT Bus Interface Control Registers. 7 6 5 4 3 2 1 0 X X X X X /MEMEN /DSPRST BLKSEL MEMEN - enable DSP shared memory window (active low) 0 - shared memory window is enabled (address decoded) 1 - shared memory window is disabled (address not decoded) Note: This function is available with Xilinx code rev 1.30 and higher only. DSPRST - DSP Reset (active low) 0 - DSP is in reset 1 - DSP is running BLKSEL - Memory Block Select This port allows the PC to choose which of the two 32 KB blocks of DSP RAM is currently viewed in the shared RAM window. 0 - View DSP address X/P:4000h-7FFFh, Y:8000h-BFFFh 1 - View DSP address X/P:8000h-BFFFh, Y:4000h-7FFFh Receive Data Registers (base+5h, base+6h, base+7h) RO The receive byte registers are viewed as 3 read only bytes from the host processor. These registers contain valid data when RXDF is set. This indicates to the host processor that the receive data registers are full, and can be read. Because reading RXL clears the RXDF bit, RXL is always the last byte read from these registers. Transmit Data Registers (base+5h, base+6h, base+7h) WO The transmit byte registers are viewed as 3 write only bytes from the host processor. Data may be written to these registers when the TXDE bit is set. Writing to TXL clears the TXDE bit, so TXL is always the last transmit register to be written to. The Bus Interface Control Registers xx8-xxE of the previous Multisound boards are replaced by the PNP registers which are these: PnP PC Configuration Registers CFG_INDEX xx0H Plug and Play Index Register (WO) CFG_DATA xx1H Plug and Play Data Register (R/W) EEPROM_CTL xx2H PnP EEPROM Control Register (R/W) Memory Addressing This section goes over the addressing scheme for both the DSP and the PC. MultiSound has 32K x 24 bits of memory. The DSP views this memory as one continuous block. Since the DSP data bus is 24 bits wide and the PC data bus is 16 bits wide, the least significant 8 bits are not available from the PC bus side. The PC memory is addressed in 8 bit increments so 16K of DSP RAM appears as 32K of RAM on the PC side. This is why the memory block selector (see above) is needed. It also explains why the PC's window appears to be twice the size of the related DSP RAM. While all types of RAM accesses will work from the PC side, the most useful are word accesses since the DSP has no choice but to view the RAM in words. All translation of the data is taken care of by the hardware. Note that this RAM is truly shared. If the PC is using the memory, the DSP will be put on hold during external bus cycles. This shared memory area (SMA) is used to buffer MIDI and audio data going both in and out, as well as holding the queuing structures. The memory map of the SMA is shown in the Appendix. The usage of the individual locations will be explained as we proceed. For now just take a look at it, and note what kind of data is held there. Software overview The demo code uses several typedefs, macros, structures, and unions. All of there definitions are contained in the file MCONTROL.H. Here we will briefly describe their utility. If you have done any windows programming these typedefs will be familiar to you typedef unsigned char BYTE; typedef unsigned int WORD; typedef unsigned long DWORD; The following macros are provided to make the code more readable. HIWORD(l) These macros are used to extract WORDs from DWORDs LOWORD(l) Example: HIWORD ( 0x0000:FFFF ) = 0x0000 LOWORD ( 0x0000:FFFF ) = 0xFFFF HIBYTE(l) These macros are used to extract BYTEs from WORDs LOBYTE(l) Example: HIBYTE ( 0x00FF ) = 0x00 LOBYTE ( 0x00FF ) = 0xFF MAKELONG(low,hi) These macros are used to make a long from two WORDs MAKEWORD(low,hi) or a WORD from two BYTEs. Example: MAKELONG( 0xFFFF , 0x0000 ) = 0x0000:FFFF MAKEWORD( 0xFF , 0x00 ) = 0x00FF PCTODSP_OFFSET(w) These macros are used to convert a PC offset address PCTODSP_BASED(w) into an offset or a based address readable by the DSP. Example: PCTODSP_OFFSET( 0x2400 ) = 0x1200 PCTODSP_BASED( 0x2400 ) = 0x1200 + 0x4000 CLI These macros are just their assembly equivalent, and STI are used to disable and re-enable interrupts. They are defined in MCONTROL.H as: _asm { cli } and _asm { sti } respectively CHAPTER 2 - Initializing the Hardware This chapter will take you through the steps necessary to initialize the hardware. Then it will step you through the demo code AUXVOL.C and INVOL.C which will allow you to adjust the input volumes of the MultiSound. There are four basic steps necessary to initializing the hardware. They are as follows: + Initializing SMA + Loading DSP Code + Setting up the IRQ + Resetting the IRQ Each of these will be described in full in the following paragraphs. Initializing the Shared Memory Area (SMA) Once the DSP code is loaded we have to set the SMA variables to their default values. The function InitializeSMA() in MCONTROL.C is used by the demo code to do this. This function defaults the variables, as well as setting the SMA offsets to the appropriate pointer values based on the selected shared RAM base address. You may want to modify the values set in InitializeSMA depending on your specific application. Loading the DSP code The specifics of uploading the DSP code are not relevant to the material in this manual. We provide the function UploadDSPCode to do this. Setting up the IRQ The functions SetupMsndIRQ() and SetupMPUIRQ() and SetIntMap (BYTE bIRQ) are provided to set up the Interrupt on the MultiSound. The SetIntMap function sets up the interrupt value received from the MultiSound. Since the polarity of the host port and the PC interrupts are inverted, this map should only be written during an active host port interrupt. To do this, have the PC's 8259 interrupt controller disabled (or CLI in PC assembler), then enable transmit interrupts on the host port. This will set the interrupt logic active (high), provided that the transmitter is empty ( waitTXDE() ). Write the desired HP_IRQM value, then disable the transmit interrupt on the host port. Finally, re-enable the interrupt on the PC's 8259 (or STI in PC assembler). The function waitTXDE() is included in MCONTROL.C, and merely waits for the TXD register to be emptied of any current data. It returns true if the register clears and false if it times out waiting for them to clear. The source for this function is also listed below. BYTE SetIntMap( BYTE bIrq ) { if( WaitTXDEmpty() ) { outp( wBASEIO + HP_ICR , inp( wBASEIO + HP_ICR ) | (HPICR_TREQ) ); if (bIrq != HPIRQ_NONE) outp( wBASEIO + HP_ICR , inp( wBASEIO + HP_ICR ) & ~(HPICR_TREQ)); return(1); } return(0); } BYTE WaitTXDEmpty( void ) { int nTimeOutCount = 20000; while(nTimeOutCount-- >= 0) { if( inp(wBASEIO + HP_ISR) & HPISR_TXDE ) { return(1); } } return(0); } BYTE SetupMsndIRQ() { WORD wGP; WORD wPic; BYTE cIrqMask; BYTE cIntBit; if( nIRQValue > 7 ) { wGP = nIRQValue + 0x68; cIntBit = ( 1 << (nIRQValue - 8)); wPic = PIC2+1; } else { wGP = nIRQValue + 8; cIntBit = (1 << nIRQValue ); wPic = PIC1+1; } if( SetIntMap( bIrq ) == 0 ) { return(0); } nDSPSaveVect = _dos_getvect( wGP ); _dos_setvect( wGP , InterruptHandler ); CLI; // lock out interrupts. cIrqMask = inp( wPic); // only enable it if it is not already enabled. if (cIrqMask & cIntBit) outp( wPic, ((~cIntBit) & cIrqMask) ); outp( wBASEIO + HP_ICR , inp( wBASEIO + HP_ICR ) | HPICR_RREQ ); STI; return(1); } Resetting the IRQ There is also a function ResetMsndIRQ() and ResetMPUIRQ() included in MCONTROL.C which restores the interrupt to its original value. This should be run in your exit code to insure system integrity. Example #1 - AUXVOL.C and INVOL.C We will now take the information we learned from above, and apply it to a simple application. This application, included with this kit, will allow you to set the auxiliary input volume of the MultiSound. Based on the preceding information we can write the entry and exit code for our first example as follows. #include <stdio.h> #include <conio.h> #include "msnd_dsp.h" #include "pcontrol.h" BYTE bTerminator = 0; main() { bMem = HPMEM_D000; //bIrq = HPIRQ_10; //nIRQValue = 10; bIrq = HPIRQ_9; nIRQValue = 9; pMEM.dw.h = 0xD000; pMEM.dw.l = 0x0000; wBASEIO = 0x290; // non plug and play jumpers as follows: wCFGIO =0x250; // ------------------- // | /-------------\ | address x250 // | | o o | | // | \-------------/ | // | | // | o o | // | | // ------------------- InitializeSMA(); if (!UploadDspCode()) { printf("Failed to upload and initialize DSP\n"); exit(0); } SetupMsndIRQ(); . . application code . . ResetMsndIRQ(); return(0); } Here we have defined several global variables for use with the program. The reason for defining these globally is that virtually all the functions will need access to one or more of these values. The variable pMEM is a union defined in MCONTROL.H. Its definition follows: union mem_tag { BYTE far *p; struct dw_tag { WORD l; WORD h; } dw; } pMEM; This union allows us to access the SMA either as a far pointer, a WORD offset, or the WORD describing the segment. The constants HPMEM_D000, HPIRQ_10, HPIO_3E0 are the register values for the associated parameters and are defined in MSND_DSP.H. Now we will add in the high level application code. We will allow the user to increase and decrease the volume using the '+' and '-' keys. This will be done using a while loop based on a termination flag ( bTerminator ). The flag will be set when the user hits the 'q' key, and it will stop the program. The application loop that we will insert in the code will be as follows. while( !bTerminator ) { if( kbhit() ) { switch ( getch() ) { case '+': SetAuxVolume( ++SMA->bAuxPotPosLeft , GANG ); printf("Aux Volume = 0x%X\n", SMA->bAuxPotPosLeft ); break; case '-': SetAuxVolume( --SMA->bAuxPotPosLeft , GANG ); printf("Aux Volume = 0x%X\n", SMA->bAuxPotPosLeft ); break; case 'Q': case 'q': printf("Terminating\n"); bTerminator = 1; break; } } } As can be seen when the user requests that the volume be increased, the program responds by incrementing the SMA AUX volume variable, and then calling the function SetAuxVolume(). This function must tell the DSP that the value of the AUX volume has changed. This function is included in MCONTROL.C and is shown below. void SetAuxVolume( void ) { SendHostWord( 0x00 , 0x00 , HDEXAR_AUX_SET_POTS ); SendDSPCommand( HDEX_AUX_REQ ); } This function uses two other functions from MCONTROL.C. The first one loads the specific AUX request into the host port transmit registers. The code for sendhostword is listed below. First the function calls waitTXDE to wait for the transmit data registers to clear any data they currently contain. Then it loads them with the AUX request. If the waitTXDE fails, the function returns false. BYTE SendHostWord(BYTE bHi, BYTE bMid , BYTE bLow ) { if( WaitTXDEmpty() ) { outp( wBASEIO + HP_TXH , bHi ); outp( wBASEIO + HP_TXM , bMid ); outp( wBASEIO + HP_TXL , bLow ); return(1); } return(0); } The next function, SendDSPcommand(), sends the HDEX_AUX_REQ exception to the DSP. This interrupts the DSP and tells it that there is an AUX request in the TXD registers. The value of bCommand tells the DSP which of its ISRs to execute. The function waitHC() is similar to waitTXDE(), only it is waiting for the DSP to acknowledge that it is ready for another command. BYTE SendDSPCommand( BYTE bCommand ) { if( WaitHostClear() ) { outp( wBASEIO + HP_CVR , bCommand ); return(1); } return(0); } Now we have constructed all the elements of our first MultiSound application. All that is necessary is to compile it. The source for this program is contained in the kit. Be sure enter the appropriate hardware settings into your code before compiling (ie wBASEIO, pMEM.p, and IRQ ). If you are using Microsoft C compiler, you can use the MAKEFILE included and run 'nmake' to compile the code. When you run the program be sure that you have a line level audio source hooked up to the auxiliary input. To adjust the record input level we only have to change the SetAuxVolume() function to SetInVolume(), and change the SMA variable which is changed( SMA->bInPotPosLeft ). SetInVolume() does the same thing as SetAuxVolume() only it sends the HDEXAR_SET_IN_POTS to the TXD registers. The source code for this version is called INVOL.C and is in this kit. CHAPTER 3 - Using the MultiSound Queues Now that we have laid the foundation for programming the MultiSound, the next step is to create an application which interacts with the DSP in real time. Before we can do that we must have a firm grasp on how the job queue system works in the MultiSound. The SMA contains 5 job queues. These are used to control the data for digital audio recording, digital audio playback, MIDI in, MIDI out, and DSP to host messages. Each of these queues has a structure associated with it, which is used for control of that particular queue. This structure is shown below, and is the foundation of the queue based system by which the host and the DSP interact. struct JobQueueStruct { WORD wStart; WORD wSize; WORD wHead; WORD wTail; }; The 5 JobQueueStruct's are located in the SMA, since both the DSP and the host will need access to them. The wStart value will always be a DSP based pointer to an associated data buffer, of size wSize. Thus if the structure was for MIDI in data, wStart would be a pointer to an SMA buffer of size wSize where the DSP will write incoming MIDI data. The head and tail values are used to keep track of the current location in the queue. No matter which way the information is going, data is always written to the tail of a queue, and read from the head. So if we were sending MIDI data, the host would write the data to the tail of the queue, and the DSP would read it from the head. On the other hand, if we were receiving MIDI data, the DSP would write incoming data to the tail, and the host would read that information off the head of the queue. Since any one queue will be used exclusively as either a read or write data channel, only one element of the structure has to be maintained by the host. For data out queues, the host will maintain the tail, and the DSP will maintain the head. Likewise for data in queues, the DSP will maintain the tail, and the host will maintain the head. The start and size parameters are constants which will only be set at initialization time. The names of the queue structures used in this text are listed below along with who controls each of the elements. DAPQ - Digital audio play job queue Host owns wTail DSP owns wHead DARQ - Digital audio record job queue DSP owns wTail Host owns wHead MODQ - MIDI out data job queue Host owns wTail DSP owns wHead MIDQ - MIDI in data job queue DSP owns wTail Host owns wHead DSPQ - DSP to host messages job queue DSP owns wTail Host owns wHead For MIDI data and DSP messages the JobQueueStruct will point directly to the associated buffer where the information is stored. However for digital audio play and record, the process is not as direct. For these the JobQueueStruct points to a DAQueueDataStruct, which contains the pointer to the data buffer, as well as information on the type of audio data contained in that buffer. This Structure is shown below, and is included in MCONTROL.H. struct DAQueueDataStruct { WORD wStart; WORD wSize; WORD wFormat; WORD wSampleSize; WORD wChannels; WORD wSampleRate; WORD wIntMsg; WORD wFlags; }; In this case of audio the job queue ( DAPQ or DARQ ) pointers refer to a buffer of three of the data queue structures. These structures in turn have a pointer( wStart ) to the location of the actual audio data. For the case of playing audio there are 3 buffers of 0x2400 bytes in bank 0 of the SMA, each with an associated DAQueueDataStruct. For recording audio there are 3 buffers of 0x2000 bytes in bank 1 of the SMA. Again each of the record buffers has an associated DAQueueDataStruct. The other elements of the structure are used to control the audio format. The supported values are tabulated as follows. wFormat - 1 = PCM wSampleSize - 16 bit / 8 bit wChannels - 1 = MONO; 2 = STEREO wSampleRate - 11025 Hz / 22050 Hz / 44100 Hz The remaining two values are for the hosts use. Upon completion of a job the DSP will interrupt the host, and place the value wIntMsg into the DSP to host message queue. The last variable wFlags can be used to store any data that the user wants to have linked to a particular buffer of audio data. Now that we have an overview of the queuing mechanism used by MultiSound we can start writing an application using them. Although the queue based system lends itself to audio multitasking (i.e. recording audio and sending MIDI simultaneously) this manual will only treat one audio task at a time. CHAPTER 4 - Using Digital Audio This chapter will walk you through the sample record and play applications. Before doing this, you may want to review the memory map in the Appendix section of this manual, and review the structure definitions that are documented in mcontrol.c. Doing this will make understanding the process much easier. We will use the foundation code we wrote in chapter 2 to start us off with. The first thing we need to add to this is a message handling system. As the DSP completes tasks from its job queues, it will interrupt the host and place messages, dependant on the particular task completed, in the DSPQ data buffer( pointed to by DSPQ->wStart ). So when the host receives an interrupt from the DSP it will have read the messages from the location pointed to by DSP->wHead. Then it will have to increment DSP->wHead and compare it to DSP->wTail. This process will continue until DSP->wHead is equal to DSP->wTail. Now we will start to write the Interrupt Service Routine( ISR ). This manual assumes that you are familiar with writing an ISR. The following is a listing of the interrupt handler used by the demo code in this tool kit. void _interrupt _far int_handler() { if( bIntFlag != 1 ) { bIntFlag = 1; if( bCurrBank ) outp( wBASEIO + HP_BLKS , HPBLKSEL_0 ); inp( wBASEIO + HP_RXL ); if( nIRQValue > 7 ) outp( PIC2 , EOI ); outp( PIC1 , EOI ); while( DSPQ->wTail != DSPQ->wHead ) { if( ++pwHostHead >= ( pwHostQueue + HOSTQ_SIZE ) ) pwHostHead = pwHostQueue; *pwHostHead = *( pwDSPQData + DSPQ->wHead ); if( ++DSPQ->wHead > DSPQ->wSize ) DSPQ->wHead = 0x00; } if( bCurrBank ) outp( wBASEIO + HP_BLKS , HPBLKSEL_1 ); bIntFlag = 0; } } The first thing we check on entry is the value of bIntFlag. This is to make sure we are not nested in the ISR. If we are nested, we exit immediately. When this happens no data is lost. This is because we are already reading the data off the DSP message data queue. The DSP will write the message into the queue and advance DSPQ->wTail, and the host will continue to read messages until it reaches the tail. The next value we check is bCurrBank. This is a global variable which tracks which SMA bank the host is currently using. Since all the queue structures are stored in SMA bank 0, we have to insure that we are looking at that bank while inside the ISR. We do this by writing, if necessary, to the HP_BLKS register as explained in CHAPTER 1. After we are definitely looking at the correct bank, we read the low receive register of the DSP host port. inp( wBASEIO + HP_RXL ); This acts as an acknowledge to the DSP that the port has been read, enabling it to send more information if necessary. After clearing the DSP and issuing the EOI( End Of Interrupt ) to the PIC( Programmable Interrupt Controller ) we can start to read the data off of the message queue. For this sample code we have set up another queue which we will refer to as pwHostQueue. The ISR merely transfers the new information in the DSP message queue into the pwHostQueue. When the host exits from the ISR, it has its own queue of tasks to deal with. Once the host has read all the available information from the message queue ( DSPQ->wHead = DSPQ->wTail ), it restores the previous state of the machine. To do this we restore the state of HP_BLKS, and reset bIntFlag. Now we have the framework for the application and the ISR to handle the interrupts from the DSP. All that remains is the processing of the DSP messages which will be stored in pwHostQueue. The main loop of our program is listed below. Basicaly it keeps checking for a difference between pwHostHead and pwHostTail until the termination flag bTerminator is set or the user hits a key. When the DSP interrupts the host, the ISR will increase pwHostTail. This will cause our program to execute EvalDSPMessage. while( !kbhit() && !bTerminator ) { if( pwHostHead != pwHostTail ) { if( ++pwHostTail >= (pwHostQueue + HOSTQ_SIZE ) ) { pwHostTail = pwHostQueue; printf("Wrapping Host Queue\n"); } EvalDSPMessage( *pwHostTail ); } } All the possible messages that can be received from the DSP are defined in the file MSND_DSP.H. These messages are transmitted in TXH at interrupt time and also written to the DSPQ data buffer. For now we will only be concerned with HIMT_RECORD_DONE and HIMT_DSP \ HIDSP_INT_RECORD_OVER. However we will write our EvaluateDSPMessage to include all messages to make the later examples easier. Our EvaluateDSPMessage is essentialy just a large switch function with a case for every message we want to respond to. The skeleton for the code follows. void EvalDSPMessage( WORD wMessage ) { switch ( HIBYTE( wMessage ) ) { case HIMT_PLAY_DONE: switch ( LOBYTE( wMessage ) ) { // LOBYTE( wMessage ) will be either 0 , 1 , or 2 // depending on which buffer was completed } break; case HIMT_DSP: switch ( LOBYTE( wMessage ) ) { case HIDSP_PLAY_UNDER: printf("HIDSP_PLAY_UNDER\n"); break; case HIDSP_INT_PLAY_UNDER: printf( "HIDSP_INT_PLAY_UNDER\n" ); break; case HIDSP_SSI_TX_UNDER: printf( "HIDSP_SSI_TX_UNDER\n" ); break; case HIDSP_RECQ_OVERFLOW: printf( "HIDSP_RECQ_OVERFLOW\n" ); break; case HIDSP_INT_REC_OVERFLOW: printf( "HIDSP_INT_REC_OVERFLOW\n" ); break; case HIDSP_SSI_RX_OVERFLOW: printf( "HIDSP_SSI_RX_OVERFLOW\n" ); break; case HIDSP_MIDI_FRAME_ERR: printf( "HIDSP_MIDI_FRAME_ERR\n" ); break; case HIDSP_MIDI_PARITY_ERR: printf( "HIDSP_MIDI_PARITY_ERR\n" ); break; case HIDSP_INPUT_CLIPPING: printf( "HIDSP_INPUT_CLIPPING\n" ); break; case HIDSP_MIX_CLIPPING: printf( "HIDSP_MIX_CLIPPING\n" ); break; case HIDSP_DAT_IN_OFF: printf( "HIDSP_DAT_IN_OFF\n" ); break; case HIDSP_MIDI_IN_OVER: printf( "HIDSP_MIDI_IN_OVER\n" ); break; case HIDSP_MIDI_OVERRUN_ERR: printf( "HIDSP_MIDI_OVERRUN_ERR\n" ); break; default: printf("ERROR: UNKNOWN_DSP_MESSAGE %X : %X\n" , HIBYTE( wMessage ) , LOBYTE( wMessage ) ); break; } break; case HIMT_RECORD_DONE: switch ( LOBYTE( wMessage ) ) { // LOBYTE( wMessage ) will be either 0 , 1 , or 2 // depending on which buffer was completed } break; case HIMT_MIDI_IN_BYTE: break; } } Using this framework, we just have to fill in the code appropriate to which messages we want to respond to. For the HIDSP_INT_RECORD_OVER message we will just increment a counter to track the number, if any, of overflows that happened during the record. The meat and potatoes of this program is under the HIMT_RECORD_DONE message. The first step is to check the LOBYTE of the message to see if we are receiving a second message for the same buffer. If this is the case we do not perform any processing. Otherwise we set bLastBank to the current buffer. case HIMT_RECORD_DONE: if( bLastBank != LOBYTE( wMessage ) ) { bLastBank = LOBYTE( wMessage ); . . } break; The next step is to manage the job queue. We have to store the tail value, and increment it. When using the digital audio queues incrementing the tail means we have to make it point at the next DAQueueDataStruct. To do this we add the size of that structure to our stored value. If our new value is greater than the queue size, 3 elements, we will have to wrap the queue. To do this we just set the tail to zero. As a precaution we make sure that queue is not wrapping itself by comparing the head to the tail. If they are equal we have to wait until the DSP advances the head before we can advance the tail. Then we send the DSP a HDEX_RECORD_START exception so it will check the queue to see the update. Our new code looks like this. case HIMT_RECORD_DONE: if( bLastBank != LOBYTE( wMessage ) ) { bLastBank = LOBYTE( wMessage ); wTemp = DARQ->wTail + DARQ_STRUCT_SIZE; if( wTemp > DARQ->wSize ) { wTemp = 0; } while( wTemp == DARQ->wHead ); DARQ->wTail = wTemp; SendDSPCommand( HDEX_RECORD_START ); . . } break; Now we have a buffer full of recorded digital audio data, and we have to copy it to a local buffer, and then write it to disk. First we get a pointer to the buffer where the data is located based on the LOBYTE of the DSP message sent to us. Since the record buffers are located in the SMA bank 1, getting the data is a little tricky. First we change the bank the host is looking at by writing to the HP_BLKS register. During this transition we can't have any interrupts so we disable them ( CLI ) going into write, and then re-enable them when leaving ( STI ). Once we are looking at the correct bank, we can copy the data to our local buffer. Then we restore HP_BLKS so the host is looking at bank 0. We must disable interrupts for this transition as well. At last we write the data to disk. The code now looks like this. case HIMT_RECORD_DONE: if( bLastBank != LOBYTE( wMessage ) ) { bLastBank = LOBYTE( wMessage ); wTemp = DARQ->wTail + DARQ_STRUCT_SIZE; if( wTemp > DARQ->wSize ) { wTemp = 0; } while( wTemp == DARQ->wHead ); // Wait if we are going to // wrap the queue DARQ->wTail = wTemp; SendDSPCommand( HDEX_RECORD_START ); pbCurRecordBuff = ( BYTE far *)( pMEM.p + bLastBank * DAR_BUFF_SIZE ); CLI; outp( wBASEIO + HP_BLKS , HPBLKSEL_1 ); bCurrBank = 1; STI; _fmemcpy( pbHostBuffer , pbCurRecordBuff , DAR_BUFF_SIZE ); CLI; outp( wBASEIO + HP_BLKS , HPBLKSEL_0 ); bCurrBank = 0; STI; fwrite( pbHostBuffer , sizeof( BYTE ) , DAR_BUFF_SIZE, f_point ); } break; The only remaining task is taking care of the file opening, pre-loading the queue and file closing. Since these are straight forward it is left to the user to look at the final source code, included in this kit, to see how this is accomplished. The only explanation left is the calibration of the analog to digital converters. The function CalibrateAD( WORD wSampleRate ) is provided in MCONTROL.C to accomplish this. The source for CalibrateAD is shown below. void CalibrateAD( WORD wSampleRate ) { BYTE bTime; SMA->wCalFreqAtoD = wSampleRate; sendhostword( 0x00 , 0x00 , HDEXAR_CAL_A_TO_D ); SendDSPCommand( HDEX_AUX_REQ ); // The actual time for the calibration is equal to // 4096 / SAMPLE_RATE // multiply by 100 to get bTime as # of hundreths of seconds bTime = 100 * 4096 / SAMPLE_RATE; GetDelayTimefor( bTime ); } This function takes the recording sample rate desired as a parameter. It calculates the calibration time based on this value. Then it sets the sample rate in the SMA variable SMA->wCalFreqAtoD. Next it puts the particular AUX request into TXL, and sends the HDEX_AUX_REQ exception to the DSP. It finishes off by calling GetTimeDelay, a function which waits the specified number of hundreths of a second and then returns. Now we have a fully functional recording program. The sample rate, bits per sample, and number of channels can be set using the defines in the beginning of the program. Now we can start writing a program which will play the audio. Playing Audio To play audio is almost identical to recording the audio. We can use the same framework we used for recording. The only major change we have to make is the message response in EvalDSPMessage. Playing is actually easier since the play data buffers are in the same SMA bank as the queues, so we do not have to change our SMA window to access the data. This kit contains the source in PLAY.C. If you take a look at it you will see that main routine is basically the same as in RECORD.C. Instead of setting up the DARQ structure, we setup the DAPQ structure, as well as the associated DAQueueDataStructs. Also you will note that we don't have to calibrate the converters. We change the entry code to pre load the buffers with audio data from our file. Then we only have to add a case to EvalDSPMessage. In PLAY.C when we get a bank empty message, we have to load a new buffer of information into the SMA play data buffer from the disk. As with recording the first step is to make sure we are not getting a repeated message. LOBYTE( wMessage ) will contain the buffer number which the DSP has just finished playing. case HIMT_PLAY_DONE: if( bLastBank != LOBYTE( wMessage ) ) { bLastBank = LOBYTE( wMessage ); . . . } break; To reduce the chance of data underflows, we always want to have all three SMA buffers filled with data. You will notice in the entry code that we preload all three buffers. However we only set the DAPQ as if two were loaded. So when we get an HIMT_PLAY_DONE message we just advance DAPQ->wTail, and the data is already there. Then we send the DSP exception so it will look at the queue. Before leaving we must advance our own pointers, and read the next buffer from the disk. So when we get the next HIMT_PLAY_DONE message our data is already loaded. Our final play done section now looks like this. case HIMT_PLAY_DONE: if( bLastBank != LOBYTE( wMessage ) ) { bLastBank = LOBYTE( wMessage ); CurDAQD->wSize = (WORD)lReadSize; if((DAPQ->wTail += PCTODSP_OFFSET(DAPQ_STRUCT_SIZE)) > DAPQ->wSize ) { DAPQ->wTail = 0; } SendDSPCommand( HDEX_PLAY_START ); if( ++CurDAQD > (LPDAQD)( pMEM.p + DAPQ_DATA_BUFF + 2*DAPQ_STRUCT_SIZE ) ) CurDAQD = (LPDAQD)( pMEM.p + DAPQ_DATA_BUFF ); if( ( pbCurPlayBuff += DAP_BUFF_SIZE ) > (pMEM.p + 2*DAP_BUFF_SIZE)) pbCurPlayBuff = pMEM.p; lReadSize = fread( pbCurPlayBuff , sizeof( BYTE ) , DAP_BUFF_SIZE, f_point ); } break; Now we have the ability to play and record audio through the MultiSound. As you play around with this code, you can use the SMA->wCurrPlayVolLeft and SMA->wCurrPlayVolRight variables to adjust the output volume of the audio. This completes our coverage of digital audio. The Appendix section and mcontrol.c should provide you with all the information you need to program the card once you understand the basic concepts explained in this chapter. CHAPTER 5 - Using MIDI The MultiSound has a Kurzweil MA-1 MIDI sound module on board (Pinnacle only). This section will explain how to access the Kurzweil as well as the external MIDI port. This manual assumes the reader is knowledgeable on the subject. Like our work with audio on the MultiSound, MIDI is controlled via a queuing system. The MultiSound has two MIDI data buffers in the SMA. One is for transmitting MIDI data and the other is for receiving. To send MIDI, place the data you want to transmit into the MIDI out buffer at the tail, offset MODQ->wTail, and advance the tail so the DSP knows there is data in the buffer. Likewise when there is MIDI data coming in, the DSP will interrupt the host. When the host receives a HIMT_MIDI_IN_BYTE message it reads the MIDI byte from the head of the MIDI in data buffer, offset MIDQ->wHead. Several functions are contained in MCONTROL.C which ease the manipulation of MIDI data. In addition to the MIDI queueing system outlined above, the Kurzweil MA-1 chip is implemented through its own separate MPU compatible port and IRQ. Midi data can be directed through the queues via the calls: SetMidiOutPort(WAVEHDR_MOP); SetMidiInPort(EXTIN_MIP); This sets up the MIDI out port to be directed to the wave header synth (WAVEHDR_MOP) and the Midi in port is directed to the External Midi input. All subsequent calls to the function MODPortWrite() will be directed to the wave header port. To enable talking to the Kurzweil chip call the function EnableMPU(), to set up the port and interrupt vector to talk to this. Writing data to the Kurzweil chip is done via the WriteMPU() function and data from the Kurzweil (such as Sysex info) is done via the GetMPUInData() function, which reads data out of the circular buffer from the MPU chip. The following is an example of initializing the board to talk to both the Kurzweil, External MIDI and Wavetable Daugherboard synth. NOTE: the following global variables must be set in-order for the EnableMPU to work. wMPUIO = 0x330; nMPUIRQ = 5; main() { bMem = HPMEM_D000; //bIrq = HPIRQ_10; //nIRQValue = 10; bIrq = HPIRQ_9; nIRQValue = 9; wMPUIO = 0x330; nMPUIRQ = 5; pMEM.dw.h = 0xD000; pMEM.dw.l = 0x0000; wBASEIO = 0x290; // DSP base address to program. // non plug and play jumpers as follows: wCFGIO =0x250; // ------------------- // | /-------------\ | address x250 // | | o o | | // | \-------------/ | // | | // | o o | // | | // ------------------- if( ( pwHostQueue = calloc( sizeof(WORD) , HOSTQ_SIZE ) ) == NULL ) exit(0); pwHostTail = pwHostQueue; pwHostHead = pwHostQueue; InitializeSMA(); if (!UploadDspCode()) { printf("Failed to upload and initialize DSP\n"); exit(0); } SetupMsndIRQ(); if (!EnableMPU()) { printf("Could not setup Kurzweil MPU\n"); } SetMidiOutPort(WAVEHDR_MOP); SetMidiInPort( EXTIN_MIP); SendDSPCommand( HDEX_MIDI_IN_START ); SendDSPCommand( HDEX_MIDI_OUT_START ); while( !kbhit() ) { if( pwHostTail != pwHostHead ) { if( ++pwHostHead >= ( pwHostQueue + HOSTQ_SIZE ) ) { pwHostHead = pwHostQueue; printf("Wrapping Host Queue\n"); } EvalDSPMessage( *pwHostHead ); } } SendDSPCommand( HDEX_MIDI_IN_STOP ); DisableMPU(); ResetMsndIRQ(); return(0); } APPENDIX: SMA memory map Shared Memory Area I Bank 0 DAP buffer #1: Ox0000 - Ox23FF DAP buffer #2: Ox2400 - Ox47FF DAP buffer #3: Ox4800 - Ox6BFF 3 * DAP Queue Data Structures ORG Ox6COO Struct 1 Struct 2 Struct 3 wStart ORG + 0x00 ORG + 0x10 ORG + 0x20 wSize ORG + 0x02 ORG + 0x12 ORG + 0x22 wFormat ORG + 0x04 ORG + 0x14 ORG + 0x24 wSampleSize ORG + 0x06 ORG + 0x16 ORG + 0x26 wChannels ORG + 0x08 ORG + 0x18 ORG + 0x28 wSampleRate ORG + 0x0A ORG + 0x1A ORG + 0x2A wlntMsg ORG + 0x0C ORG + 0x1C ORG + 0x2C wFlags ORG + 0x0E ORG + 0x1E ORG + 0x2E 3 * DAR Queue Data Structures ORG Ox6C30 Struct 1 Struct 2 Struct 3 wStart ORG + 0x00 ORG + 0x10 ORG + 0x20 wSize ORG + 0x02 ORG + 0x12 ORG + 0x22 wFormat ORG + 0x04 ORG + 0x14 ORG + 0x24 wSampleSize ORG + 0x06 ORG + 0x16 ORG + 0x26 wChannels ORG + 0x08 ORG + 0x18 ORG + 0x28 wSampleRate ORG + 0x0A ORG + 0x1A ORG + 0x2A wlntMsg ORG + 0x0C ORG + 0x1C ORG + 0x2C wFlags ORG + 0x0E ORG + 0x1E ORG + 0x2E MODQ Buffer 0x6C60 - 0x745F MIDQ Buffer 0x7460 - 0x785F DSPQ Buffer 0x7860 - 0x7DFF DAPQ->Start 0x7F00 DAPQ->Size 0x7F02 DAPQ->Head 0x7F04 DAPQ->Tail 0x7F06 DARQ->Start 0x7FO8 DARQ->Size 0x7FOA DARQ->Head 0x7FOC DARQ->Tail 0x7FOE MODQ->Start 0x7F10 MODQ->Size 0x7F12 MODQ->Head 0x7F14 MODQ->Tail 0x7F16 MIDQ->Start 0x7F18 MIDQ->Size 0x7F1A MIDQ->Head 0x7F1C MIDQ->Tail 0x7F1E DSPQ->Start 0x7F20 DSPQ->Size 0x7F22 DSPQ->Head 0x7F24 DSPQ->Tail 0x7F26 wCurrPlayBytes 0x7F40 // decrements with time wCurrRecordBytes 0x7F42 // decrements with time wCurrPlayVolLeft 0x7F44 wCurrPlayVolRight 0x7F46 wCurrInVolLeft 0x7F48 wCurrInVolRight 0x7F4A HOST definable 0x7F48 - 0x7F59 Reserved by DSP 0x7F5A - 0x7F69 wCurrDSPStatusFlags 0x7F6A wCurrHostStatusFlags 0x7F6C wCurrInputTagBits 0x7F6E wCurrLeftPeak 0x7F70 wCurrRightPeak 0x7F72 wExtDSPbits 0x7F74 bExtHostbits 0x7F76 bBoardLevel 0x7F77 blnPotPosRight 0x7F78 blnPotPosLeft 0x7F79 bAuxPotPosRight 0x7F7A bAuxPotPosLeft 0x7F7B HOST definable 0x7F7C - 0x7F85 wCalFreqAtoD 0x7F86 HOST definable 0x7F88 - 0x7F8B Shared Memory Area I Bank 1 DAR Buffer #1 0x0000 - 0x1FFF DAR Buffer #2 0x2000 - 0x3FFF DAR Buffer #3 0x4000 - 0x5FFF Reserved by DSP 0x6000 - 0x7FFFDownload Driver Pack
After your driver has been downloaded, follow these simple steps to install it.
Expand the archive file (if the download file is in zip or rar format).
If the expanded file has an .exe extension, double click it and follow the installation instructions.
Otherwise, open Device Manager by right-clicking the Start menu and selecting Device Manager.
Find the device and model you want to update in the device list.
Double-click on it to open the Properties dialog box.
From the Properties dialog box, select the Driver tab.
Click the Update Driver button, then follow the instructions.
Very important: You must reboot your system to ensure that any driver updates have taken effect.
For more help, visit our Driver Support section for step-by-step videos on how to install drivers for every file type.