This document is intended to provide programmers with a FREE source of information about the programming of these sound cards.
The information contained in this document is a combination of information found in the Sound Blaster Software Developer's Kit, and that learned by painful experience. Some of the information may not be valid for AdLib cards; if this is so, I apologize in advance.
Please note that numbers will be given in hexadecimal, unless otherwise indicated. If a number is written out longhand (sixteen instead of 16) it is in decimal.
Chapter One - Sound Card I/O
The sound card is programmed by sending data to its internal registers via its two I/O ports:
0388 (hex) - Address/Status port (R/W) 0389 (hex) - Data port (W/O)The Sound Blaster Pro is capable of stereo FM music, which is accessed in exactly the same manner. Ports 0220 and 0221 (hex) are the address/ data ports for the left speaker, and ports 0222 and 0223 (hex) are the ports for the right speaker. Ports 0388 and 0389 (hex) will cause both speakers to output sound.
The sound card possesses an array of two hundred forty-four registers; to write to a particular register, send the register number (01-F5) to the address port, and the desired value to the data port.
After writing to the register port, you must wait twelve cycles before sending the data; after writing the data, eighty-four cycles must elapse before any other sound card operation may be performed.
The AdLib manual gives the wait times in microseconds: three point three (3.3) microseconds for the address, and twenty-three (23) microseconds for the data.
The most accurate method of producing the delay is to read the register port six times after writing to the register port, and read the register port thirty-five times after writing to the data port.
The sound card registers are write-only.
The address port also functions as a sound card status byte. To retrieve the sound card's status, simply read port 388. The status byte has the following structure:
7 6 5 4 3 2 1 0 +------+------+------+------+------+------+------+------+ | both | tmr | tmr | unused | | tmrs | 1 | 2 | | +------+------+------+------+------+------+------+------+ Bit 7 - set if either timer has expired. 6 - set if timer 1 has expired. 5 - set if timer 2 has expired.
Chapter Two - The Registers
The following table shows the function of each register in the sound card. Registers will be explained in detail after the table. Registers not listed are unused.
Address | Function |
01 | Test LSI / Enable waveform control |
02 | Timer 1 data |
03 | Timer 2 data |
04 | Timer control flags |
08 | Speech synthesis mode / Keyboard split note select |
20..35 | Amp Mod / Vibrato / EG type / Key Scaling / Multiple |
40..55 | Key scaling level / Operator output level |
60..75 | Attack Rate / Decay Rate |
80..95 | Sustain Level / Release Rate |
A0..A8 | Frequency (low 8 bits) |
B0..B8 | Key On / Octave / Frequency (high 2 bits) |
BD | AM depth / Vibrato depth / Rhythm control |
C0..C8 | Feedback strength / Connection type |
E0..F5 | Wave Select |
Channel 1 2 3 4 5 6 7 8 9 Operator 1 00 01 02 08 09 0A 10 11 12 Operator 2 03 04 05 0B 0C 0D 13 14 15Thus, the addresses of the attack/decay bytes for channel 3 are 62 for the first operator, and 65 for the second. (The address of the second operator is always the address of the first operator plus three).
To further illustrate the relationship, the addresses needed to control channel 5 are:
29 - Operator 1 AM/VIB/EG/KSR/Multiplier 2C - Operator 2 AM/VIB/EG/KSR/Multiplier 49 - Operator 1 KSL/Output Level 4C - Operator 2 KSL/Output Level 69 - Operator 1 Attack/Decay 6C - Operator 2 Attack/Decay 89 - Operator 1 Sustain/Release 8C - Operator 2 Sustain/Release A4 - Frequency (low 8 bits) B4 - Key On/Octave/Frequency (high 2 bits) C4 - Feedback/Connection Type E9 - Operator 1 Waveform EC - Operator 2 WaveformExplanations of Registers
Byte 01 - This byte is normally used to test the LSI device. All bits should normally be zero. Bit 5, if enabled, allows the FM chips to control the waveform of each operator. 7 6 5 4 3 2 1 0 +-----+-----+-----+-----+-----+-----+-----+-----+ | unused | WS | unused | +-----+-----+-----+-----+-----+-----+-----+-----+ Byte 02 - Timer 1 Data. If Timer 1 is enabled, the value in this register will be incremented until it overflows. Upon overflow, the sound card will signal a TIMER interrupt (INT 08) and set bits 7 and 6 in its status byte. The value for this timer is incremented every eighty (80) microseconds. Byte 03 - Timer 2 Data. If Timer 2 is enabled, the value in this register will be incremented until it overflows. Upon overflow, the sound card will signal a TIMER interrupt (INT 08) and set bits 7 and 5 in its status byte. The value for this timer is incremented every three hundred twenty (320) microseconds. Byte 04 - Timer Control Byte 7 6 5 4 3 2 1 0 +-----+-----+-----+-----+-----+-----+-----+-----+ | IRQ | T1 | T2 | unused | T2 | T1 | | RST | MSK | MSK | | CTL | CTL | +-----+-----+-----+-----+-----+-----+-----+-----+ bit 7 - Resets the flags for timers 1 & 2. If set, all other bits are ignored. bit 6 - Masks Timer 1. If set, bit 0 is ignored. bit 5 - Masks Timer 2. If set, bit 1 is ignored. bit 1 - When clear, Timer 2 does not operate. When set, the value from byte 03 is loaded into Timer 2, and incrementation begins. bit 0 - When clear, Timer 1 does not operate. When set, the value from byte 02 is loaded into Timer 1, and incrementation begins. Byte 08 - CSM Mode / Keyboard Split. 7 6 5 4 3 2 1 0 +-----+-----+-----+-----+-----+-----+-----+-----+ | CSM | Key | unused | | sel | Spl | | +-----+-----+-----+-----+-----+-----+-----+-----+ bit 7 - When set, selects composite sine-wave speech synthesis mode (all KEY-ON bits must be clear). When clear, selects FM music mode. bit 6 - Selects the keyboard split point (in conjunction with the F-Number data). The documentation in the Sound Blaster manual is utterly incomprehensible on this; I can't reproduce it without violating their copyright. Bytes 20-35 - Amplitude Modulation / Vibrato / Envelope Generator Type / Keyboard Scaling Rate / Modulator Frequency Multiple 7 6 5 4 3 2 1 0 +-----+-----+-----+-----+-----+-----+-----+-----+ | Amp | Vib | EG | KSR | Modulator Frequency | | Mod | | Typ | | Multiple | +-----+-----+-----+-----+-----+-----+-----+-----+ bit 7 - Apply amplitude modulation when set; AM depth is controlled by the AM-Depth flag in address BD. bit 6 - Apply vibrato when set; vibrato depth is controlled by the Vib-Depth flag in address BD. bit 5 - When set, the sustain level of the voice is maintained until released; when clear, the sound begins to decay immediately after hitting the SUSTAIN phase. bit 4 - Keyboard scaling rate. This is another incomprehensible bit in the Sound Blaster manual. From experience, if this bit is set, the sound's envelope is foreshortened as it rises in pitch. bits 3-0 - These bits indicate which harmonic the operator will produce sound (or modulation) in relation to the voice's specified frequency: 0 - one octave below 1 - at the voice's specified frequency 2 - one octave above 3 - an octave and a fifth above 4 - two octaves above 5 - two octaves and a major third above 6 - two octaves and a fifth above 7 - two octaves and a minor seventh above 8 - three octaves above 9 - three octaves and a major second above A - three octaves and a major third above B - " " " " " " " C - three octaves and a fifth above D - " " " " " " E - three octaves and a major seventh above F - " " " " " " " Bytes 40-55 - Level Key Scaling / Total Level 7 6 5 4 3 2 1 0 +-----+-----+-----+-----+-----+-----+-----+-----+ | Scaling | Total Level | | Level | 24 12 6 3 1.5 .75 | <-- dB +-----+-----+-----+-----+-----+-----+-----+-----+ bits 7-6 - causes output levels to decrease as the frequency rises: 00 - no change 10 - 1.5 dB/8ve 01 - 3 dB/8ve 11 - 6 dB/8ve bits 5-0 - controls the total output level of the operator. all bits CLEAR is loudest; all bits SET is the softest. Don't ask me why. Bytes 60-75 - Attack Rate / Decay Rate 7 6 5 4 3 2 1 0 +-----+-----+-----+-----+-----+-----+-----+-----+ | Attack | Decay | | Rate | Rate | +-----+-----+-----+-----+-----+-----+-----+-----+ bits 7-4 - Attack rate. 0 is the slowest, F is the fastest. bits 3-0 - Decay rate. 0 is the slowest, F is the fastest. Bytes 80-95 - Sustain Level / Release Rate 7 6 5 4 3 2 1 0 +-----+-----+-----+-----+-----+-----+-----+-----+ | Sustain Level | Release | | 24 12 6 3 | Rate | +-----+-----+-----+-----+-----+-----+-----+-----+ bits 7-4 - Sustain Level. 0 is the loudest, F is the softest. bits 3-0 - Release Rate. 0 is the slowest, F is the fastest. Bytes A0-B8 - Octave / F-Number / Key-On 7 6 5 4 3 2 1 0 +-----+-----+-----+-----+-----+-----+-----+-----+ | F-Number (least significant byte) | (A0-A8) | | +-----+-----+-----+-----+-----+-----+-----+-----+ 7 6 5 4 3 2 1 0 +-----+-----+-----+-----+-----+-----+-----+-----+ | Unused | Key | Octave | F-Number | (B0-B8) | | On | | most sig. | +-----+-----+-----+-----+-----+-----+-----+-----+ bit 5 - Channel is voiced when set, silent when clear. bits 4-2 - Octave (0-7). 0 is lowest, 7 is highest. bits 1-0 - Most significant bits of F-number. In octave 4, the F-number values for the chromatic scale and their corresponding frequencies would be: F Number Frequency Note 16B 277.2 C# 181 293.7 D 198 311.1 D# 1B0 329.6 E 1CA 349.2 F 1E5 370.0 F# 202 392.0 G 220 415.3 G# 241 440.0 A 263 466.2 A# 287 493.9 B 2AE 523.3 C Bytes C0-C8 - Feedback / Algorithm 7 6 5 4 3 2 1 0 +-----+-----+-----+-----+-----+-----+-----+-----+ | unused | Feedback | Alg | | | | | +-----+-----+-----+-----+-----+-----+-----+-----+ bits 3-1 - Feedback strength. If all three bits are set to zero, no feedback is present. With values 1-7, operator 1 will send a portion of its output back into itself. 1 is the least amount of feedback, 7 is the most. bit 0 - If set to 0, operator 1 modulates operator 2. In this case, operator 2 is the only one producing sound. If set to 1, both operators produce sound directly. Complex sounds are more easily created if the algorithm is set to 0. Byte BD - Amplitude Modulation Depth / Vibrato Depth / Rhythm 7 6 5 4 3 2 1 0 +-----+-----+-----+-----+-----+-----+-----+-----+ | AM | Vib | Rhy | BD | SD | TOM | Top | HH | | Dep | Dep | Ena | | | | Cym | | +-----+-----+-----+-----+-----+-----+-----+-----+ bit 7 - Set: AM depth is 4.8dB Clear: AM depth is 1 dB bit 6 - Set: Vibrato depth is 14 cent Clear: Vibrato depth is 7 cent bit 5 - Set: Rhythm enabled (6 melodic voices) Clear: Rhythm disabled (9 melodic voices) bit 4 - Bass drum on/off bit 3 - Snare drum on/off bit 2 - Tom tom on/off bit 1 - Cymbal on/off bit 0 - Hi Hat on/off Note: KEY-ON registers for channels 06, 07, and 08 must be OFF in order to use the rhythm section. Other parameters such as attack/decay/sustain/release must also be set appropriately. Bytes E0-F5 - Waveform Select 7 6 5 4 3 2 1 0 +-----+-----+-----+-----+-----+-----+-----+-----+ | unused | Waveform | | | Select | +-----+-----+-----+-----+-----+-----+-----+-----+ bits 1-0 - When bit 5 of address 01 is set, the output waveform will be distorted according to the waveform indicated by these two bits. I'll try to diagram them here, but this medium is fairly restrictive. ___ ___ ___ ___ _ _ / \ / \ / \ / \ / | / | /_____\_______ /_____\_____ /_____\/_____\ /__|___/__|___ \ / \___/ 00 01 10 11Detecting a Sound Card
According to the AdLib manual, the 'official' method of checking for a sound card is as follows:
Making a Sound
Many people have asked me, upon reading this document, what the proper register values should be to make a simple sound. Well, here they are.
First, clear out all of the registers by setting all of them to zero. This is the quick-and-dirty method of resetting the sound card, but it works. Note that if you wish to use different waveforms, you must then turn on bit 5 of register 1. (This reset need be done only once, at the start of the program, and optionally when the program exits, just to make sure that your program doesn't leave any notes on when it exits.)
Now, set the following registers to the indicated value:
REGISTER | VALUE | DESCRIPTION |
20 | 01 | Set the modulator's multiple to 1 |
40 | 10 | Set the modulator's level to about 40 dB |
60 | F0 | Modulator attack: quick; decay: long |
80 | 77 | Modulator sustain: medium; release: medium |
A0 | 98 | Set voice frequency's LSB (it'll be a D#) |
23 | 01 | Set the carrier's multiple to 1 |
43 | 00 | Set the carrier to maximum volume (about 47 dB) |
63 | F0 | Carrier attack: quick; decay: long |
83 | 77 | Carrier sustain: medium; release: medium |
B0 | 31 | Turn the voice on; set the octave and freq MSB |
INTRODUCTION
The Gravis Ultrasound is by far the best & easiest sound card to program. Why? Because the card does all the hard stuff for you, leaving you and the CPU to do other things! This reference will document some (but not all) of the Gravis Ultrasound's hardware functions, allowing you to play music & sound effects on your GUS.
We will not be going into great detail as to the theory behind everything - if you want to get technical information then read the GUS SDK. We will be merely providing you with the routines necessary to play samples on the GUS, and a basic explanation of how they work.
This document will NOT go into DMA transfer or MIDI specifications. If someone knows something about them, and would like to write some info on them, we would appreciate it very much.
All source code is in Pascal (tested under Turbo Pascal v7.0, but should work with TP 6.0 and possibly older versions). This document will assume reasonable knowledge of programming, and some knowledge of soundcards & music.
INITIALISATION & AUTODETECTION
Since we are not using DMA, we only need to find the GUS's I/O port, which can be done from the DOS environment space, or preferably from a routine that will scan all possible I/O ports until it finds a GUS.
The theory behind the detection routine is to store some values into GUS memory, and then read them back. If we have the I/O port correct, we will read back exactly what we wrote. So first, we need a routine that will write data to the memory of the GUS :
Function GUSPeek(Loc : Longint) : Byte; { Read a value from GUS memory } Var B : Byte; AddLo : Word; AddHi : Byte; Begin AddLo := Loc AND $FFFF; AddHi := LongInt(Loc AND $FF0000) SHR 16; Port [Base+$103] := $43; Portw[Base+$104] := AddLo; Port [Base+$103] := $44; Port [Base+$105] := AddHi; B := Port[Base+$107]; GUSPeek := B; End; Procedure GUSPoke(Loc : Longint; B : Byte); { Write a value into GUS memory } Var AddLo : Word; AddHi : Byte; Begin AddLo := Loc AND $FFFF; AddHi := LongInt(Loc AND $FF0000) SHR 16; Port [Base+$103] := $43; Portw[Base+$104] := AddLo; Port [Base+$103] := $44; Port [Base+$105] := AddHi; Port [Base+$107] := B; End;Since the GUS can have up to 1meg of memory, we need to use a 32bit word to address all possible memory locations. However, the hardware of the GUS will only accept a 24bit word, which means we have to change the 32bit address into a 24bit address. The first two lines of each procedure does exactly that.
The rest of the procedures simply send commands and data out through the GUS I/O port defined by the variable BASE (A word). So to test for the presence of the GUS, we simply write a routine to read/write memory for all possible values of BASE :
Function GUSProbe : Boolean; { Returns TRUE if there is a GUS at I/O address BASE } Var B : Byte; Begin Port [Base+$103] := $4C; Port [Base+$105] := 0; GUSDelay; GUSDelay; Port [Base+$103] := $4C; Port [Base+$105] := 1; GUSPoke(0, $AA); GUSPoke($100, $55); B := GUSPeek(0); If B = $AA then GUSProbe := True else GUSProbe := False; End; Procedure GUSFind; { Search all possible I/O addresses for the GUS } Var I : Word; Begin for I := 1 to 8 do Begin Base := $200 + I*$10; If GUSProbe then I := 8; End; If Base < $280 then Write('Found your GUS at ', Base, ' '); End;The above routines will obviously need to be customised for your own use - for example, setting a boolean flag to TRUE if you find a GUS, rather than just displaying a message.
It is also a good idea to find out exactly how much RAM is on the GUS, and this can be done in a similar process to the above routine. Since the memory can either be 256k, 512k, 768k or 1024k, all we have to do is to read/write values on the boundaries of these memory addresses. If we read the same value as we wrote, then we know exactly how much memory is available.
Function GUSFindMem : Longint; { Returns how much RAM is available on the GUS } Var I : Longint; B : Byte; Begin GUSPoke($40000, $AA); If GUSPeek($40000) <> $AA then I := $3FFFF else Begin GUSPoke($80000, $AA); If GUSPeek($80000) <> $AA then I := $8FFFF else Begin GUSPoke($C0000, $AA); If GUSPeek($C0000) <> $AA then I := $CFFFF else I := $FFFFF; End; End; GUSFindMem := I; End;Now that we know where the GUS is, and how much memory it has, we need to initialise it for output. Unfortunately, the below routine is slightly buggy. If you run certain programs (I discovered this after running Second Reality demo) that use the GUS, and then your program using this init routine, it will not initialise the GUS correctly.
It appears that I am not doing everything that is necessary to initialise the GUS. However, I managed to correct the problem by either re-booting (not a brilliant solution) or running Dual Module Player, which seems to initialise it properly. If someone knows where i'm going wrong, please say so!
Anyway, the following routine should be called after you have found the GUS, and before you start doing anything else with the GUS.
Procedure GUSDelay; Assembler; { Pause for approx. 7 cycles. } ASM mov dx, 0300h in al, dx in al, dx in al, dx in al, dx in al, dx in al, dx in al, dx End; Procedure GUSReset; { An incomplete routine to initialise the GUS for output. } Begin port [Base+$103] := $4C; port [Base+$105] := 1; GUSDelay; port [Base+$103] := $4C; port [Base+$105] := 7; port [Base+$103] := $0E; port [Base+$105] := (14 OR $0C0); End;Now you have all the routine necessary to find and initialise the GUS, let's see just what we can get the GUS to do!
MAKING SOUNDS
The GUS is unique in that it allows you to store the data to be played in it's onboard DRAM. To play the sample, you then tell it what frequency to play it at, what volume and pan position, and which sample to play. The GUS will then do everything in the background, it will interpolate the data to give an effective 44khz (or less, depending on how many active voices) sample. This means that an 8khz sample will sound better on the GUS than most other cards, since the GUS will play it at 44khz!
The GUS also has 32 seperate digital channels (that are mixed by a processor on the GUS) which all have their own individual samples, frequencies, volumes and panning positions. For some reason, however, the GUS can only maintain 44khz output with 16 channels - the more channels, the lower the playback rate (which basically means, lower quality). If you are using all 32 channels (unlikely), then playback is reduced to 22khz.
Since you allready know how to store samples in the GUS dram (simply use the GUSPoke routine to store the bytes) we will now look at various routines to change the way the gus plays a sample. The first routine we will look at will set the volume of an individual channel :
Procedure GUSSetVolume( Voi : Byte; Vol : Word); { Set the volume of channel VOI to Vol, a 16bit logarithmic scale volume value - 0 is off, $ffff is full volume, $e0000 is half volume, etc } Begin Port [Base+$102] := Voi; Port [Base+$102] := Voi; Port [Base+$102] := Voi; Port [Base+$103] := 9; Portw[Base+$104] := Vol; { 0-0ffffh, log scale not linear } End;The volume (and pan position & frequency) can be changed at ANY time regardless of weather the GUS is allready playing the sample or not. This means that to fade out a sample, you simply make several calls to the GUSSetVolume routine with exponentially (to account for the logarithmic scale) decreasing values.
The next two routines will set the pan position (from 0 to 15, 0 being left, 15 right and 7 middle) and the frequency respectively :
Procedure GUSSetBalance( V, B : Byte); Begin Port [Base+$102] := V; Port [Base+$102] := V; Port [Base+$102] := V; Port [Base+$103] := $C; Port [Base+$105] := B; End; Procedure GUSSetFreq( V : Byte; F : Word); Begin Port [Base+$102] := V; Port [Base+$102] := V; Port [Base+$102] := V; Port [Base+$103] := 1; Portw[Base+$104] := F; End;I'm not sure the the value F in the set frequency procedure. The GUS SDK claims that it is the exact frequency at which the sample should be played.
When playing a sample, it is necessary to set the volume, position and frequency BEFORE playing the sample. In order to start playing a sample, you need to tell the GUS where abouts in memory the sample is stored, and how big the sample is :
Procedure GUSPlayVoice( V, Mode : Byte;VBegin, VStart, VEnd : Longint); { This routine tells the GUS to play a sample commencing at VBegin, starting at location VStart, and stopping at VEnd } Var GUS_Register : Word; Begin Port [Base+$102] := V; Port [Base+$102] := V; Port [Base+$103] := $0A; Portw[Base+$104] := (VBegin SHR 7) AND 8191; Port [Base+$103] := $0B; Portw[Base+$104] := (VBegin AND $127) SHL 8; Port [Base+$103] := $02; Portw[Base+$104] := (VStart SHR 7) AND 8191; Port [Base+$103] := $03; Portw[Base+$104] := (VStart AND $127) SHL 8; Port [Base+$103] := $04; Portw[Base+$104] := ((VEnd) SHR 7) AND 8191; Port [Base+$103] := $05; Portw[Base+$104] := ((VEnd) AND $127) SHL 8; Port [Base+$103] := $0; Port [Base+$105] := Mode; { The below part isn't mentioned as necessary, but the card won't play anything without it! } Port[Base] := 1; Port[Base+$103] := $4C; Port[Base+$105] := 3; end;There are a few important things to note about this routine. Firstly, the value VEnd refers to the location in memory, not the length of the sample. So if the sample commenced at location 1000, and was 5000 bytes long, the VEnd would be 6000 if you wanted the sample to play to the end. VBegin and VStart are two weird values, one of them defines the start of the sample, and the other defines where abouts to actually start playing. I'm not sure why both are needed, since I have allways set them to the same value.
Now that the gus is buisy playing a sample, the CPU is totally free to be doing other things. We might, for example, want to spy on the gus and see where it is currently up to in playing the sample :
Function VoicePos( V : Byte) : Longint; Var P : Longint; Temp0, Temp1 : Word; Begin Port [Base+$102] := V; Port [Base+$102] := V; Port [Base+$102] := V; Port [Base+$103] := $8A; Temp0 := Portw[Base+$104]; Port [Base+$103] := $8B; Temp1 := Portw[Base+$104]; VoicePos := (Temp0 SHL 7)+ (Temp1 SHR 8); End;This routine will return the memory location that the channel V is currently playing. If the GUS has reached the end of the sample, then the returned value will be VEnd. If you want to see what BYTE value is currently being played (for visual output of the sample's waveform), then you simply PEEK the location pointed to by this routine.
Finally, we might want to stop playing the sample before it has reached it's end - the following routine will halt the playback on channel V.
Procedure GUSStopVoice( V : Byte); Var Temp : Byte; Begin Port [Base+$102] := V; Port [Base+$102] := V; Port [Base+$102] := V; Port [Base+$103] := $80; Temp := Port[Base+$105]; Port [Base+$103] := 0; Port [Base+$105] := (Temp AND $df) OR 3; GUSDelay; Port [Base+$103] := 0; Port [Base+$105] := (Temp AND $df) OR 3; End;SPECIAL EFFECTS
There are a few extra features of the GUS that are worthy of mention, the main one being hardware controlled sample looping. The GUS has a control byte for each of the 32 channels. This control byte consists of 8 flags that effect the way the sample is played, as follows : ( The table is taken directly from the GUS Software Developers Kit )
================================= | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | ================================= | | | | | | | | | | | | | | | +---- Voice Stopped | | | | | | +-------- Stop Voice | | | | | +------------ 16 bit data | | | | +---------------- Loop enable | | | +-------------------- Bi-directional loop enable | | +------------------------ Wave table IRQ | +---------------------------- Direction of movement +-------------------------------- IRQ pending
Procedure GUSVoiceControl( V, B : Byte); Begin Port [Base+$102] := V; Port [Base+$102] := V; Port [Base+$103] := $0; Port [Base+$105] := B; End;The above routine will set the Voice Control byte for the channel defined in V. For example, if you want channel 1 to play the sample in a continuous loop, you would use the procedure like this :
GUSVoiceControl( 1, $F ); { Bit 3 ON = $F }
CONCLUSION
The above routines are all that is necessary to get the GUS to start playing music. To prove this, I have included my 669 player & source code in the package as a practical example. The GUSUnit contains all the routines discussed above. I won't go into the theory of the 669 player, but it is a good starting point if you want to learn about modplayers. The player is contained within the archive 669UNIT.ARJ
HEADER (bytes 00-19)
Series of DATA BLOCKS (bytes 1A+) [Must end w/ Terminator Block]
byte # | Description |
00-12 | "Creative Voice File" |
13 | 1A (eof to abort printing of file) |
14-15 | Offset of first datablock in .voc file (std 1A 00 in Intel Notation) |
16-17 | Version number (minor,major) (VOC-HDR puts 0A 01) |
18-19 | 2's Comp of Ver. # + 1234h (VOC-HDR puts 29 11) |
Data Block: TYPE(1-byte), SIZE(3-bytes), INFO(0+ bytes) NOTE: Terminator Block is an exception -- it has only the TYPE byte.
TYPE | Description | Size (3-byte int) | Info | |
00 | Terminator | (NONE) | (NONE) | |
01 | Sound data | 2+length of data | * | |
02 | Sound continue | length of data | Voice Data | |
03 | Silence | 3 | ** | |
04 | Marker | 2 | Marker# (2 bytes) | |
05 | ASCII | length of string | null terminated string | |
06 | Repeat | 2 | Count# (2 bytes) | |
07 | End repeat | 0 | (NONE) | |
08 | Extended | 4 | *** | |
*Sound Info Format: **Silence Info Format: --------------------- ---------------------------- 00 Sample Rate 00-01 Length of silence - 1 01 Compression Type 02 Sample Rate 02+ Voice Data ***Extended Info Format: --------------------- 00-01 Time Constant: Mono: 65536 - (256000000/sample_rate) Stereo: 65536 - (25600000/(2*sample_rate)) 02 Pack 03 Mode: 0 = mono 1 = stereo Marker# -- Driver keeps the most recent marker in a status byte Count# -- Number of repetitions + 1 Count# may be 1 to FFFE for 0 - FFFD repetitions or FFFF for endless repetitions Sample Rate -- SR byte = 256-(1000000/sample_rate) Length of silence -- in units of sampling cycle Compression Type -- of voice data 8-bits = 0 4-bits = 1 2.6-bits = 2 2-bits = 3 Multi DAC = 3+(# of channels) [interesting-- this isn't in the developer's manual]
1. Overview
A midi (.MID) file contains basically 2 things, Header chunks and Track chunks. Section 2 explains the header chunks, and Section 3 explains the track chunks. A midi file contains ONE header chunk describing the file format, etc., and any number of track chunks. A track may be thought of in the same way as a track on a multi-track tape deck. You may assign one track to each voice, each staff, each instrument or whatever you want. 2. Header Chunk
The header chunk appears at the beginning of the file, and describes the file in three ways. The header chunk always looks like:
4D 54 68 64 00 00 00 06 ff ff nn nn dd ddThe ascii equivalent of the first 4 bytes is MThd. After MThd comes the 4-byte size of the header. This will always be 00 00 00 06, because the actual header information will always be 6 bytes.
ff ff is the file format. There are 3 formats:
0 - single-track
1 - multiple tracks, synchronous
2 - multiple tracks, asynchronous
Single track is fairly self-explanatory - one track only. Synchronous multiple tracks means that the tracks will all be vertically synchronous, or in other words, they all start at the same time, and so can represent different parts in one song. Asynchronous multiple tracks do not necessarily start at the same time, and can be completely asynchronous.
nn nn is the number of tracks in the midi file.
dd dd is the number of delta-time ticks per quarter note. (More about this
later)
3. Track Chunks
The remainder of the file after the header chunk consists of track chunks. Each track has one header and may contain as many midi commands as you like. The header for a track is very similar to the one for the file:
4D 54 72 6B xx xx xx xxAs with the header, the first 4 bytes has an ascii equivalent. This one is MTrk. The 4 bytes after MTrk give the length of the track (not including the track header) in bytes.
Following the header are midi events. These events are identical to the actual data sent and received by MIDI ports on a synth with one addition. A midi event is preceded by a delta-time. A delta time is the number of ticks after which the midi event is to be executed. The number of ticks per quarter note was defined previously in the file header chunk. This delta-time is a variable-length encoded value. This format, while confusing, allows large numbers to use as many bytes as they need, without requiring small numbers to waste bytes by filling with zeros. The number is converted into 7-bit bytes, and the most-significant bit of each byte is 1 except for the last byte of the number, which has a msb of 0. This allows the number to be read one byte at a time, and when you see a msb of 0, you know that it was the last (least significant) byte of the number. According to the MIDI spec, the entire delta- time should be at most 4 bytes long.
Following the delta-time is a midi event. Each midi event (except a running midi event) has a command byte which will always have a msb of 1 (the value will be >= 128). A list of most of these commands is in appendix A. Each command has different parameters and lengths, but the data that follows the command will have a msb of 0 (less than 128). The exception to this is a meta- event, which may contain data with a msb of 1. However, meta-events require a length parameter which alleviates confusion.
One subtlety which can cause confusion is running mode. This is where the actual midi command is omitted, and the last midi command issued is assumed. This means that the midi event will consist of a delta-time and the parameters that would go to the command if it were included. 4. Conclusion If this explanation has only served to confuse the issue more, the appendices contain examples which may help clarify the issue.
Appendix A
1. MIDI Event Commands
Each command byte has 2 parts. The left nybble (4 bits) contains the actual command, and the right nybble contains the midi channel number on which the command will be executed. There are 16 midi channels, and 8 midi commands (the command nybble must have a msb of 1). In the following table, x indicates the midi channel number. Note that all data bytes will be <128 (msb set to 0).
Hex | Binary | Data | Description |
8x | 1000xxxx | nn vv | Note off (key is released) nn=note number vv=velocity |
9x | 1001xxxx | nn vv | Note on (key is pressed) nn=note number vv=velocity |
Ax | 1010xxxx | nn vv | Key after-touch nn=note number vv=velocity |
Bx | 1011xxxx | cc vv | Control Change cc=controller number vv=new value |
Cx | 1100xxxx | pp | Program (patch) change pp=new program number |
Dx | 1101xxxx | cc | Channel after-touch cc=channel number |
Ex | 1110xxxx | bb tt | Pitch wheel change (2000H is normal or no
change) bb=bottom (least sig) 7 bits of value tt=top (most sig) 7 bits of value |
The following table lists meta-events which have no midi channel number. They are of the format:
FF xx nn dd
All meta-events start with FF followed by the command (xx), the length, or number of bytes that will contain data (nn), and the actual data (dd).
Hex | Binary | Data | Description |
00 | 00000000 | nn ssss | Sets the track's sequence number. nn=02 (length of 2-byte sequence number) ssss=sequence number |
01 | 00000001 | nn tt .. | Text event- any text you want. nn=length in bytes of text tt=text characters |
02 | 00000010 | nn tt .. | Same as text event, but used for
copyright info. nn tt=same as text event |
03 | 00000011 | nn tt .. | Sequence or Track name nn tt=same as text event |
04 | 00000100 | nn tt .. | Track instrument name nn tt=same as text event |
05 | 00000101 | nn tt .. | Lyric nn tt=same as text event |
06 | 00000110 | nn tt .. | Marker nn tt=same as text event |
07 | 00000111 | nn tt .. | Cue point nn tt=same as text event |
2F | 00101111 | 00 | This event must come at the end of each track |
51 | 01010001 | 03 tttttt | Set tempo tttttt=microseconds/quarter note |
58 | 01011000 | 04 nn dd ccbb | Time Signature nn=numerator of time sig. dd=denominator of time sig. 2=quarter 3=eighth, etc. cc=number of ticks in metronome click bb=number of 32nd notes to the quarter note |
59 | 01011001 | 02 sf mi | Key signature sf=sharps/flats (-7=7 flats, 0=key of C, 7=7 sharps) mi=major/minor (0=major, 1=minor) |
7F | 01111111 | xx dd .. | Sequencer specific information
xx=number of bytes to be sent dd=data |
Hex | Binary | Data | Description |
F8 | 11111000 | Timing clock used when synchronization is required. | |
FA | 11111010 | Start current sequence | |
FB | 11111011 | Continue a stopped sequence where left off | |
FC | 11111100 | Stop a sequence | |
Octave # | Note Numbers | |||||||||||
C | C# | D | D# | E | F | F# | G | G# | A | A# | B | |
0 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
1 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
2 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
3 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
4 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
5 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
6 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 |
7 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 |
8 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 |
9 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 |
10 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 |
BIBLIOGRAPHY
"MIDI Systems and Control" Francis Rumsey 1990 Focal Press
"MIDI and Sound Book for the Atari ST" Bernd Enders and Wolfgang Klemme
1989 M&T Publishing, Inc.
MIDI file specs and general MIDI specs were also obtained by sending e-mail
to LISTSERV@AUVM.AMERICAN.EDU with the phrase GET MIDISPEC PACKAGE
in the message.
Because the .WAV article is really big (the size of The QB Times without the article!) I've decided to put it in a seprate file. Go here if you want to read the .WAV article.
Offset Bytes Description 0 20 Songname. Remember to put trailing null bytes at the end... Information for sample 1-31: Offset Bytes Description 20 22 Samplename for sample 1. Pad with null bytes. 42 2 Samplelength for sample 1. Stored as number of words. Multiply by two to get real sample length in bytes. 44 1 Lower four bits are the finetune value, stored as a signed four bit number. The upper four bits are not used, and should be set to zero. Value: Finetune: 0 0 1 +1 2 +2 3 +3 4 +4 5 +5 6 +6 7 +7 8 -8 9 -7 A -6 B -5 C -4 D -3 E -2 F -1 45 1 Volume for sample 1. Range is $00-$40, or 0-64 decimal. 46 2 Repeat point for sample 1. Stored as number of words offset from start of sample. Multiply by two to get offset in bytes. 48 2 Repeat Length for sample 1. Stored as number of words in loop. Multiply by two to get replen in bytes.Information for the next 30 samples starts here. It's just like the info for sample 1.
Offset Bytes Description 50 30 Sample 2... 80 30 Sample 3... . . . 890 30 Sample 30... 920 30 Sample 31... Offset Bytes Description 950 1 Songlength. Range is 1-128. 951 1 Well... this little byte here is set to 127, so that old trackers will search through all patterns when loading. Noisetracker uses this byte for restart, but we don't. 952 128 Song positions 0-127. Each hold a number from 0-63 that tells the tracker what pattern to play at that position. 1080 4 The four letters "M.K." - This is something Mahoney & Kaktus inserted when they increased the number of samples from 15 to 31. If it's not there, the module/song uses 15 samples or the text has been removed to make the module harder to rip. Startrekker puts "FLT4" or "FLT8" there instead. Offset Bytes Description 1084 1024 Data for pattern 00. . . . xxxx Number of patterns stored is equal to the highest patternnumber in the song position table (at offset 952-1079).Each note is stored as 4 bytes, and all four notes at each position in the pattern are stored after each other.
00 - chan1 chan2 chan3 chan4 01 - chan1 chan2 chan3 chan4 02 - chan1 chan2 chan3 chan4 etc.Info for each note:
_____byte 1_____ byte2_ _____byte 3_____ byte4_ / / / / 0000 0000-00000000 0000 0000-00000000 Upper four 12 bits for Lower four Effect command. bits of sam- note period. bits of sam- ple number. ple number. Periodtable for Tuning 0, Normal C-1 to B-1 : 856,808,762,720,678,640,604,570,538,508,480,453 C-2 to B-2 : 428,404,381,360,339,320,302,285,269,254,240,226 C-3 to B-3 : 214,202,190,180,170,160,151,143,135,127,120,113To determine what note to show, scan through the table until you find the same period as the one stored in byte 1-2. Use the index to look up in a notenames table.
This is the data stored in a normal song. A packed song starts with the four letters "PACK", but i don't know how the song is packed: You can get the source code for the cruncher/decruncher from us if you need it, but I don't understand it; I've just ripped it from another tracker...
In a module, all the samples are stored right after the patterndata. To determine where a sample starts and stops, you use the sampleinfo structures in the beginning of the file (from offset 20). Take a look at the mt_init routine in the playroutine, and you'll see just how it is done.
EFFECT COMMANDS
Effect commands on protracker should be compatible with all other trackers.
0 - None/Arpeggio 8 - * NOT USED * 1 - Portamento Up 9 - SampleOffset 2 - Portamento Down A - VolumeSlide 3 - TonePortamento B - PositionJump 4 - Vibrato C - Set Volume 5 - ToneP + VolSlide D - PatternBreak 6 - Vibra + VolSlide E - Misc. Cmds 7 - Tremolo F - Set SpeedE - COMMANDS
The E command has been altered to contain more commands than one.
E0- Filter On/Off E8- * NOT USED * E1- Fineslide Up E9- Retrig Note E2- Fineslide Down EA- FineVol Up E3- Glissando Control EB- FineVol Down E4- Vibrato Control EC- NoteCut E5- Set Finetune ED- NoteDelay E6- Patternloop EE- PatternDelay E7- Tremolo Control EF- Invert LoopCmd 0. Arpeggio [Range:$0-$F/$0-$F]
Usage: $0 + 1st halfnote add + 2nd halfnote add Arpeggio is used to simulate chords. This is done by rapidly changing the pitch between 3(or 2) different notes. It sounds very noisy and grainy on most samples, but ok on monotone ones. Example: C-300047 C-major chord: (C+E+G or C+4+7 halfnotes) C-300037 C-minor chord: (C+D#+G or C+3+7 halfnotes)
Cmd 1. Portamento up [Speed:$00-$FF]
Usage: $1 + portamento speed Portamento up will simply slide the sample pitch up. You can NOT slide higher than B-3! (Period 113) Example: C-300103 1 is the command, 3 is the portamentospeed. NOTE: The portamento will be called as many times as the speed of the song. This means that you'll sometimes have trouble sliding accuratly. If you change the speed without changing the sliderates, it will sound bad...
Cmd 2. Portamento down [Speed:$00-FF]
Usage: $2 + portamento speed Just like command 1, except that this one slides the pitch down instead. (Adds to the period). You can NOT slide lower than C-1! (Period 856) Example: C-300203 2 is the command, 3 is the portamentospeed.
Cmd 3. Tone-portamento [Speed:$00-$FF]
Usage: Dest-note + $3 + slidespeed This command will automatically slide from the old note to the new. You don't have to worry about which direction to slide, you need only set the slide speed. To keep on sliding, just select the command $3 + 00. Example: A-200000 First play a note. C-300305 C-3 is the note to slide to, 3 the command, and 5 the speed.
Cmd 4. Vibrato [Rate:$0-$F,Dpth:$0-$F]
Usage: $4 + vibratorate + vibratodepth Example: C-300481 4 is the command, 8 is the speed of the vibrato, and 1 is the depth of the vibrato. To keep on vibrating, just select the command $4 + 00. To change the vibrato, you can alter the rate, depth or both. Use command E4- to change the vibrato-waveform.
Cmd 5. ToneP + Volsl [Spd:$0-$F/$0-$F]
Usage: $5 + upspeed + downspeed This command will continue the current toneportamento and slide the volume at the same time. Stolen from NT2.0. Example: C-300503 3 is the speed to turn the volume down. C-300540 4 is the speed to slide it up.
Cmd 6. Vibra + Volsl [Spd:$0-$F/$0-$F]
Usage: $6 + upspeed + downspeed This command will continue the current vibrato and slide the volume at the same time. Stolen from NT2.0. Example: C-300605 5 is the speed to turn the volume down. C-300640 4 is the speed to slide it up.
Cmd 7. Tremolo [Rate:$0-$F,Dpth:$0-$F]
Usage: $7 + tremolorate + tremolodepth Tremolo vibrates the volume. Example: C-300794 7 is the command, 9 is the speed of the tremolo, and 4 is the depth of the tremolo. To keep on tremoling, just select the command $7 + 00. To change the tremolo, you can alter the rate, depth or both. Use command E7- to change the tremolo-waveform.
Cmd 9. Set SampleOffset [Offs:$00-$FF]
Usage: $9 + Sampleoffset This command will play from a chosen position in the sample, and not from the beginning. The two numbers equal the two first numbers in the length of the sample. Handy for speech- samples. Example: C-300923 Play sample from offset $2300.
Cmd A. Volumeslide [Speed:$0-$F/$0-$F]
Usage: $A + upspeed + downspeed Example: C-300A05 5 is the speed to turn the volume down. C-300A40 4 is the speed to slide it up. NOTE: The slide will be called as many times as the speed of the song. The slower the song, the more the volume will be changed on each note.
Cmd B. Position-jump [Pos:$00-$7F]
Usage: $B + position to continue at Example: C-300B01 B is the command, 1 is the position to restart the song at. This command will also perform a pattern-break (see 2 pages below). You can use this command instead of restart as on noisetracker, but you must enter the position in hex!
Cmd C. Set volume [Volume:$00-$40]
Usage: $C + new volume Well, this old familiar command will set the current volume to your own selected. The highest volume is $40. All volumes are represented in hex. (Programmers do it in hex, you know!) Example: C-300C10 C is the command, 10 is the volume (16 decimal).
Cmd D. Pattern-break [Pattern-pos:00-63, decimal]
Usage: $D + pattern-position This command just jumps to the next song-position, and continues play from the patternposition you specify. Example: C-300D00 Jump to the next song-position and continue play from patternposition 00. Or: C-300D32 Jump to the next song-position and continue play from patternposition 32 instead.
Cmd E0. Set filter [Range:$0-$1]
Usage: $E0 + filter-status This command jerks around with the sound-filter on some A500 + A2000. All other Amiga-users should keep out of playing around with it. Example: C-300E01 disconnects filter (turns power LED off) C-300E00 connects filter (turns power LED on)
Cmd E1. Fineslide up [Range:$0-$F]
Usage: $E1 + value This command works just like the normal portamento up, except that it only slides up once. It does not continue sliding during the length of the note. Example: C-300E11 Slide up 1 at the beginning of the note. (Great for creating chorus effects)
Cmd E2. Fineslide down [Range:$0-$F]
Usage: $E2 + value This command works just like the normal portamento down, except that it only slides down once. It does not continue sliding during the length of the note. Example: C-300E26 Slide up 6 at the beginning of the note.
Cmd E3. Glissando Ctrl [Range:$0-$1]
Usage: $E3 + Glissando-Status Glissando must be used with the tone- portamento command. When glissando is activated, toneportamento will slide a halfnote at a time, instead of a straight slide. Example: C-300E31 Turn Glissando on. C-300E30 Turn Glissando off.
Cmd E4. Set vibrato waveform [Range:$0-$3]
Usage: $E4 + vibrato-waveform Example: C-300E40 Set sine(default) E44 Don't retrig WF C-300E41 Set Ramp Down E45 Don't retrig WF C-300E42 Set Squarewave E46 Don't retrig WF C-300E43 Set Random E47 Don't retrig WF
Cmd E5. Set finetune [Range:$0-$F]
Usage: $E5 + finetune-value Example: C-300E51 Set finetune to 1. Use these tables to figure out the finetune-value. Finetune: +7 +6 +5 +4 +3 +2 +1 0 Value: 7 6 5 4 3 2 1 0 Finetune: -1 -2 -3 -4 -5 -6 -7 -8 Value: F E D C B A 9 8
Cmd E6. PatternLoop [Loops:$0-$F]
Usage: $E6 + number of loops This command will loop a part of a pattern. Example: C-300E60 Set loopstart. C-300E63 Jump to loop 3 times before playing on.
Cmd E7. Set tremolo waveform
[Range:$0-$3] Usage: $E7 + tremolo-waveform Example: C-300E70 Set sine(default) E74 Don't retrig WF C-300E71 Set Ramp Down E75 Don't retrig WF C-300E72 Set Squarewave E76 Don't retrig WF C-300E73 Set Random E77 Don't retrig WF
Cmd E9. Retrig note [Value:$0-$F]
Usage: $E9 + Tick to Retrig note at. This command will retrig the same note before playing the next. Where to retrig depends on the speed of the song. If you retrig with 1 in speed 6 that note will be trigged 6 times in one note slot. Retrig on hi-hats! Example: C-300F06 Set speed to 6. C-300E93 Retrig at tick 3 out of 6.
Cmd EA. FineVolsl up [Range:$0-$F]
Usage: $EA + value This command works just like the normal volumeslide up, except that it only slides up once. It does not continue sliding during the length of the note. Example: C-300EA3 Slide volume up 1 at the beginning of the note.
Cmd EB. FineVolsl down [Range:$0-$F]
Usage: $EB + value This command works just like the normal volumeslide down, except that it only slides down once. It does not continue sliding during the length of the note. Example: C-300EB6 Slide volume down 6 at the beginning of the note.
Cmd EC. Cut note [Value:$0-$F]
Usage: $EC + Tick to Cut note at. This command will cut the note at the selected tick, creating extremely short notes. Example: C-300F06 Set speed to 6. C-300EC3 Cut at tick 3 out of 6. Note that the note is not really cut, the volume is just turned down.
Cmd ED. NoteDelay [Value:$0-$F]
Usage: $ED + ticks to delay note. This command will delay the note to the selected tick. Example: C-300F06 Set speed to 6. C-300ED3 Play note at tick 3 out of 6.
Cmd EE. PatternDelay [Notes:$0-$F]
Usage: $EE + notes to delay pattern. This command will delay the pattern the selected numbers of notes. Example: C-300EE8 Delay pattern 8 notes before playing on. All other effects are still active when the pattern is being delayed.
Cmd EF. Invert Loop [Speed:$0-$F]
Usage: $EF + Invertspeed This command will need a short loop ($10,20,40,80 etc. bytes) to work. It will invert the loop byte by byte. Sounds better than funkrepeat... Example: C-300EF8 Set invspeed to 8. To turn off the inverting, set invspeed to 0, or press ctrl + Z.
Cmd F. Set speed [Speed:$00-$FF]
Usage: $F + speed This command will set the speed of the song. Note:
The 6 and 8 channel mod files differ from the normal mods in two ways:
1) The signature string "M.K." at offset 1080 is either "6CHN" or "8CHN". 2) The pattern data table starting at offset 1084 stores 6 or 8 notes for each pattern position position.
© Copyright 1999-2000 The QB Times.
This tutorial was taken from The QB Times with permission from the webmaster(s) of the site. You may not take this article and place it on any other website.