Ŀ
 TRACKJOY - Gravis Ultrasound Tracker by RSC                               
 (c) Copyright 1993, 1994, 1995 Tomi Joy                                   
 All rights reserved                                                       
 Programming & documentation: Tomi Joy                                     
                                                                           
 TECHNICAL DOCUMENTATION for programmers                                   
                                                                           
 *** NOTE: This is not supposed to be a particularly pretty                
           document, but is does supply some useful and                    
           possibly interesting information.                               
                                                                           
 Specs are theoretically for Trackjoy version 1.00,                        
 which is technically equivalent to all the                                
 less than or equal to 1.00 beta versions.                                 
 The file format specifications currently cover                            
 *file* versions 1.0 and 1.1, the only difference being                    
 in the way four bytes in then sample controllers are used.                
                                                                           


  Note: You might see some references to a program called Instrument 
   Maker in this document. IM is the sample editor which will be 
   included in future versions of Trackjoy. At the moment it doesn't 
   support 16-bit samples and has some severe bugs, so I didn't want to 
   release it yet.

  I don't promise that the information in this document is correct 
   and/or up-to-date.

 
  Table of contents
 

 
 1. Overview
 2. The Gravis Ultrasoud
 3. Trackjoy system requirements + other information
 4. Trackjoy music storage & playing definitions
 5. File formats (.TJS, .JOY, .BLK, samples)
 6. Compression schemes
 7. The interrupt interface
 8. The sound effect interface


 
  1.  Overview
 

 The heart of Trackjoy is the playing engine. It's a relatively simple
 group of functions, one of which is hooked to the timer interrupt vector
 08h. This function increments counters and checks for the next notes to
 play. It also checks when to damp voices down with the Ultrasound's
 volume ramping register. This is done to avoid clicking when samples
 are abruptly cut off. The damping timing can be adjusted to some extent.

 The other functions associated with the playing engine simply fetch
 the note, volume and instrument (and special command) data from the
 storage array. One of the functions does the opposite of damping the
 voices, ie. it lights them if the time is ripe.

 Normally when an 80x86 is booted up, the timer interrupt vector points
 to a routine which increments the time and date counters in the PC's
 memory. The frequency at which this is done is approximately 18.2 Hz.
 Unfortunately this gives rise to a problem: Trackjoy needs a 100 Hz
 timer for the player engine, so the timer has to be re-programmed.
 Obviously if we still keep on chaining back to the old service
 routine after the player has done with an interrupt, time is going to
 go too fast. If, on the other hand, we don't chain to the previous
 vector at all, time is going to *stop*. Now we don't want that either.
 The solution (although not a very pleasing one) is to call the old
 routine only every now and then.

 Trackjoy uses a 500 Hz hardware timer, but uses only every fifth tick 
 (100 Hz) to do anything to the music score.

 Data bytes recognized by Trackjoy are the note, volume, instrument
 bytes. On so-called FULL channels 3 more bytes are used: the command 
 and its two parameters. Notes are very straight forward: the value 0 is 
 C-0, 1 is C#0 and so on. When *any* of the data bytes has the falue 255 
 or FFh, it's an *empty* byte and is represented by dots on the Trackjoy
 editing screen.

 Semitones are supposed to be about 2^(1/12) * the previos note. ("The 
 twelth root of two" equals "two to the power of one twelth".) In 
 decimal this is about 1.05946. In other words, the frequency of any 
 note in an octave is basic_frequency * 1.05947 ^ (number_of_note). To 
 raise a note to an octave multiply it by 2 ^ (number_of_octave).
 
 Actually, that is *not* exacty what Trackjoy does. It looks up notes
 from a table, since it's much quicker *and* it avoids using floating
 point math that way.



 2. The Gravis Ultrasound


 Let's be blunt: the Gravis Ultrasound card is a bugger to program.
 Consider these "features" of the card from the programmer's point
 of view.

   1. 16-bit samples can't be played across 256k boundaries
      (Memory is segmented as 1 to 4 256k banks)

   2. With 32 voices in use, the maximum output frequency is only
      19293 Hz, and 44.1 kHz is achieved only with 14 voices.

   3. The frequency the card produces with a certain frequency 
      controller register value (and the speed at which volume ramping 
      is done) depends on the number of active voices in use.

   4. Without significant programming effort, clicking is a real 
      problem. The click is caused by suddenly changing the DAC value by 
      a large degree. To remove it, you have to do a lot of careful 
      volume ramping register programming.

   5. It only has 16 stereo pan positions. This isn't enough, because
      the say in the official GUS documentation that it causes clicking.

   6. Certain registers (the self-modifying ones) have to be updated 
      twice before you can be sure the card got the message.

 
  3. Trackjoy system requirements + other information
 

  Trackjoy was programmed in C and assembler

  System requirements:
        - 80286+
        - EGA, although VGA is supported
        - Ultrasound card (256k will do)
        - about 300k free low memory
        - hard disk stronly recommeneded

   
 
  4. Trackjoy music storage & playing definitions
 

 4.1 Music score is divided into patterns.

 4.2 Pattern length (1-255 rows) and structure (ie. width and type
     for each channel) can vary

 4.3 Patterns consist of channels (max. 17 channels) Actual voice
     channels are limited to sixteen in order to retain acceptable sound 
     quality with the GUS. In practice this will be quite enough for 
     almost any purpose. The 17th channel is an optional global channel 
     that controls all channels.

 4.4 Three channel types are supported. They are:

     Stripped: Only note, instrument & volume information
     Full:     Note, instrument, volume & special effect info
     Global:   Global volume & special effect information (max one per pattern)

     (The CHORD type was omitted due to programming difficulties, not 
     actually needing it and most important of all  laziness.)

 4.5 Notes: C-0 to B-9 (could in theory go to D-21, but that wouldn't be
     practical)

 4.6 Instruments: Normal 8- and  16-bit samples will be supported in version
     1.0. Version 1.1 might add ADSR support to these samples. Sorry, no
     GF1 patch support.

 4.7 Channel sample volume: 0 - 254. This will be from a table, since 
     the US's volume 0-4095 is logarithmic instead of linearic.

 4.8 Special effects (Full channels)

     (Look for these in the next version)

4.10 Data requirements (in bytes per row per channel):

     Stripped channels.... 3 bytes
     Full channels........ 6 bytes
     Global channels...... 4 bytes

     If simple silence packing is implemented, this will be a lot less 
     in most instances. It probably won't, though, so no need to get 
     excited.
 

5. File format (Trackjoy saves and loads both songs and modules)


Trackjoy music file format version 2.0 (It *should* be impossible
to come across a version 1.X file, since this  the very first 
release of Trackjoy writes file version 2.0)

Format of song (.TJS) and module (.JOY)


OFFSET:       PURPOSE:
       
0-7           8 bytes: "TRACKJOY"
8             1 byte: 0=song, 1=module
9             1 byte: file version, 20 decimal (20 means 2.0)
10            2 bytes (reserved, but Trackjoy writes 0x99 and 0x52)
12            1 word: default tempo
14            1 word: tempo modifier (not used)
16            1 word: master volume (max. 256)
18            1 word: volume modifier (not used)
20            1 byte: transpose
21            1 byte (reserved)
22            1 word: number of directory entries
              Trackjoy can't write more than 196 (128 patterns,
              64 samples + 4), and even this is more than anybody is 
              usually going to use.

