![]() ![]() |
[ / main / writing / gus ] |
The Gravis Ultrasound |
![]() ©1998 Jeff Weeks and Codex software
|
![]() |
||||||||||||||||||
Sample source code: |
![]() |
![]() |
||||||||||||||||||
Well, let's get right into it. Before ever using the GUS code, you must, ofcouse, check to see that a GUS exists. This is actually really easy. You can communicate to the GUS directly through its base port, so all your must do is write to every possible base port, and if what you read back is the same as what you wrote, you've not only detected a GUS card, but you've found its base port too! |
![]() |
![]() |
||||||||||||||||||
Well, before you can do any of that, you'll need peek and poke (read and write) functions that operate using the GUS's ports. Here's what they look like:
char gpeek(long loc) { short addlo; char addhi; addlo = loc & 0xFFFF; addhi = long(loc & 0xFF0000) >> 16; outportb(port + 0x103, 0x43); outportw(port + 0x104, addlo); outportb(port + 0x103, 0x44); outportb(port + 0x105, addhi); return inportb(port + 0x107); } // write to the GUS's memory void gpoke(long loc, char stuff) { short addlo; char addhi; addlo = loc & 0xFFFF; addhi = long(loc & 0xFF0000) >> 16; outportb(port + 0x103, 0x43); outportw(port + 0x104, addlo); outportb(port + 0x103, 0x44); outportb(port + 0x105, addhi); outportb(port + 0x107, stuff); return; } |
![]() |
![]() |
||||||||||||||||||
Perhaps that looks a little complicated, but it really isn't, especially if you've ever looked at some Sound Blaster code ;) So, what's the above all about? Well, first of all, the GUS uses 24-bit memory addressing so the first two lines of almost all functions will convert the 32-bit address (loc) into a 24-bit address. That shouldn't be anything new, you basically do the same thing to convert a protected mode pointer to a real mode pointer. |
![]() |
![]() |
||||||||||||||||||
So, next you have to read or write to this address. This is done by sending 0x43, or 0x44 to port 0x103, which tells the GUS what part of the address you're about to send it (as far as I know, anyway) and then you have to send that part of the address. It seems that the GUS combines port 0x104 and 0x105 to make the 24-bit address, so you will write the low half to 0x104, and the high half to 0x105. You can see that happening in the above code snippets. |
![]() |
![]() |
||||||||||||||||||
Lastly, you can read and write from the data port, 0x107. Simple, right? So, let's use the above functions to create a detect function. |
![]() |
![]() |
||||||||||||||||||
char probe(int base) { char c; port = base; // needed for the peek and poke routines outportb(base + 0x103, 0x4C); outportb(base + 0x105, 0); gdelay(); gdelay(); outportb(base + 0x103, 0x4C); outportb(base + 0x105, 1); gdelay(); gdelay(); gpoke(0,0xF); gpoke(0x100,0x55); c = gpeek(0); if(c == 0xF) return 1; else return 0; } // find the GUS char find(void) { short i; short base; for(i = 1; i <= 8; i++) { base = 0x200 + i * 0x10; if(probe(base)) { printf("Found your GUS at base %x\n", base); break; } else printf("No GUS found at base %x\n", base); } return 0; } |
![]() |
![]() |
||||||||||||||||||
The find function is probably pretty easy to understand, but the probe function probably needs some explanation. Unfortunately, I don't think I can give you the explanation you want. Basically all it does is set the global 'port' variable to the port in question. This is because the peek and poke functions require the port variable to do their work. The probe function does some initialization at the start, which I'm afraid I'm not totally sure what it does. You'll have to be content in knowing that it's required. Then it simply writes to the GUS, and reads back from the same address and compares the two. If they're the same, you've found a GUS. |
![]() |
![]() |
||||||||||||||||||
Oh yeah, you'll notice the occasional interleaved gdelay() call. That's just a little delay to accomodate the GUS's processing. It's a simple function that just halts for about 7 cycles. |
![]() |
![]() |
||||||||||||||||||
void gdelay(void) { #if defined __DJGPP__ __asm__ ( "movw $0x300, %dx\n" "inw %dx, %al\n" "inw %dx, %al\n" "inw %dx, %al\n" "inw %dx, %al\n" "inw %dx, %al\n" "inw %dx, %al\n" "inw %dx, %al\n" ); #else asm { mov dx, 0x300 in al, dx in al, dx in al, dx in al, dx in al, dx in al, dx in al, dx } #endif return; } |
![]() |
![]() |
||||||||||||||||||
So, what else can we find out about our GUS? How about how much memory it has? That's very similar to detecting the GUS actually. You just write to the different possible memory boundaries (256k, 512k, 768k or 1024k) and read back from the same address. If you don't read the same value that you wrote, then you've passed the memory boundary for the GUS card. |
![]() |
![]() |
||||||||||||||||||
int i; char b; gpoke(257*1024, 0xF); if(gpeek(257*1024) != 0xF) i = 256*1024; else { gpoke(513*1024, 0xF); if(gpeek(513*1024) != 0xF) i = 512*1024; else { gpoke(769*1024, 0xF); if(gpeek(769*1024) != 0xF) i = 768*1024; else i = 1024*1024; } } memsize = i; return i; } |
![]() |
![]() |
||||||||||||||||||
Simple enough, right? I don't think that requires any explanation. So, let's actually create some sounds. You can create sounds on the GUS simply by writting to its memory and setting one of the voices to point to it. Oh, but wait, there is one last thing. You must now initialize the GUS. Here's what that looks like... |
![]() |
![]() |
||||||||||||||||||
outportb(port+0x103, 0x4C); outportb(port+0x105, 1); delay(); outportb(port+0x103, 0x4C); outportb(port+0x105, 7); outportb(port+0x103, 0x0E); outportb(port+0x105, 14 | 0x0C0); return; } |
![]() |
![]() |
||||||||||||||||||
So, now you can use your gpoke routine to write your samples to anywhere withen the GUS's memory boundaries. With that done you have to initialize a voice and point it to your samples memory address. Initialization is done by setting up the volume, panning, and frequency. |
![]() |
![]() |
||||||||||||||||||
outportb(port+0x102, channel); outportb(port+0x102, channel); outportb(port+0x102, channel); outportb(port+0x103, 9); outportw(port+0x104, vol); return; } void balance(char channel, char bal) { outportb(port+0x102, channel); outportb(port+0x102, channel); outportb(port+0x102, channel); outportb(port+0x103, 0xC); outportb(port+0x104, bal); return; } void frequency(char channel, int freq) { outportb(port+0x102, channel); outportb(port+0x102, channel); outportb(port+0x102, channel); outportb(port+0x103, 1); outportw(port+0x104, freq); return; } |
![]() |
![]() |
||||||||||||||||||
You can see the technique is very similar for each of the above. Next, you set the voice pointers. Oddly enough you must specify a beginning, starting and ending address. The first two seem rather redundant, but they are needed. You'd usually just set them to the same thing. |
![]() |
![]() |
||||||||||||||||||
outportb(port+0x102, chan); outportb(port+0x102, chan); outportb(port+0x103, 0x0A); outportw(port+0x104, (begin >> 7) & 8191); outportb(port+0x103, 0x0B); outportw(port+0x104, (begin & 0x127) << 8); outportb(port+0x103, 0x02); outportw(port+0x104, (start >> 7) & 8191); outportb(port+0x103, 0x03); outportw(port+0x104, (start & 0x127) << 8); outportb(port+0x103, 0x04); outportw(port+0x104, (end >> 7) & 8191); outportb(port+0x103, 0x05); outportw(port+0x104, (end & 0x127) << 8); outportb(port+0x103, 0x0); outportb(port+0x105, mode); outportb(port, 1); outportb(port+0x103, 0x4C); outportb(port+0x105, 3); return; } |
![]() |
![]() |
||||||||||||||||||
That's a nice long one, and again you're going to have to take it on faith. The process is pretty simple though; You write to the command port, 0x104, and then write the data to the other appropriate ports. You'll notice I also provide a mode parameter. The GUS uses this byte to set up some special effects and voice parameters. The effects are as follows... |
![]() |
![]() |
||||||||||||||||||
|
![]() |
![]() |
||||||||||||||||||
Lastly, maybe you want to get some input from the GUS. For example, you can get the position of any voice through the following function...
long p; short temp0, temp1; outportb(port+0x102, channel); outportb(port+0x102, channel); outportb(port+0x102, channel); outportb(port+0x103, 0x8A); temp0 = inportw(port+0x104); outportb(port+0x103, 0x8B); temp1 = inportw(port+0x104); return (temp0 << 7) + (temp1 >> 8); } |
![]() |
![]() |
||||||||||||||||||
Well, that's it. I hope you can now see why programmers love the GUS. It's a wonderful card with lots of hardware support. |
![]() |
![]() |
||||||||||||||||||