THE OFFICAL
GRAVIS ULTRASOUND PROGRAMMERS ENCYCLOPEDIA
( G.U.P.E )
v 0.1
Written by Mark Dixon.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
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
(*)Bit 0 = 1 : Voice is
stopped. This gets set by hitting the end
address (not looping) or by setting bit 1 in this reg.
Bit 1
= 1 : Stop Voice. Manually force voice to stop.
Bit 2
= 1 : 16 bit wave data, 0 = 8 bit data
Bit 3
= 1 : Loop to begin address when it hits the end address.
Bit 4
= 1 : Bi-directional looping enabled
Bit 5
= 1 : Enable wavetable IRQ. Generate an irq when the voice
hits the end address. Will generate irq even if looping
is enabled.
(*)Bit 6 = 1 - Decreasing
addresses, 0 = increasing addresses. It is
self-modifying because it might shift directions when
it hits one of the loop boundaries and looping is enabled.
(*)Bit 7 = 1 - Wavetable
IRQ pending. If IRQ's are enabled and
looping is NOT enabled, an IRQ will be constantly
generated until voice is stopped. This means that
you may get more than 1 IRQ if it isn't handled
properly.
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
ÚÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
³ README ³
ÀÄÄÄÄÄÄÄÄÙ
GUS669 Unit v0.2b
Copyright 1994 Mark Dixon.
(aka C.D. of Silicon Logic)
LEGAL STUFF
~~~~~~~~~~~
I'd like to avoid this, but it has to be done. Basically, if
anything
in this archive causes any kind of damage, I cannot be held
responsable - USE AT YOUR OWN RISK.
In adition, since I spent long hours working on this project,
and
attempting to decode the GUS SDK, I would appreciate it if people
didn't rip off my work. Give me credit for what I have done,
and if
your planning to use my routines for commercial purposes, talk
to me
first, or you might find yourself on the wrong side of a legal
battle.
(Hey, let's sound tough while i'm at it, I have lawyer's in
the
family, so it's not gonna cost me much to sue someone. And don't
criticise my spelling! :)
BORING STUFF
~~~~~~~~~~~~
Well, if your the sort of person who likes to ignore all the
rubishy
bits that go into a README text file, then you'd better stop
now and
go and try out the source code!
Basically, this readme isn't going to say much more than what
the
source code is, and then go dribling on for five pages about
absolutely nothing.
SOURCE CODE! DID SOMEONE SAY - SOURCE CODE!! - ????
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Yes, that's right, free with every download of this wonderful
archive
comes the complete Pascal source code to a 669 module player
for the
GUS. I'd have included my MOD player, but I haven't been able
to get
all the MOD commands working, so you'll just have to make do
with a
669 player :)
Feel free to make use of this source code for any non-commercial
purposes you might be able to think of - and mention my name
while
your at it! Since the source code is here, people are bound
to modify
it for their personal uses. If you do this, I would very much
like to
see your modifications - so that I can include them in the next
release of the player.
Well, I don't want to bore you anymore, and it's getting late
(not!)
so i'd better let you go and play around with the source code
:)
SILICON LOGIC
~~~~~~~~~~~~~
What ever happened to Silicon Logic? Well, after being killed
off over
in Perth, a major revival is underway here in Canberra, with
a more
commercial view - more on that later.
For those of you who have never heard of Silicon Logic, then
you're
either not Australian, or not into the ausie demo scene. But
then,
that covers about 99.999999999999% of the world population :)
GREETINGS
~~~~~~~~~
I've allways wanted to dribble some thanks, so here goes.
Thanks go to...
Darren Lyon - Who got me into this
programming lark in the first
place. Finally wrote myself a mod player :)
Tran
- Your source code really helped!
Kitsune
- Love those mods, keep up the good work!
... and Advanced Gravis, for making the best sound card ever.
Greetings to...
FiRE members - I'll probably never join
you guys, but good luck
anyway!
UNiQUE
- How's the board going?
CRaSH
- Still ripping other peoples source code?
Old SL members - Thanks for the support, good luck
with your new
group!
Oliver White - G'day... just thought
i'd say hi, since you so
kindly beta tested the player for me.
Murray Head - Rick Price sux!
:-) SoundBlaster sux too! :-)
Perth people - I'm coming back... someday!
THE PICK / MINNOW - Hey, give me
a call sometime, long time no
talk...
INTERESTED IN A DEMO GROUP IN CANBERRA?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If there is anyone interested in joining a demo / coding group
in
Canberra (ACT), then drop me a line.
ÚÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
³ GUSUNIT.PAS³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ
Unit GUSUnit;
{
GUS DigiUnit v1.0
Copyright 1994 Mark Dixon.
This product is "Learnware".
All contents of this archive, including source and executables,
are the
intellectual property of the author, Mark Dixon. Use of this
product for
commercial programs, or commercial gain in ANY way, is illegal.
Private
use, or non-commercial use (such as demos, PD games, etc) is
allowed,
provided you give credit to the author for these routines.
Feel free to make any modifications to these routines, but I
would
appreciate it if you sent me these modifications, so that I
can include
them in the next version of the Gus669 Unit.
If you wish to use these routines for commercial purposes, then
you will
need a special agreement. Please contact me, Mark Dixon, and
we can work
something out.
What's "Learnware"? Well, I think I just made it up actually.
What i'm
getting at is that the source code is provided for LEARNING
purposes only.
I'd get really angry if someone ripped off my work and tried
to make out
that they wrote a mod player.
As of this release (Gus699 Unit), the Gus DigiUnit has moved
to version
1.0, and left the beta stage. I feel these routines are fairly
sound,
and I haven't made any changes to them in weeks.
Notice the complete absence of comments here? Well, that's partially
the fault of Gravis and their SDK, since it was so hard to follow,
I
was more worried about getting it working than commenting it.
No offense
to Gravis though, since they created this wonderful card! :-)
It helps
a lot if you have the SDK as a reference when you read this
code,
otherwise you might as well not bother reading it.
}
INTERFACE
Procedure GUSPoke(Loc : Longint; B : Byte);
Function GUSPeek(Loc : Longint) : Byte;
Procedure GUSSetFreq( V : Byte; F : Word);
Procedure GUSSetBalance( V, B : Byte);
Procedure GUSSetVolume( Voi : Byte; Vol : Word);
Procedure GUSPlayVoice( V, Mode : Byte;VBegin, VStart, VEnd : Longint);
Procedure GUSVoiceControl( V, B : Byte);
Procedure GUSReset;
Function VoicePos( V : Byte) : Longint;
Const
Base : Word = $200;
Mode : Byte = 0;
IMPLEMENTATION
Uses Crt;
Function Hex( W : Word) : String;
Var
I, J : Word;
S : String;
C : Char;
Const
H : Array[0..15] of Char = ('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
Begin
S := '';
S := S + H[(W DIV $1000) MOD 16];
S := S + H[(W DIV $100 ) MOD 16];
S := S + H[(W DIV $10 ) MOD 16];
S := S + H[(W DIV $1 ) MOD 16];
Hex := S+'h';
End;
Procedure GUSDelay; Assembler;
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;
Function VoicePos( V : Byte) : Longint;
Var
P : Longint;
I, Temp0, Temp1 : Word;
Begin
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);
For I := 1 to 10 do GusDelay;
End;
Function GUSPeek(Loc : Longint) : Byte;
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);
Var
AddLo : Word;
AddHi : Byte;
Begin
AddLo := Loc AND $FFFF;
AddHi := LongInt(Loc AND $FF0000) SHR 16;
{ Write('POKE HI :', AddHi:5, ' LO : ', AddLo:5,
' ');}
Port [Base+$103] := $43;
Portw[Base+$104] := AddLo;
Port [Base+$103] := $44;
Port [Base+$105] := AddHi;
Port [Base+$107] := B;
{ Writeln(B:3);}
End;
Function GUSProbe : Boolean;
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);
{ Port [Base+$103] := $4C;
Port [Base+$105] := 0;}
{ Above bit disabled since it appears to prevent the GUS from
accessing
it's memory correctly.. in some bizare way.... }
If B = $AA then GUSProbe := True else GUSProbe := False;
End;
Procedure GUSFind;
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 ', Hex(Base), ' ');
End;
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;
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 { DIV 19}); { actual frequency / 19.0579083837
}
End;
Procedure GUSVoiceControl( V, B : Byte);
Begin
Port [Base+$102] := V;
Port [Base+$102] := V;
Port [Base+$103] := $0;
Port [Base+$105] := B;
End;
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 GUSSetVolume( Voi : Byte; Vol : Word);
Begin
Port [Base+$102] := Voi;
Port [Base+$102] := Voi;
Port [Base+$102] := Voi;
Port [Base+$103] := 9;
Portw[Base+$104] := Vol; { 0-0ffffh, log ... not linear
}
End;
Procedure GUSSetLoopMode( 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 $E7) OR Mode;
End;
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;
Procedure GUSPlayVoice( V, Mode : Byte;VBegin, VStart, VEnd : Longint);
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;
Procedure GUSReset;
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;
Var
I : Longint;
F : File;
Buf : Array[1..20000] of Byte;
S : Word;
Begin
Clrscr;
Writeln('GUS DigiUnit V1.0');
Writeln('Copyright 1994 Mark Dixon.');
Writeln;
GUSFind;
Writeln('with ', GUSFindMem, ' bytes onboard.');
Writeln;
GUSReset;
End.
ÚÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
³ GUS669.PAS ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ
UNIT Gus669;
{
GUS669 Unit v0.2b
Copyright 1994 Mark Dixon.
This product is "Learnware".
All contents of this archive, including source and executables,
are the
intellectual property of the author, Mark Dixon. Use of this
product for
commercial programs, or commercial gain in ANY way, is illegal.
Private
use, or non-commercial use (such as demos, PD games, etc) is
allowed,
provided you give credit to the author for these routines.
Feel free to make any modifications to these routines, but I
would
appreciate it if you sent me these modifications, so that I
can include
them in the next version of the Gus669 Unit.
If you wish to use these routines for commercial purposes, then
you will
need a special agreement. Please contact me, Mark Dixon, and
we can work
something out.
What's "Learnware"? Well, I think I just made it up actually.
What i'm
getting at is that the source code is provided for LEARNING
purposes only.
I'd get really angry if someone ripped off my work and tried
to make out
that they wrote a mod player.
Beta version? Yes, since the product is still slightly unstable,
I feel
it is right to keep it under beta status until I find and fix
a few
bugs.
FEATURES
- Only works with the GUS!
- 8 channel, 669 music format.
- That's about it really.
- Oh, 100% Pascal high level source code = NO ASSEMBLER!
(So if you want to learn about how to
write your own MOD player, this
should make it easier for you)
- Tested & compiled with Turbo Pascal v7.0
BUGS
- Not yet, give me a chance!
(If you find any, I would very much
appreciate it if you could take
the time to notify me)
- Doesn't sound right with some modules, advice
anyone??
- Could do with some better I/O handling routines
when loading the
669 to give better feedback to the user
about what went wrong
if the module didn't load.
You can contact me at any of the following :
FidoNet : Mark Dixon 3:620/243
ItnerNet : [email protected]
( prefered )
[email protected] ( might not work for mail
:) )
[email protected] ( Don't use this
one often )
[email protected] ( Might not exist any more,
that's how often it's used! )
I collect internet accounts.... :)
If you happen to live in the Australian Capital Territory, you
can
call me on 231-2000, but at respectable hours please.
"Want more comments? Write em!"
Sorry, I just had to quote that. I'm not in the mood for writing
lots
of comments just yet. The main reason for writing it in Pascal
is so
that it would be easy to understand. Comments may (or may not)
come later
on.
Okay, enough of me dribbling, here's the source your after!
}
Interface
Procedure Load669(N : String);
Procedure PlayMusic;
Procedure StopMusic;
Type
{ This is so that we can keep a record of what each channel
is
currently doing, so that we can inc/dec the Frequency
or volume,
or pan left/right, etc }
Channel_Type = Record
Vol : Word;
Freq : Word;
Pan : Byte;
End;
Var
Channels : Array[1..8] of Channel_Type;
Flags : Array[0..15] of Byte;
{ Programmer flags. This will be explained when it is fully
implemented. }
Const
Loaded : Boolean = False; { Is a module loaded?
}
Playing : Boolean = False; { Is a module playing?
}
WaitState : Boolean = False; { Set to TRUE whenever a new note
is played }
{ Helpful for timing in with the player }
Const
NumChannels = 8;
{ Thanks to Tran for releasing the Hell demo source code, from
which
I managed to find these very helpfull volume and
frequency value
tables, without which this player would not have
worked! }
voltbl : Array[0..15] of Byte =
( $004,$0a0,$0b0,$0c0,$0c8,$0d0,$0d8,$0e0,
$0e4,$0e8,$0ec,$0f1,$0f4,$0f6,$0fa,$0ff);
freqtbl : Array[1..60] of Word = (
56,59,62,66,70,74,79,83,88,94,99,105,
112,118,125,133,141,149,158,167,177,188,199,211,
224,237,251,266,282,299,317,335,355,377,399,423,
448,475,503,532,564,598,634,671,711,754,798,846,
896,950,1006,1065,1129,1197,1268,1343,1423,1508,1597,1692 );
Type
Header_669_Type = Record
Marker : Word;
Title : Array[1..108] of Char;
NOS,
{ No of Samples 0 - 64 }
NOP : Byte;
{ No of Patterns 0 - 128 }
LoopOrder : Byte;
Order : Array[0..127] of Byte;
Tempo : Array[0..127] of Byte;
Break : Array[0..127] of Byte;
End;
Sample_Type = Record
FileName : Array[1..13] of Char;
Length : Longint;
LoopStart : Longint;
LoopLen : Longint;
End;
Sample_Pointer = ^Sample_Type;
Note_Type = Record
Info, { <- Don't worry about this little bit here }
Note,
Sample,
Volume,
Command,
Data : Byte;
End;
Event_Type = Array[1..8] of Note_Type;
Pattern_Type = Array[0..63] of Event_Type;
Pattern_Pointer = ^Pattern_Type;
Var
Header : Header_669_Type;
Samples : Array[0..64] of Sample_Pointer;
Patterns : Array[0..128] of Pattern_Pointer;
GusTable : Array[0..64] of Longint;
GusPos : Longint;
Speed : Byte;
Count : Word;
OldTimer : Procedure;
CurrentPat, CurrentEvent : Byte;
Implementation
Uses Dos, Crt, GUSUnit;
Procedure Load669(N : String);
Var
F : File;
I, J, K : Byte;
T : Array[1..8,1..3] of Byte;
Procedure LoadSample(No, Size : Longint);
Var
Buf : Array[1..1024] of Byte;
I : Longint;
J, K : Integer;
Begin
GusTable[No] := GusPos;
I := Size;
While I > 1024 do
Begin
BlockRead(F, Buf, SizeOf(Buf), J);
For K := 1 to J do GusPoke(GusPos+K-1,
Buf[K] XOR 127);
Dec(I, J);
Inc(GusPos, J);
End;
BlockRead(F, Buf, I, J);
For K := 1 to J do GusPoke(GusPos+K-1, Buf[K] XOR
127);
Inc(GusPos, J);
End;
Begin
{$I-}
Assign(F, N);
Reset(F, 1);
BlockRead(F, Header, SizeOf(Header));
If Header.Marker = $6669 then
Begin
For I := 1 to Header.NOS do
Begin
New(Samples[I-1]);
BlockRead(F, Samples[I-1]^, SizeOf(Samples[I-1]^));
End;
For I := 0 to Header.NOP-1 do
Begin
New(Patterns[I]);
For J := 0 to 63 do
Begin
BlockRead(F, T, SizeOf(T));
For K := 1 to 8 do
Begin
Patterns[I]^[J,K].Info
:= t[K,1];
Patterns[I]^[J,K].Note
:= ( t[K,1] shr 2);
Patterns[I]^[J,K].Sample
:= ((t[K,1] AND 3) SHL 4) + (t[K,2] SHR 4);
Patterns[I]^[J,K].Volume
:= ( t[K,2] AND 15);
Patterns[I]^[J,K].Command
:= ( t[K,3] shr 4);
Patterns[I]^[J,K].Data
:= ( t[K,3] AND 15);
End;
End;
End;
For I := 1 to Header.NOS do
LoadSample(I-1, Samples[I-1]^.Length);
End;
Close(F);
{$I+}
If (IOResult <> 0) OR (Header.Marker <> $6669) then
Loaded := False else Loaded := True;
End;
Procedure UpDateNotes;
Var
I : Word;
Inst : Byte;
Note : Word;
Begin
WaitState := True;
For I := 1 to NumChannels do
With Patterns[Header.Order[CurrentPat]]^[CurrentEvent, I] do
For I := 1 to NumChannels do
If (Patterns[Header.Order[CurrentPat]]^[CurrentEvent, I].Info
< $FE) then
Begin
Inst := Patterns[Header.Order[CurrentPat]]^[CurrentEvent,
I].Sample;
Note := Patterns[Header.Order[CurrentPat]]^[CurrentEvent,
I].Note;
Channels[I].Freq := FreqTbl[Note];
{ Channels[I].Pan := (1-(I AND 1)) * 15;}
Channels[I].Vol := $100*VolTbl[Patterns[Header.Order[CurrentPat]]^[CurrentEvent,
I].Volume];
{ Write(Note:3,Inst:3,' -');}
GUSSetVolume (I, 0);
GUSVoiceControl (I, 1);
GUSSetBalance (I, Channels[I].Pan);
GusSetFreq ( I, Channels[I].Freq);
{ GUSPlayVoice ( I, 0, GusTable[Inst],
GusTable[Inst],
GusTable[Inst]+Samples[Inst]^.Length );}
{ Write(Samples[Inst]^.LoopLen:5);}
If Samples[Inst]^.LoopLen < 1048575 then
Begin
GUSPlayVoice ( I, 8, GusTable[Inst],
GusTable[Inst]+Samples[Inst]^.LoopStart,
GusTable[Inst]+Samples[Inst]^.LoopLen );
End
Else
Begin
GUSPlayVoice ( I, 0, GusTable[Inst],
GusTable[Inst],
GusTable[Inst]+Samples[Inst]^.Length );
End;
End;
{ Writeln;}
For I := 1 to NumChannels do
If (Patterns[Header.Order[CurrentPat]]^[CurrentEvent,
I].Info < $FF) then
GUSSetVolume (I, $100*VolTbl[Patterns[Header.Order[CurrentPat]]^[CurrentEvent,
I].Volume]);
For I := 1 to NumChannels do
With Patterns[Header.Order[CurrentPat]]^[CurrentEvent, I] do
Case Command of
5 : Speed := Data;
3 : Begin
Channels[I].Freq
:= Channels[I].Freq + 10;
GUSSetFreq(I,
Channels[I].Freq);
End;
8 : Inc(Flags[Data]);
6 : Case Data of
0 : If Channels[I].Pan
> 0 then
Begin
Dec(Channels[I].Pan);
GusSetBalance(I, Channels[I].Pan);
End;
1 : If Channels[I].Pan
< 15 then
Begin
Inc(Channels[I].Pan);
GusSetBalance(I, Channels[I].Pan);
End;
End;
End;
Inc(CurrentEvent);
If CurrentEvent > Header.Break[CurrentPat] then Begin CurrentEvent
:= 0; Inc(CurrentPat) End;
If Header.Order[CurrentPat] > (Header.NOP) then Begin CurrentEvent
:= 0; CurrentPat := 0; End;
End;
Procedure UpDateEffects;
Var
I : Word;
Begin
For I := 1 to 4 do
With Patterns[Header.Order[CurrentPat]]^[CurrentEvent, I] do
Begin
Case Command of
0 : Begin
Inc(Channels[I].Freq, Data);
GusSetFreq(I, Channels[I].Freq);
End;
1 : Begin
Dec(Channels[I].Freq, Data);
GusSetFreq(I, Channels[I].Freq);
End;
End;
End;
End;
{ $ F+,S-,W-}
Procedure ModInterrupt; Interrupt;
Begin
Inc(Count);
If Count = Speed then
Begin
UpDateNotes;
Count := 0;
End;
UpDateEffects;
If (Count MOD 27) = 1 then
Begin
inline ($9C);
OldTimer;
End;
Port[$20] := $20;
End;
{ $ F-,S+}
Procedure TimerSpeedup(Speed : Word);
Begin
Port[$43] := $36;
Port[$40] := Lo(Speed);
Port[$40] := Hi(Speed);
end;
Procedure PlayMusic;
Begin
If Loaded then
Begin
TimerSpeedUp( (1192755 DIV 32));
GetIntVec($8, Addr(OldTimer));
SetIntVec($8, Addr(ModInterrupt));
Speed := Header.Tempo[0];
Playing := True;
End
{ If the module is not loaded, then the Playing flag will not
be set,
so your program should check the playing flag just
after calling
PlayMusic to see if everything was okay. }
End;
Procedure StopMusic;
Var
I : Byte;
Begin
If Playing then
Begin
SetIntVec($8, Addr(OldTimer));
For I := 1 to NumChannels do GusSetVolume(I, 0);
End;
TimerSpeedUp($FFFF);
End;
Procedure Init;
Var
I : Byte;
Begin
GusPos := 1;
Count := 0;
Speed := 6;
CurrentPat := 0;
CurrentEvent := 0;
For I := 1 to NumChannels do Channels[I].Pan := (1-(I
AND 1)) * 15;
For I := 1 to NumChannels do GUSVoiceControl(I, 1);
For I := 0 to 15 do Flags[I] := 0;
End;
Var
I, J : Byte;
Begin
Init;
Writeln('GUS669 Unit V0.2b');
Writeln('Copyright 1994 Mark Dixon.');
Writeln;
End.
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
³ PLAY669.PAS ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
Program Testout_Gus669_Unit;
Uses Crt, GUS669;
Begin
If ParamCount > 0 then Load669(Paramstr(1))
else
Begin
Writeln;
Writeln('Please specify the name of the 669 module
you wish to play');
Writeln('from the command line.');
Writeln;
Writeln('eg : Play669 Hardwired.669
');
Writeln;
Halt(1);
End;
PlayMusic;
If Playing then
Begin
Writeln('Playing ', ParamStr(1) );
Writeln('Press any key to stop and return to DOS.');
Repeat
Until Keypressed
End
else
Begin
Writeln;
Writeln('Couldn''t load or play the module for some
reason!');
Writeln;
Writeln('Please check your GUS is working correctly,
and that you have');
Writeln('correctly specified the 669 filename.');
Writeln;
End;
StopMusic;
End.