24            Directory
              [1 DWORD:PTR][1 BYTE:TAG][1 BYTE:UNUSED]

              <number of dir entries> of these

              The DWORD is a real byte-precision pointer to the object. 
              Objects will start at even byte offsets.

              The first byte (tag) in a directory entry defines the 
              object pointed to.

              Tags

              0  Writer information (text)
              1  Song name
              2  Song composer
              3  Song comment
              4  Pan positions
              5  Song order list
              6  Pattern
              7  Sample parameter block (no sample data)
              8  Sample parameter block + sample data

              All objects in the directory are sorted in ascending
              order by tag, ie., if song name exists, it will always
              be before song composer etc.

              The patterns come in the order in which they will be
              in memory. The order of sample depends on the sample
              slot (sample number) they go into, but Trackjoy writes 
              them in ascending order (by sample number).

              Trackjoy will always write the order first, then all the 
              actual patterns and finally sample parameter blocks (with 
              or without samples). Files written by Trackjoy can be read 
              according to the directory almost sequentially to minimise 
              the necessity for random access. However, it is legal for 
              any of the objects to reside anywhere in the file.

              Please remember that all objects start at even byte
              addresses, so there will be some padding bytes around
              in the file.

              Note that each song and module has at least one pattern 
              and the order array. There may not be any instruments, and 
              the song information text strings are optional.

              The only difference between a song and a module is
              the byte at offset 8 from the file's beginning and
              that in modules, the samples have tag 8 instead of 7.
              Of course, the *format* allows both types of sample in 
              either song or module, but Trackjoy doesn't support this 
              at least at the moment.

              Anything with a tag greater than 8 will promptly be
              ignored by Trackjoy.

