TECH_REF.TXT Driver File Contents (pnddk100.zip)

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 - 0x7FFF


Download Driver Pack

How To Update Drivers Manually

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.

server: web5, load: 1.00