The QB Times
Issue #2
Contents:
Introduction
Well, here we are, the second issue of The QB Times! I had some
great responses on issue one as you can read below. There are no site
review because Viper is really busy in RealLife... Also I decided
to push the scripting article to next issue 'cause due lack of time
I could get a big enough article.. Next time will be a real good one!!
I promise! For now, enjoy issue #2!!!!!
Feedback
Here are some letters I received about The QB Times issue #1.
I was really surprised after reading your QB mag, it was really good, I
do have a couple of problems with it (nothing is ever perfect).
With your C/C++ code, use the ANSI standards for variable definitions
BYTE, WORD and DWORD are just typedefs of unsigned char, unsigned int,
unsigned long, and are not in the ANSI standard. It's a small thing,
but it makes a difference, because some compilers have headers that
define it (mostly windows ones) but some don't.
Your RPG scripting article was good, RPG scripts are cool to make, if
you want some help on writing some more on it or converting it to C/C++
code, I'd be happy to help.
Keep up the good work!
leroy.
Hi!
First, I hope you like issue #2 even better than issue #1! =)
Ok, first about the C/C++ code, I didn't write it myself, the document
was found on the internet and I just formatted it. I don't know C or
C++ myself so I can't really change it. Sorry!
Yes, RPG scripts are very cool to make and useful! I hope everybody
understanded the article well, so they can add scripting to their RPG!
And yes, you may port the code to C/C++, as long as you write somewhere:
Original QBasic code by Nightwolf (http://nwep.hypermart.net)
For other people wanting to port my code, please ask my permission first!
Nightwolf
Your magazine looks a lot like QB:TM as far as content goes. Both mags have
an article about asm in QB, a place to mention projects (most projects are
the same too), scripting, etc.
Why don't you both just join together to make an even better magazine?
Currently, QB:TM has some good stuff, but for many people (me), most of
it isn't that interesting (I don't feel like learning asm now, or how to
make MIDIs).
Just my thoughts.
-Entropy
Hi!
Well, as for now I don't want to join together and I don't think QB:TM wants
that also. For this issue and future issues I will try to have articles on
a wide range of things, so that everybody can make use of it!
Nightwolf
Errors in the last issue
- Enchanted Creations should be Enhanced Creations, I wrote this wrong
in shorts.
- NemChat is only made by Marcade and NOT by Viper. Viper is making NemIRC.
- Viper said that Future Software' navigation bar was taken from IFace. This is not true! Only the buttons on the navigation
bar were taken from IFace (with permission).
Shorts
Future Software buys www.qb45.com
After www.qbasic.com and www.quickbasic.com, www.qb45.com is now bought!
Jorden Chamid of Future Software bought www.qb45.com about two weeks ago.
Future Software is on www.qb45.com and Nightwolf Productions is also going to
move to www.qb45.com/nwep!
B-Czar floods qbasic.com forum
On the 10th of April, B-Czar flooded the qbasic.com forum with a program
he has made. He has made this program in Visual Basic and it can automaticly
spam 20 pages of almost any forum.
Projects
- Guerilla Software is working on a RPG called Reign of Annihilation.
Anthracks does the programming, scripting, and design, Lord Noremac does art, music, and design and ColdMouse does writing, and design.
It uses DQB to get fast pixel by tile scrolling, multi layered maps (like bridges you can walk both on top of and under),
day/night rotation, an original soundtrack, very long gameplay. They hope to have
an original, engaging story.
Some form of demo should be out by the end of April.
More info? Check out http://roa.hypermart.net
- Matthias-Emanuel Thoemmes(actlab) is working on THE CURSE of Elaine's Castle. It's an adventure game
and is last updated in March 99.
Features of the game are DirectQB, MIDI, using the Keyboard, many locations, Script-based Engine.
More info? Check http://www.actlab.de
- Rems and a friend of his are working on an RPG called Search For Souls.
Right now he's working on the utilities that will be required to make the game:
Character Profile Editor, Enemy Editor, Item Editor, Spell Editor,Tile Editor,
Map Editor, Story Editor, Etc.
- The Brain is working on a project which now has as codename: "wb2".
It's a sidescroller and The Brain says it will be completly different from
any game made in QBasic. There is a demo of wb1 on his page (sorry don't know an address)
but the final version will look completly different because he's recoding the
engine, redoing the graphics. He says it just a completly different project.
It will not be using typical tiles or anything like that...
Actually, it will be using a technique, that nobody has ever attemtped
before in QB...
He hasn't really started yet, but he has all of the
ideas written down.
There won't be any demos out for quite a while, because the engine
for it is going to take quite a lot of planning before it will work.
- DigitalDude is working on a SVGA RPG called Kalderan.
It uses pixel-by-pixel scrolling not that cheesy pixle-by-tile-by-pixel-by-tile stuff.
It has "time elements".(e.g. It switches from day to night) It also has an advanced
scripting system, FF-style battle engine, and an epic story.
The story is generaly done. The engine still needs collision detection and NPCs.
And the development tools are still being work on.
You can get the latest information at http://www.bizzarecreations.com and download a demo.
- Tal Bereznitskey is working on a project without a name =) but it will be an RPG.
It's in a 640x480 resolution, mouse controlled, animation parts and it'll be very funny!
Main game engine and the talking engine are done. Tal is looking for an artist, so I've
you want to draw tiles for him, contact him.
- Majiko is working on Story of Muai, an RPG. It has pretty nice graphics,
MIDI music, walking NPCs!, DirectQB 1.31, Pixel-by-Pixel Scrolling, EngineZero v1 and Map Scripting.
A demo will come some time in the future!
- DeVo is working on a complete 3D Rpg using Polygon Graphics, Cutscenes with Camera effect, 3D characters, more than one on screen at a time, the others follow the main character. Midi's and Wavs.
Here's a list on what's going on with the project:
- 3D engine is Completed
- Translation routine is being "translated" into Asm.
- Currently finishing Camera routines such as tilt, 3D rotate was just finished.
- Next step is the 3D objects engine based on wire frame.
- Once the engine has been completed, I then have some friends who enjoy hacking Snes RPG roms, and they are quite good at plot, I also know some photoshop artist :P.
- Expect plot and game development to take at least 1 year.
He's currently using the Tri's from the Dqb Lib, but he's planning to have his own that support Vesa graphics, for more the 256 color, more than likely it'll be 16 bit, not 24.
- Ryan Limbag (Magic Wand President) is working on a game called Sector-Z
You're an alien(Lork - a native Basendian) trapped on the planet Timba, in a prison called Sector-Z.
It's an action/adventure, with an overhead view. It'll have 4 or 8 levels, that hasn't been decide yet.
It will have either MIDI or MOD music, 3 different laser levels and more items,
page by page scrolling. It uses Dash and the graphics are made with PP256
- The Collector is working on Aliens!-X which is a two-sided
starfighter game (full roaming... vaguely resembles SaR2) where you play as
a human pilot or an alien pilot, to win the war and discover the secrets of
the Galaxy. A crude, and very buggy version of the flight engine is done,
but is unsuitable for public release.
It will contain over 30 different guns, missiles, and bombs; an user friendly
interface; realtime lighting & translucency effects, MOD-based music,
sounds; 2-player co-op/battle mode; and maybe a random mission generator.
- Future Software is currently adding SVGA subs to their Future Library.
They are really good!
- After the release of 3DES Designer v0.43b, BlackBird put it on hold for a
while so he can concentrate on his other projects.
- BlackBird is making a platform game, JumP, it has multilpe layered, paralax
scrolling backgrounds, a 3D rendered character and a real fast graphics engine.
Read the Preview below!
- leroy is making King Of Men. It's an RPG, and it features a really cool scripting engine
640x480x8bit graphics, Digital Sound FX (SB compat, ESS
Audiodrive, SoundScape), MIDI music (Adlib, MPU-401) and a multibranching
pathway story.
Although the game is being written in C, consider it mostly BASIC orientated simply because the scripting language is as close to basic as leroy could make it.
- Zack Friedrich is working on a graphics utility without a name.
It uses DQB so you can load PCX and BMP, and implemented BSV
(bsave), and will probably support other formats from other graphics
utilities. You can copy/paste between pictures, same or different
format, same or different palette (!) You can resize, blend, blur,
zoom, rotate, flip, fade, and even change your cursor color =) An
important feature of the program is the Undo option. You can
convert pictures to any palette you wish, and it will usually
still look good :) ..a not-yet-slated feature, which should
be very big, might be implemented, and there is much more!!
- Marius is working on a point and click adventure like the Lucas Arts games, the engine is running an the storyboard stands,
It's finished for about 30%,
At the moment the name of the game is: Kings Quark 7 part 2 - Celine Dion is a stupid bitch
- .Scapegoat Software is working on a hunting game, similar to the
original Deer Hunter. It is about 40-50% complete, and features sound effects,
mouse support, scrolling, and cheat codes. We may add music, but probably only
at title screens, help screens, and ending screens. Since we're still in high
school, we have very little time to work on the game. I am doing
and any music for the game, as well as some sounds and graphics. The other guy
- Gamkiller and TomteFan are new basic programmers so this project
kind of sucks...(This were Gamkiller's own words!) It's called "Chip-the game"
and it features 320*240 res, 2 player suport, no sound, pinball mode, and in a
while you will actually be able to SHOOT!
Joystick Usage in QB
Hi and welcome to my tutorial on Joystick usage in QB.
This requires a knowledge of bits so if you are not to sure check out
the BIT tutor.
Now the purpose of this tutorial basicaly is to allow you to write joystick
routines without having to resort to QB's crappy STRIG routines which
are anoyingly bad and can be sometimes confusing!
So without further delay lets get started.
The Joystick basicaly has a value in which all the status is stored.
This value is read from PORT &H201 (Joy% = INP(&H201))
.
The data uses BITS to show which part of the joystick is in use and
where the joystick is currently at.
Here is a little diagram of the BIT usage in the Value:-
BIT 7 | BIT 6 | BIT 5 | BIT 4 | BIT 3 | BIT 2 | BIT 1 | BIT 0 |
Button 2 Joystick B |
Button 1 Joystick A |
Button 2 Joystick A |
Button 1 Joystick B |
Y Axis Joystick B |
X axis Joystick A |
Y axis Joystick A |
X axis Joystick B |
Now reading the status of the buttons is straight forward, Just read
the bit(be it 7, 6, 5 or 4) and if it = 0 then the button
is being pressed! Wasn't that simple?
As for the reading of the sticks position, well that is not as
simple as the buttons. First you must send out a dummy byte to
the Joystick port. Just out any old value to port &H201, eg:-
OUT &H201, anyvalue%. This in effect will set the axis bits to 1.
Then you must time how long it takes for the bit to return to 0.
This is roughly proprtionl to the position of the stick.
This sounds complex but it can be done! I assure you. The values
should be around as follows:
Minimum: 8
Maximum: 990
Centered: 330
So, I hope this has give you enough insight to write your joystick
routine! All you must do is wait for the bit to return and record
the duration. Easy!
This article was written by Greg McAusland (Maz)
ASM in QBasic
Lesson no.3 More ASM commands
Now we've went through some very fundimental commands but these are
not enough for us to do jack sqaut. ; ) We need to learn how to make
use of our computer's I/O abilities! What use is calculations were
you can't see the results let alone interact with them? Not much.
Some of you would have look at the famous mouse.bas asm code.
Somewhere along the line, you'd see this:
mov ax,1
int 33h
retf
What was that you asked?
It's very simple think of a INT command as a secratary. You give this
beautiful blonde a peice of paper outlining thing to do (the input).
She then goes off mysteriously on her own doing what she was trained
to. At a later time you get back a peice of paper back with feedback
on what she has done (output). Some times the jobs you ask her are
too simple for feedback so she gives you none.
On to the point now (enough fun) INTs are basicly QB's equivalent of
SUBs. Now heres that code agian with a little explaination:
mov ax,1 ;gives AX a value of 1
int 33h ;does an int 33, this will
;basiclly call the mouse
;function. Because AX's value
;is 1, that function acts by
;showing the mouse.
retf ;Returns
Ok, INT 33 takes input from AX.
If ax is 1 then the int show the mouse
If ax is 2 then the int hides the mouse
If ax is 3 then the int gives info on the mouse at AX, BX and CX.
Aha! So all the code up there just show the mouse! Handy eh?
Lesson no.4 Procedures
Have you noticed the retf up there? Well that's just a return from a call.
Here's an example of a procedure:
1E7D:0100 CALL 200
RETF
1E7D:0200 MOV AX,1
INT 33h
RETN
Hmmm ... first the program does a near call (A near call is a call to
a procedure from a procedure from the same segment e.g 1E7D) to 200.
After reaching 200 the program shows the mouse then returns from the
near call.
After that retf actually exits the program and returns back to the OS.
Well that sums it up for this issue, next issue we are going to
actually do something useful with ints. Be sure to pick it up. = )
This article was written by abionnnn
Program (P)Reviews
DarkAges II Preview
I am sure that all of you reading this will have heard of Dark Ages 2 the RPG -
If not then where have you been since October? Right now, the official name is
"Dark Ages 2 : Taftkraftengel".
The engine is being written in SVGA with a movement system like none ever
before, a mouse movement system. No cursor keys anymore...no more chr$
anymore...a sure improvement? Well in some ways yes and in others, no. Sure, the
mouse makes movement faster but at what cost? Right now it is very hard to be
critical as the mouse system is far from completion and has many bugs. I think
it may be a bit tedious clicking the TALK button every few minutes then clicking
WALK to continue on.
I asked DarkAges what was new and exciting about DA2 and this is what he replyed
- First, the story is non-linear. It has the same beginning and same ending
always, but the in between changes based on player actions. Sometimes, player's
choices may actually change the world. The next time you play, you may do
something different and see a whole different set of subquests never possible in
the first time through.
Now, if that doesn't get an award for making you play it more than once, I don't
know what will.
The world of DA2 is entirely in the same perspective...there is no "walking into
towns", the whole map is on one level. An improvement surely (less loading).
One of the best parts of Dark Ages 2 must be the graphics which are very
impressive in my opinion. Gavan has really gone all out to make some very clean
and easily tiled images that you don't notice as being one tiny image repeated
over and over again. The little shadows under the characters and the way that
roofs disappear when you go into a building are nice, but very subtle things
that make the game slightly better than so many others.
Cutscenes will be in there, but only to move along the plot. The emphasis is
going to be on the game play. Cutscenes will be as few as possible. Only 15-20
throughout the whole game. No cutscenes are being produced as yet, as Enigma has
alot on his plate...he still has Bubble Fighters to finish. :)
Another thing that I asked DarkAges was about the scripting Engine. He says -
Scripting takes two forms, conversation and object. Between the two they change
all the variables in the game. The alteration of these variables is what moves
the plot along. To put it simply, you win the game by successfully altering all
the necessary people and objects in the game. After much thought, anyone who
tries another procedure is either doing something amazing or just making the job
difficult.
In this second demo there are 314 tiles already...how many tiles are there going
to be in the real game? Well i asked that too...Originallt, there was to be 500
sprites and 500 tiles but already there is 500 tiles and 100 sprites produced. DarkAges thinks that
there will be around 1200 tiles and 800 sprites in the finished game. All of the
tile are originally produced in bitmap format and converted to Bsaves.
The next demo should be out at around 11-18th of April. I can assure you that I
am looking forward to it.
As there is not alot more left to say about this demo, as it is only a demo with
not much to show for itself game-wise, I will leave you with this thought...in
1994 we had text based RPG's with line numbers and goto...in 1999 we have mouse
driven SVGA RPG's...what will we see in another 5 years?
Dark Ages 2 - http://www.tcnj.edu/~hoopman2
This article was written by Gza[wu]
RPGEngine Preview
This is ceratinly a different way to go...Writing an engine for an RPG (or similar type of)
game with which you can use the code of as long as you talk to the engine authors about it.
A fair trade? Let us review the evidence.
This project has been around for about 3-4 months now, started by QBprogger and Necrolyte.
They are telling people that this is not just another "Tile Engine 3" (something which is greatly
overrated) as it is a public viewing of their own project.
The engine is sadly a tile based movement system which is growing to be rather old hat nowadays.
The animation of water and the map scrolling is superb, although the jerky character movement and
walking animation when stationary seem to let the engine down...something which I am sure is to
be addressed in future builds. The engine is real time from what I can gather as there is no
"wait for a key" style coding involved. Sadly this trade-off has seemed to leave the keyboard
handler somewhere low on priorities as it feels like inkey$ (select case ucase$(key$) is what is
actually used alot).
The font, the menu system, EMS memory routines and the transparency effects make up for alot.
The screensaver is still buggy however, as some pixels get trapped and do not move. For all
these bugs, I am sure there will be fixes as the team are only at build 20 so far and havre
progressed a long way from where their engine began. If pixel scrolling was implemented and
some careless coding sorted then we will have a winnner on our hands that may help you to make
DA3.
8 coders on the team including such people as Entropy and Pasco can ensure that the project is
not going downhill. I can also assure you that when more RPGEngine news appears, The QB Times will
have it.
RPGEngine - http://qbprogger.cjb.net
Gza_wu (Christopher Neill) - Exposed Dreams '99
(http://www.gza-wu.freeserve.co.uk)
JumP Preview
JumP is a new platform game created by BlackBird. It uses BlackBird's own
ASM routines, which are faster than DirectQB's!!! It looks really nice. It uses a 3D rendered character made by BlackBird's brother.
In the newest version he added painfull spikes, an animated character (see the screenshot), he fixed the
keyboard handler bug, translucency and some speed increases.
It runs really fast, on my P133 it runs with and average of 70 FPS.
Which is really good as compared to other programs I've seen. The graphics
are also looking good, I don't know who made them but he's really a great
artist.
The QB Times will keep you informed of updates on JumP!
Want more info and a download? Go to B.A.T Software.
Ramblings of a Crazed RPG Programmer: Plot Design
Yes! Another plot article to add to your collection - with one small twist...come in close...got
your eyeballs right on the screen now? good, I just wanted you to know, THIS ARTICLE IS USEFUL!!!!
I mean it, I'm only letting my ego in a little on this one. Honestly now, you play a lot of the
amateur RPGs and think to yourself, "This is horrible. The story is dumb, I don't know what I'm doing,
and none of it makes sense!" Well, that's probably because the plot was never formally formulated
(hehehe...pun intended - you'll have to deal with it, I still have the rest of the article to go!)
Ok, obscure misleading intro out of the way, I'll start writing well again. The most important aspect
of any plot is a concrete, structured theme. Let me repeat that: a concrete, structured theme. What
this means is you set a timeperiod, a place, a mood, and you develop the entire plot around that. Perhaps
some examples will help. If you've ever played the Ultima series, you will notice that the original theme
was based upon the British Midieval period in the height of feudalism. By Ultima 4, the theme followed
history and developed into a large medieval country, similar to the chronicals of King Arthur. Everything
follows from that!!! You have your set of heroes and heroines (like the Knights of the Round), you have
your medieval weapons, armor, and transportation. You fight dozens of mythical creatures like dragons,
hydras, orcs, etc. And you follow a main quest from Lord British (parallel for King Arthur) which may
divide itself into several subquests along the way which you are obligated to play because your
character follows the Virtues of the land. In other words you must complete these quests just like a
Knight of the Round would in order to follow the Code of Chivalry. Following this? The parallels are
rather blatant, but the series is making its 9th game now! It's that good, it has that concrete,
structured theme that makes the world so realistic and the game so much fun.
Now, that is a game series that is probably older than most of you (Ultima I was originally on an apple
way back in 1979), so don't think you have to make a series as great as Ultima. Rather, just take the
concepts it presents to make a good game. It doesn't have to be medieval; many futuristic and space RPGs
exist. Also Square's Final Fantasy series has moved over the years to encompass a technologically
advanced era with a midieval feel. The original was purely midieval, Final Fantasy III (US release), was
medieval with technology mixed in, and by VII the medieval aspects were almost non-existant. They were
able to do it by developing a concrete theme of an evolving world in which each game is only a chapter in
the whole story, never putting too much evolution in at one time.
Ok, I've filled you with all sorts of ideas - now what to do with them. Well, let's take just a standard
pirate theme. This means that no matter how much you want there to be a nuclear sub that you use to blow
ships out of the water, you can't put it in the game. You must also remember that you've now confined
yourself to a post renaissance era - no mythical beasts, no magic. Your technology is swords, cannons, and
muskets. Your boats are small, 15 man ships, to huge 200 man galleons. You're probably thinking: what fun
is the story now? Well, remember that there will be people who won't want to play a pirate theme, but there
are more who would if you do it right!
What makes piracy so much fun? The adventure, the thievery, raiding, cannons, secret romances, corrupt
provincial governors, and of course, yelling "AAARRRR!" You have your wonderful theme right in front of
you, now use your imagination!!! Think of one great, all-encompassing purpose for your game. How about
destroying the corrupt armada of the bad guy? Here's where your background information comes in. There is
a corrupt official who using his military prowress in the water has subdued and captured 75% of the coastal
towns in the game and is using this to control all the trading in the seas. As one of the citizens of the few
remaining free towns, the only way you can survive in your trade is through piracy - sinking the bad guy's
ships, raiding his towns, sailing in his waters, etc. Your ultimate goal would be to kill this individual
and free all the towns under his command. Ahh, but how can one small man do such a thing? You obviously
need to go on several subquests building your own group of followers, wealth, and armada.
If your following this, we've now tackled two things: a concrete, structured theme and a main quest. Now comes
the easy part, the subquests. Remember that they also must follow the theme. Let's start with an easy one.
If you've read up on piracy, many pirates were English noblemen. Since your character is a good one, from a free
town, you could make him a nobleman and perfect gentleman. That way, winning the heart of some important woman
in one of captured towns wouldn't be a problem. After sinking or stealing 12 ships from the bad guy, you return
to a free city for repairs where a mysterious note had been deliverd while you were at sea. It is from the
daughter of one of the governors in a captured town. She has been betrothed to a local captain, an evil and
disgusting man. Having heard of your great valor and deeds in helping free the towns, she has determined you
to be her savior, someone who could steal her away from her troubles. Naturally, you now go out to sneak into
the town under the cover of darkness, kill the guards in her mansion and sneak out under a full moon with her.
Yes, I know it sounds a bit cheezy, but think of your theme! It fits right in and people who play it will know it.
You could add many, many more subquests, like gathering the pieces of a treasure map, freeing local towns,
raiding a galleon at port, sinking the ship of a rival pirate, capturing a government official and holding him
for ransom, etc. Just don't add any sea serpents or jet bombers...
That about covers it, you should be set. Remember your theme could be something as ridiculous as space invaders.
Just remember to develop your game around little green men with lazer guns. The quests should follow the theme
and they don't have to be elaborate, just as long as they fit within the world you have created. Good luck!
3D in QB
Hi folks! It's me, your all-nutcase, 100% freaked out programmer, BlackBird.
Nightwolf asked me to write an article about 3D techniques for his online magazine,
the QB Times, and that's what you're reading right now.
I originally shoud have written this for issue #1 but, as always, I was late.
Nevertheless, it's here now, and it will be better then ever! (er... that shouldn't
be too hard for a first time I guess)
Since I have been working on a 3D modeller (or editor, or drawing program, whatever),
called 3DES Designer (or 3DES for short) for quite some time now, I figured it would
be nice to combine the 3D tutorial with a 3DES tutorial, also because I have since
received many emails regaring the use of the models created in 3DES.
Soon I will release a 3DES SDK (or Software Development Kit) for use with your own
programs.
As you might have noticed, the 3DES zipfile came with a small program called LOAD3DP.BAS
which explained the 3DP file format that 3DES uses to store it's models.
However, this is not sufficent for most users and therefore I will try to explain a little
about the 3DES models in this article.
But first you'll have to understand the basics of 3D graphics, so here we go...
Firstly, three dimensional objects consist of points in a 3D space, and for the totally
braindead among us, three dimensional points have three coordinates: the X, Y and Z
coordinates.
In maths classes at school (at least mine) they teach you Z is the horizontal axis and Y
is the axis that points into the depth... bummer for them, 'cause I hereby treat Z as the
'depth' axis, and X and Y as the 2D axis that lie on your computer screen... let's use a
nice ASCII drawing to support that:
(just imagine that the drawing is 3D and that the Z-axis points into the depth)
o--------> x-axis
|\
| \
| \ z-axis
|
\/ y-axis
Mind that 3D coordinates are notated in the following order: X,Y,Z
Also, the arrows in the drawing above point to the positive values relative to the center
of the cooridinate system (being o in the drawing), eg: negative Z values lie in front
of the center, positive Z values behind it.
Got that? good. Now how do you display those points? Well, since your screen is only
2D, you can't just plot a pixel at, say 10,4,12... (or PSET (10,4,12),15) you could try...
but it definitively won't work.
So what you need to do is 'convert' the 3D coordinates to 2D ones that fit onto your
screen (this process is called translation).
But how? It's quite simple, just follow this formula:
FlatX = X * 256 / Z
FlatY = Y * 256 / Z
Where FlatX and FlatY are the 2D coordinates that you can plot to the screen right away.
Noticed the number 256 in there? Right, now that is the value that controls one of the
most important things in the world of 3D graphics, perspective (meaning things get smaller
the further they are away from the viewer).
The larger the value, the less perspective in your object, keep that in mind. 256 is a
pretty good 'default' value. You don't need to change it unless your model's perspective
looks exaggerated or something.
Okay, so we've gotten quite far now. You now ought to know how to draw a 3D object
consisting of points on a 2D screen...
if that is still unclear, let me clarify it for you, here's a little source code to
get you started:
-------------8<-------------8<-------------8<-------------8<-------------
'// Ajust this value to load larger models or save memory
CONST numberOfPoints = 100
'// Ajust this value to zoom in or out on the object
CONST zoomLevel = 0
TYPE ThreeDeePoint
X AS SINGLE
Y AS SINGLE
Z AS SINGLE
END TYPE
'// Set up an array of 3D points
DIM MyArray(1 TO numberOfPoints) AS ThreeDeePoint
'// Load a model into the array
'// Draw all the points
FOR pointIdx = 1 TO numberOfPoints
'// Do not draw points that are behind the screen
IF MyArray(pointIdx).Z + zoomLevel > 0 THEN
'// Translate points
flatX = MyArray(pointIdx).X * 256 / (MyArray(pointIdx).Z + zoomLevel)
flatY = MyArray(pointIdx).Y * 256 / (MyArray(pointIdx).Z + zoomLevel)
'// Plot a white dot at the point's translated coordinates
PSET (flatX, flatX), 15
END IF
NEXT
-------------8<-------------8<-------------8<-------------8<-------------
And voila! your model is here! Offcourse it still looks like shit, since you only used
dots to draw the points.
Most 3D games and applications, like 3DES for example, use face-based models.
A face consist of 3 or more points, and when linked together, they form a polygon.
Here's another one of my beautiful ASCII drawings to clarify that:
o - point 1
/ \
/ \
point 4 - o \
\ \
point 3 - o-----o - point 2
The drawing above is a face consisting of four points, to draw it, just translate all
four points to 2D, draw a line from point 1 to 2, 2 to 3, 3 to 4 and from 4 back to 1,
and hey presto! A face!
Offcourse it's still a wireframe representation, not a solid face, but that's yet
another chapter, drawing a filled polygon, and I'm not going to explain that here.
So, with a little creativity, you can apply the face-technique to the sourcecode above,
making it a so-called wireframe 3D engine. Hurray!
Still, we are missing the main part of a true 3D engine... manipulation of the model.
It's very nice and all, a still of a 3D model, but it's much more fun when the thing
actually rotates around it's axis.
3D rotation is basically not very different from 2D rotation, not to say exactly the
same.
The only thing you need to do is rotate each point three times, one time for each axis
(X,Y and Z).
Rotating a 2D point can be accomplished by using this formula:
newX = COS(degrees) * X - SIN(degrees) * Y
newY = SIN(degrees) * X + COS(degrees) * Y
To translate a 3D point, you'll have to treat the rotation around each axis (3 in total)
as a 2D rotation using the remaining two coordinates.
For example, to rotate around the X-axis, treat the Y,Z coordinates as if they were 2D coordinates in 2D space.
Assuming you put the above rotation function in a SUBroutine defined like this:
SUB rotatePoint (X, Y, newX, newY, degrees),
the correct code to rotate a point around the axis would be:
-------------8<-------------8<-------------8<-------------8<-------------
'// around X-axis
CALL rotatePoint (Z, Y, newZ, newY, degrees)
'// around Y-axis
CALL rotatePoint (X, Z, newX, newZ, degrees)
'// around Z-axis
CALL rotatePoint (Y, X, newY, newX, degrees)
-------------8<-------------8<-------------8<-------------8<-------------
Then all you have to do is just rotate all points like in the example above,
then translate them and then draw the bloody thing =)
Well folks, that is the end of part 1, this series will be continued in future
issues of the QB Times, see yers!
This article was written by BlackBird
Dash, Blast! and DirectQB Compared!
There is a lot of dispute and rivalry as to which is the best graphics library for QB. As a means to determine who is the king of graphics libs, I benchmarked 10 different functions of 3 different libraries, and compared them against each other, and Qbasic's own built in graphics functions. I compared DirectQB by Angelo Motola, http://echome.hypermart.net, DASH by Danny Gump, http://www.gnt.net/~gump, and finally Blast! by Andrew L. Ayers. Although Blast! is rather old, I decided to put it in anyway to see how it held its own against DirectQB and DASH. In order to be able to compare the library's speeds against QB's own
statements I only used these functions (though in the actual libraries, they have different names):
PSET
LINE
CIRCLE
CLS
GET
PUT
PRINT
Buffer-Screen Copy
Buffer-Buffer Copy
Screen Scrolling
I was actually very surprised at the results. See for yourself below:
Function (or equivalent) | DirectQB | DASH | Blast! | Built-in QB Graphics |
PSET | 19243 | 16863 | 15617 | 17239 |
LINE | 11837 | 9876 | 0* | 10984 |
CIRCLE | 16947 | 0** | 0** | 11090 |
CLS | 606 | 0** | 597 | 63 |
GET | 11343 | 8385 | 3927 | 3728 |
PUT | 18107 | 13386 | 11706 | 3014 |
PRINT | 11028 | 6392 | 1407 | 20 |
Buffer-Screen Copy | 745 | 746 | 386 | 138 |
Buffer-Buffer Copy | 1534 | 2844 | 697 | 138 |
Screen Scrolling | 18 | 0** | 0** | 0** |
*Blast! Contains a line sub, but it gives an error.
** Function isn't available.
All figures are in Amount/Sec, or basically, how many times the library did that function per second. This was tested on a P2 300. Secondly I rated each library on a scale from 1-10 on:
Ease of use
Stability (10 - however many times it crashed)
Speed
Variety of functions
Here are those figures:
Category | DirectQB | DASH | Blast! | Built-in QB Graphics |
Ease of use | 9 | 6 | 7 | 10 |
Stability | 10 | 8 | 9 | 10 |
Speed | 10 | 8 | 6 | 4 |
Variety | 10 | 7 | 4 | 2 |
Power | 10 | 10 | 7 | 3 |
Documentation | 10 | 6 | 4 | 8 |
TOTAL: | 9.83 | 7.5 | 6.5 | 6.17 |
|
DirectQB
I will now explain my reasoning for giving these scores. First of all let us start with DirectQB. It is
extremely easy to use, since it uses EMS layers that you refer to as 1,2,3 etc. etc., instead of having to use
buffers, VARSEG (buffer1(0)), VARPTR (buffer1(0)). This makes life much easier when using DirectQB.
As for stability, DQB did not crash once when I was testing it, thus the score of 10. As you can see from
the figures, DQB is a very very fast library, which is why it got the score of 10. As for variety, DQB has
tons and tons of different functions, way more than any of the other libraries. I haven't even used all of them yet. It also has very good sound capabilities and many other non-graphics related functions, making it
not only a graphics lib, but also a sound lib and a disk handling lib, and a bit manipulation lib. As for power, DQB is very powerful; not only in speed, but also in the effects it can create. DQB also supports
multiple joysticks, mouse, has its own keyboard handling routine which is far far superior than using INKEY$. DirectQB's documentation is top of the line, it has 130 pages of documentation, with examples,
known bugs, what the author's goals are for it. It just goes on and on. If you don't like the idea of having to
waste valuable program space to make buffers (DIM buffer1 (15999)), and don't mind the minimal speed loss involved with EMS, and want an fast, entire game programming library, then DQB is your obvious
choice.
DASH
Now I will go on and explain my reasoning for giving DASH the scores I did. First of all, DASH is not an
easy thing to use. It uses the conventional buffer system; not nice EMS pages like DQB does. I couldn't get
some of the functions to work for very odd reasons sometimes. As for stability, it is not very stable, it fatally crashed my machine twice, made my monitor go haywire and report frequency of out range erro
r multiple times. While it is not nearly as fast as DQB is some areas, and gets beaten out by QB itself
sometimes, it does beat DQB in copying buffers to the screen, and copying from buffer to buffer, most
likely because DQB uses EMS and conventional memory is faster than EMS. DASH doesn't have too many functions, and they are mostly all graphical, with some mouse and bit manipulation routines, so that
is why it got a variety score of 7. As for power, DASH is a very powerful library; it can create spectacular
effects. Finally, the DASH documentation is rather weak, short, with no examples, and leaves a lot of questions unanswered. In conclusion, I would recommend DASH to experienced QB programmers, who
only want a graphics library and don't mind using program space for graphics buffers. It is rather obvious
DASH is copying off of DirectQB, because shortly after every release of DQB, DASH comes out with
some the same routines as DQB. But if you want some of the unique features DASH has, such as specific
routine for palette rotation and others, then DASH is what you want, although you will need additional
libraries for sound and such.
Blast!
Actually, I was rather disappointed with Blast's performance. I thought
it would be speedier than either DQB or DASH, but no. Since I shouldn't
take up too much room with this article, I won't explain why I gave it
the scores, but only my conclusion about Blast! It is an older library,
the version I tried was from 1997, but it can do very cool things.
I have seen several RPG's coded in Blast!, and they rock, but unless
Blast! gets an update sometime, it is not a very smart idea to make a
game in Blast!, when you have these 2 other very good libraries with
many features.
Conclusion
After reviewing these different libraries, I think I see an obvious winner.
And the library is: DirectQB!
After getting such good benchmark scores, and being so easy to use
and so complete, it does not come as a surprise to me that DirectQB
is the best library. I'm sure many people have different opinions on
the Ease of use, variety, documentation and such scores, but it is
very obvious that DQB is the fastest. No one can deny that after
looking at those numbers. Well, all I know is that I will be using
DirectQB a lot in the future, because it is an obvious winner. No
offense to the makers of DASH and Blast, but the numbers don't lie.
DQB is nearly twice as fast as DASH in some areas, and is twice as
fast as Blast in most areas. For now, in my opinion, DQB is the
undisputed champ. Until someone comes out with an even faster and
better library, maybe even the next release of DASH will be a whole
lot better than DQB. Who knows_.
This article was written by Yoda
Soundcard Special
In this SoundCard special I'll include on how to program different SoundCards
and on how to use many sound formats! Note: The documents were not written by
me nor any of the other The QB Times writers. They were found over the internet!
Programming the SoundBlaster
Overview
Two of the most popular sound cards for the IBM-PC, the AdLib and the
Sound Blaster, suffer from a real dearth of clear documentation for
programmers. AdLib Inc. and Creative Labs, Inc. both sell developers'
kits for their sound cards, but these are expensive, and (in the case
of the Sound Blaster developers' kit) can be extremely cryptic.
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 |
|
The groupings of twenty-two registers (20-35, 40-55, etc.) have an odd
order due to the use of two operators for each FM voice. The following
table shows the offsets within each group of registers for each operator.
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 15
Thus, 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 Waveform
Explanations 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 11
Detecting a Sound Card
According to the AdLib manual, the 'official' method of checking for a
sound card is as follows:
- Reset both timers by writing 60h to register 4.
- Enable the interrupts by writing 80h to register 4. NOTE: this
must be a separate step from number 1.
- Read the status register (port 388h). Store the result.
- Write FFh to register 2 (Timer 1).
- Start timer 1 by writing 21h to register 4.
- Delay for at least 80 microseconds.
- Read the status register (port 388h). Store the result.
- Reset both timers and interrupts (see steps 1 and 2).
- Test the stored results of steps 3 and 7 by ANDing them
with E0h. The result of step 3 should be 00h, and the
result of step 7 should be C0h. If both are correct, an
AdLib-compatible board is installed in the computer.
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 |
To turn the voice off, set register B0h to 11h (or, in fact, any value
which leaves bit 5 clear). It's generally preferable, of course, to
induce a delay before doing so.
Programming the Gravis UltraSound
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
.VOC File Format
(byte numbers are hex!)
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:
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]
.MID File Format
The standard MIDI file format is a very strange beast. When viewed as a
whole, it can be quite overwhelming. Of course, no matter how you look at it,
describing a piece of music in enough detail to be able to reproduce it
accurately is no small task. So, while complicated, the structure of the midi
file format is fairly intuitive when understood.
I must insert a disclaimer here that I am by no means an expert with
midi nor midi files. I recently obtained a Gravis UltraSound board for my PC,
and upon hearing a few midi files (.MID) thought, "Gee, I'd like to be able to
make my own .MID files." Well, many aggravating hours later, I discovered that
this was no trivial task. But, I couldn't let a stupid file format stop me.
(besides, I once told my wife that computers aren't really that hard to use,
and I'd hate to be a hypocrite) So if any errors are found in this
information, please let me know and I will fix it. Also, this document's scope
does not extend to EVERY type of midi command and EVERY possible file
configuration. It is a basic guide that should enable the reader (with a
moderate investment in time) to generate a quality midi file.
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 dd
The 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 xx
As 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 |
The following table lists system messages which control the entire system.
These have no midi channel number. (these will generally only apply to
controlling a midi keyboard, etc.)
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 |
|
The following table lists the numbers corresponding to notes for use in note
on and note off commands.
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.
This article was written by Dustin Caldwell
.WAV File Format
Where's the article?
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.
.MOD File Format
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,113
To 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 Speed
E - 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 Loop
Cmd 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.
Closing words
That's it for issue #2! I'd like to thank you for reading trough all this! =)
Again issue #3 should come in around 3 or 4 weeks. For now, I hope you enjoyed
and don't forget to send me your feedback/articles/project updates!
Hasta luego!
-Nightwolf
Credits
Editor: | Regular Writers: | Submissions: |
Nightwolf |
Masamune_Q Abionnnn BlackBird gza[wu] Nightwolf Viper DarkAges |
Yoda leroy DeVo Rems Jorden Chamid B-Czar
Entropy Anthracks actlab The Brain Digital Dude Majiko Tal Bereznitskey
Ryan Limbag The Collector Zack Friedrich |
© Copyright Nightwolf Productions 1999