From here on, offsets will be relative to the beginning of the object in 
question.

Format of writer information, song name, song composer and song comment:

0             1 word: length of string
2             data

              Trackjoy will ignore parts of strings exceeding 80 bytes.
              Trackjoy currently does not write "writer information"
              at all.

Format of pan positions

0             1 word: length of string, Trackjoy currently writes 18
2             Byte array. The value 0 means left, 100 means right.

Format of sample parameter block

0             1 byte: sample number (the instrument number in patterns)
1             name: 30            (ASCIIZ)
              filename: 13        (ASCIIZ)
              type: 1             (0,1,2)
              playmode: 1
              allocated-flag: 1   (used internally by Trackjoy)
              loop_beg: 4 (dw)
              loop_end: 4 (dw)
              length: 4 (dw)
              gus_off: 4 (dw)     (used internally by Trackjoy)
              freq: 2             (default 4000)
              sampvolume: 2       (max. 256)
              padding: 2  *** Not currently used ***
              
              Some sample (slot) numbers may not appear at all, so just 
              leave the left-over slots empty.


The format of a "sample parameter block + sample data" is exactly
the same as "sample parameter block" except that is is followed
by exactly <length> bytes of sample data, whether 8- or 16-bit.
<Length> is found within the parameter block.



Format of pattern:

0             1 word: rows (Trackjoy's maximum is 256, and
              the absolute maximum is 512)
2             1 byte: width in channels (for first pattern)
3             1 byte (reserved)
4             1 byte: compression scheme (0=None, 1=Silence)
5             33 bytes = format array (SEE NOTE BELOW)
38            1 word = actual length of written pattern data
              (length of uncompressed final pattern is computed
              from the format array, the width and the number
              of rows)

40            value at [38] bytes of pattern data, compressed
              or uncompressed


Format of order array:

0             1 word: length (always 128)
2             128 bytes: pattern play order array, FFh = empty


*** NOTE: Each byte of the format array indicates what type of channel is at
          that index. The value 0 is for FULL channels, 1 for STRIPPED 
          and 2 for GLOBAL channels. There are 33 bytes because TRACKJOY 
          is built so that it can (by modifying a #define directive) be 
          made to operate on max. 33 channels.



Pattern compression:

Three byte values are reserved for special purposes. They are,
in decimal, 233, 231 and 237. These values were chosen because
they are bigger than valid note, instrument or special command
values and are fairly unlikely to be entered as volumes by the
user.

Byte/byte pair  Interpretation

231             Repeat FFh twice
233             Repeat FFh three times
237,0           231
237,1           233
237,2           237
237,n when n>2  Repeat FFh n+1 times

In other words, 231 and 233 are always alone. 237 is always
followed by another byte. The actual values 231 and 233 are
encoded through 237. This ensures that the worst compression
ratio when compressing strips of FFh longer than 1 byte will
be 1:2. The best is 1:128. FFh is by far the most common
byte in Trackjoy patterns. It is not worth bothering about
other values, as the extra_compression / annoyance_of_
extra_programming factor is very low indeed, not to mention
that happiness_of_Tomi = 1 / Time_spent_in_extra_programming.

Note that pattern data is compressed COLUMN BY COLUMN,
not row by row. This yields significantly better compression ratios
than row-by-row compression, since the way data is entered into
a pattern is more uniform vertically than horizontally.

Example pattern:

       Let X be the width (in 1-byte parameter columns like <note>, 
       <ins> or <special command>) of the current pattern, and
       let there be 3 rows in our pattern.

         
    0+ 01234567...
         
    X+ ABCDEFGH...
         
   2X+ LMNOPQRS...
         

In this pattern, you would compress or uncompress in order 
0,A,L,1,B,M,2,C,N,3... What type of field you are compressing doesn't 
matter, but knowing the pattern width is important. Note that 
compression from column to column (for instance, from P to 5) is 
allowed. This compression scheme will of course only allow compression 
of strips max. 256 times FFh in length, at a time.

Uncompression example:

The string
<16><12><255><12><255><12><233><13><237><3><237><1>

uncompresses to

<16><12><255><12><255><12><255><255><255><13>
<255><255><255><255><233>.

For obvious reasons, things like

<231><231>
<231><233>
<231><237><64>
<233><255>

should never appear in the middle of a column. However, they are
perfectly correct and readable, and may occur at the very last column
of a pattern. Within a column, there are of course more logical ways of 
compressing these oddities. However, <237><255> followed by <255>, 
<231>, <233> or another <237><n> may appear anywhere if 257, 258 or more 
FFh's are compressed.

For technical reasons, Trackjoy actually rotates and flips a pattern's
data before compression, ie. a pattern is treated as a rectangular
byte block with a width of <columns> and height of <rows>.
Ie.,

abcd              aei
efgh    becomes   bfj   which is like a 90 degree rotate clockwise
ijkl              cgk   and a flip horizontal.
                  dhl

After the 90 degree rotate (and flip) a normal run-length crunching 
algorithm is used, and the output is saved to disk with the correct 
parameters. This is the easiest way to do it :)


5.2 Trackjoy block file format


Format of block file (.BLK)

OFFSET:       PURPOSE:
       
0             11 bytes: "RRRRR!"
              To understand why these first eleven bytes are 
              "RRRRR!", you really have to be an insider. Sorry 
              about that. (According to some not-very-reliable sources, 
              "RRRRR!", derived from the verb "rist", is the 
              closest ASCII equivalent of the the sound emitted from a 
              Class A Finnish "puliukko" on discovering that he has  in 
              one way or an other  lost contact with his best friend  
              the bottle of "Kossu". The puliukko is a species of 
              red-nosed scraggy-looking being completely pickled in 
              alcohol and found in abundance in the parks of Helsinki. 
              rrrrrrr!)

11            word: left
13            word: top
15            word: right
17            word: bottom
19            word: length
21            block data in linear uncompressed full channel format 
              (ie., channel 1 row 0 is right after channel 0 row 0)
              channel 0 row 0) END

5.3 Sample formats

5.3.1 Raw 8-bit PC8

This is the unsigned typical PC sample type with no header.
Each sample is simply a value from 0 to 255, and +128 represents
an output voltage of zero. It needs to be converted to signed
format for most purposes including volume scaling and playing
with the Ultrasound.

5.3.2 Signed raw 8-bit A8

This is the same as PC8 except that it is in signed two's
complement format. The values are signed integers from
-128 to +127, zero naturally represents an output volume
of zero.

5.3.3 Unsigned raw 16-bit S16

This is the 16-bit format Trackjoy currently reads. Samples are unsigned 
16-bit integers (words) with 32768 representing the output voltage of 
zero. Also this format is converted to signed 16-bit (by flipping bit 
15) before loading into the Ultrasound's memory. The byte order is 
low-high, ie. the least significant 8 bits of the value occupy the lower 
address. This is the Intel back-words integer storage format.


5.3.4 Trackjoy headered sample format

Format of TJINSX headered sample, version 1.1

OFFSET:       PURPOSE:
       
0             5 bytes: "TJINS"
5             1 byte: version number, 0Bh (11 decimal = 1.1)
              *** Ignore any files with this byte less than 0Bh.
6             16 bytes: "XXXXXXXXXXXXXXXX", reserved for future
              use, of course
22            sample information, precisely same format
              as in songs and modules (See song & module formats,
              offset x+1 or definition of "sampleinfo" structure)
90            sample data in signed format, low-high byte order
              for 16-bit samples, can be dumped directly into
              the Ultrasound and then played
END



5.3.5 Scream Tracker III / Digiplayer sample format

Trackjoy will read the basic Scream Tracker III sample format (8-bit 
mono), and Instrument maker will, in addition to reading these files, 
also write them. For a definition of the Scream Tracker III / Digiplayer 
sample format, consult the TECH.DOC file included in the public release 
package of Scream Tracker III.



 7. The interrupt interface


 Trackjoy supports an interrupt interface for other programs to control 
 music (or sound effect) output. The interface can also be used for 
 synchronising other programs with the player engine. TRACKJOY can
 hook itself to one of the interrupts between 60H  66H, namely int 61h.
 
 When the interface (int 61h) is called, AL contains the function 
 number. AL doesn't return a value. (W) means AH and BX contain 
 information to pass to TRACKJOY (functions 0h - Fh). (R) means another 
 program reads information from TRACKJOY, which returns it in the other 
 registers (AH, BX, CX and in a few cases, DX). Pointers and long ints 
 are dwords and are returned in the register pair BX:CX.

 The interrupt can be called either from an assembly language program by 
 simply setting the registers to the desired values or through one of 
 the interrupt interfaces supplied with your high-level compiler. 
 (Syntax for popular C-compilers for MS-DOS offer the following 
 interface:

 #include <dos.h>
 ...

 call_trackjoy(void)
 {
        union REGS inregs, outregs;
        unsigned interrupt_number = 0x61;

        inregs.h.al = 0x10; /* call "READ MUSIC CONTROL" */
        int86(interrupt_number, &inregs, &outregs);

        /* Data returned is in outregs.h.ah, outregs.h.bl,
           outregs.h.cl and outregs.h.ch, in this case. */
        ...
 }
 ...)


 These are the functions that are supported:

 ==============================================================================
 WRITE COMMANDS 00h - 09h, 20h - 2Fh
 ==============================================================================

 AL==00h  (W)  MUSIC CONTROL

		AH = Select function: 0 = none, 1 = play/stop, 2=pause/play

		1: play/stop - If BL == 0 TRACKJOY will stop
		2: play/pause - If BL == 0 TRACKJOY will deep-freeze.
		   Ie., the timer interrupt is un-hooked from TRACKJOY,
		   the timer is set to 18.2 Hz and all the GUS accumulators
		   are frozen. This is equivalent to a pause state.
		   BL == 1 melts TRACKJOY...
 ------------------------------------------------------------------------------

 AL==01h  (W)  MASTER VOLUME

		AH = new master volume (0-255)
 ------------------------------------------------------------------------------

 AL==02h  (W)  CHANNEL CONTROL

		AH = number of channel
		if CL == 1 TRACKJOY will mute channel <AH>
 ------------------------------------------------------------------------------

 AL==03h  (W)  TRANSPOSE

       AH = notes to transpose (if bit 7 set, transpose down)
 ------------------------------------------------------------------------------

 AL==04h  (W)  PITCH BEND

       new_global_pitch = global_pitch * BX / 1000;
 ------------------------------------------------------------------------------

 AL==05h  (W) SET PITCH

       BX = global pitch
 ------------------------------------------------------------------------------

 AL==06h  (W) SET GLOBAL TEMPO

       BX = new global tempo
 ------------------------------------------------------------------------------


 AL==07h  (W) FORCE TRACKJOY TO PLAY A PATTERN

	  AH = pattern to play (0 = first)
 ------------------------------------------------------------------------------


 AL==08h  (W) JUMP ROW

      AH = row to jump to in current pattern (0 = first)
 ------------------------------------------------------------------------------

 AL==09h  (W) END "P" COMMAND POLLING
      If TRACKJOY is polling for keyboard input (invoked by the
      "P" pattern command), issuing this command will make TRACKJOY
      continue to play the current pattern.

      *** NOTE: This command is read from AL==1Eh, *NOT* AL==19h!

 ------------------------------------------------------------------------------

 AL==20h  (W) PLAY NOTE

      AH = number of sample
      BL = note
      BH = GUS channel to play through

 ==============================================================================
 READ COMMANDS 10h - 1Fh, 30h
 ==============================================================================


 AL==10h  (R)  MUSIC CONTROL

        If AH bit 0 is OFF, music is stopped
        If TRACKJOY is running, BL == 'T' (54h) and  BH == 'J' (4Ah)
        CL = minor version number
		CH = major version number
                If DL bit 0 is ON, Trackjoy is paused
 ------------------------------------------------------------------------------

 AL==11h  (R)  MASTER VOLUME

        AH = master volume
 ------------------------------------------------------------------------------

 AL==12h  (R)  CHANNEL CONTROL

    (w) AH = number of channel
        BL = 1 for a short period, if a note was turned on
        BH = state of voice (1 = GUS is playing voice, 0 = stopped)
        CL = state of channel (1 means muted)

 ------------------------------------------------------------------------------

 AL==13h  (R)  TRANSPOSE

       AH = semitones transposed (if bit 7 set, transposed down)
 ------------------------------------------------------------------------------

 AL==14h  (RESERVED, Pitch Bend can't be read. Use AL==15h instead)
 ------------------------------------------------------------------------------


 AL==15h  (R) READ PITCH

       BX = global pitch
 ------------------------------------------------------------------------------

 AL==16h  (R) READ GLOBAL TEMPO

       BX = global tempo
 ------------------------------------------------------------------------------


 AL==17h  (R) READ CURRENT PATTERN AND ORDER

	  AH = current pattern (0 = first)
	  BX:CX points to order string (128 bytes)
	  DL = current order
 ------------------------------------------------------------------------------

 AL==18h  (R) READ CURRENT ROW

      AH = current row in pattern (0 = first)
      BL = rows in current pattern

 ------------------------------------------------------------------------------

 AL==19h  (R) GET "sampleinfo" STRUCTURE ARRAY ADDRESS

      struct sampleinfo sample[SAMPLES];
      BX:CX equals &sample[0]
 ------------------------------------------------------------------------------

 AL==1Ah  (R) GET "gdram" STRUCTURE ADDRESS

      struct gus_dram gdram;
      BX:CX equals &gdram
 ------------------------------------------------------------------------------

 AL==1Bh  (R) GET NAME ASCIIZ ADDRESS

      char name[80];
      BX:CX equals &name
 ------------------------------------------------------------------------------


 AL==1Ch  (R) GET COMPOSER ASCIIZ ADDRESS

      char composer[80];
      BX:CX equals &composer
 ------------------------------------------------------------------------------

 AL==1Dh  (R) GET COMMENT ASCIIZ ADDRESS

      char comment[80];
      BX:CX equals &comment
 ------------------------------------------------------------------------------

 AL==1Eh  (R) CHECK PATTERN POLLING STATE
      If AH==1, TRACKJOY is waiting for a interrupt with AL==09h
      to continue with the current pattern. This state is invoked
      with the "P" pattern command.

      *** NOTE: This command is written to AL==09h, *NOT* AL=0Eh!
 ------------------------------------------------------------------------------

 AL==1Fh   (R) READ ERROR LOG

      If AH != 0, TRACKJOY has an error to report
      BX:CX points to the error message string
 ------------------------------------------------------------------------------

 AL==30h   (R) READ TIMER
      BX:CX = (unsigned long) timer ticks from start of play
      DX = (unsigned int) timer frequency

 ------------------------------------------------------------------------------

This is the format of the SAMPLEINFO structure. It controls the audio 
samples TRACKJOY uses as instruments.

*** NOTE: The constant SAMPLES = 64.

struct sampleinfo {      /* sample information */
        char name[30];   /* sample description */
        char file[13];   /* sample filename */
        char type;       /* file type (16/PC8/A8) flag */
        char playmode;   /* this is what's actually squirted into the GUS */
        char acd;        /* allocated flag */
        long loopbeg;    /* loop beginning */
        long loopend;    /* loop end */
        long length;     /* sample length (bytes) */
        long gusoff;     /* offset in GUS memory */
        int freq;        /* sample frequency */
        unsigned vol;    /* sample volume */
        unsigned pad;    /* padding */
};

Storage definition in TRACKJOY.C:
struct sampleinfo sample[SAMPLES];


This is the format of the GUS_DRAM structure:

struct gus_dram {                       /* memory control block */
        unsigned long used;             /* how much memory is allocated */
        unsigned long beg[SAMPLES];     /* beginning of a block */
        unsigned long length[SAMPLES];  /* length of a block */
        char stat[SAMPLES];             /* status. 0 = empty, 1 = allocated,
                                                   2 = free */
        int c;                          /* number of blocks */
};

*** NOTE:  Status 0 (empty) always means the free hunk of memory from
           the end of the last allocated block to the end of memory.
           Status 1 means the block in use, and status 2 means the
           block has been freed, and can be re-used. Status 2 blocks
           only exist before allocated blocks of memory. (A Status 2
           block at the end of allocated memory is always converted to
           status 0 when the block is freed.)
        
Storage definition in TRACKJOY.C:
struct gus_dram gdram[4];

Each gdram in the above array is for a 256k Ultrasound memory bank.

