issue #8

Want to print? Use this version

Back issues are in the Archive

Go back to the main site

Download a zip version

REM 'Letter from the Editor
Welcome to Issue 8 of Qbasic: The Magazine
ON KEY 'Hot List
This month's latest news
INPUT 'Your Stuff
Survey, Letters, Must Downloads and more!
! mov 'asm Intro: part 5
Petter brings ya more of the good stuff ^_^
LINE 'The Gallery
An rippin' shot of Vertical Scroller
PRINT '3d: Part III
A Correction and some more planar rotation
SADD 'Tricks with Memory
White Shadow shows ya how to twine memory with gfx
REM 'Eternal Frost preview!
Exclusive Info on DarkDread's newest game
PLAY 'Midi Composing II
19Day continues to enlighten your composing-ness
END 'Next Issue
What's up in our next issue?


Letter from the Editor

By ZKman

"NO! Not that time, you sickos!"

Como estas? It's that time of month again...

NO! Not that time, you sickos!

It's time for the new qb:tm. This month we're trying to put together a compilation worthy of last issue (which is my favorite, btw), and think we may have succeeded. First, White Shadow put together an article this month on Game Programming Tricks in Memory. Everything from the BLOAD screen load in to setting up QBV style offsets is included! Also, Petter Holmberg brings home the bacon again with Part 5 in the ever-growing series whose size is concerning multiple environmentalists ^_^. MA SNART provides a fix of last month's rotation code and improves upon the formula with a complete program!, while 19Day shows ya how to whip up s'more fly tunes with WinJammer for yer game again this month.


Playsta 2, DarkDread and more!

Meanwhiles, we gots another kickin' preview for you guys this month. Once the most famous of all qbasic programmers, and thought to have disappeared, the man himself, DarkDread, has hooked qb:tm up with a total insight into his latest "Story RPG", called Eternal Frost. Some of you may remember that this game was featured last month in the gallery, and hopefully some of you will be drooling in anticipation after this article.

In utter un-qb coolishness this month, the specs for Playsta 2 have been released. With all effects turned on (inluding Bezier curves! Not even Voodoo3 does these!), it STILL can crank out 20million polys/second. Figuring that a game probably uses 50% of hardware on graphics, a game running at 40 fps will be able to have 250,000 (!) polygons per frame! Members of Square have said that "only 5 companies in the world can even afford to make games that take full advantage of PSX2". There has been concern about the price of such a system, and how Sony intends to crank out that many wafers at .18 micron, but, as you all remember, when the specs for original Nintendo were released, many people vehemently argued that "at current, an 8-bit machine of these specification can not be made at mass-market prices." So, all of you that think Sony can't do it, enjoy Sonic Adventure. I'm already savin' my $300 so I can play Final Fantasy 9...on a PSX2.

Right, then. My editorial space has come to a close. Disagree with me? Hate my magazine? Write me, damnit! Ciao...Oh, yeah! Thanks to ChUcK for this new super-speedy web space. For those of ya that don't know, we can now be found at or

See ya' next month!



Back to top

qbasic Hot List

Compiled by ZKman


Qbt50 done for good...wait, strike that...
Apparently, no one was willing to pony up a mere 800 bucks for the qbt50 domain and scripts at Ebay last month (strange considering a character from Ultima Online and the 500,000 gold that it had sold for $500!). Considering this, the fateful words "Qbt50 will not update again" flabber-gasted qbasic coders who insisted on putting banners on their new page and voting for themselves many times (wait.. umm... I mean...).

Needless to say, as the qb:tm news peoples were thinkin' up some kind of neato-keen title to announce the disparaging news, the qbt50 will remain open for at least another year! And why this turn of events? Many an email of begging reached Steve and, y'know, his mail server was complaini... umm, I mean that a large amount of mail was sent to Steve requesting the qbt50 remain open.

Based on the changes at the qbt50 in the last months, here's the Qb:tm predictions for the future of qb...

  • Site changes to Pascal Top 50
  • Site decides to close
  • Site decides to stay open
  • Site starts using Javascript instead of CGI
  • Javascript can't save
  • The Year 2000 comes and the earth explodes
  • The Earth changes it's mind and decides to stay open for atleast another year

(BREAKING NEWS! Just as we're going to press, I find out that the qbt50 is going to get a new look and start supporting other languages in addition to qb! Can I call 'em, or what?)

The future of the Top 50, NES Emu's, and svga



More on Interplay's BASIC Interpreter
Interplay's BASIC Interpreter "Learn to Program BASIC", an interpreter that turned BASIC into a sort of "Logo" and was squarely aimed at the middle school audience, is apparently not selling very well in either the Macintosh segment or the Windows segment, although it is well supported. We'll have the exact sales figures and some comments from the creator for you next month.


NES Emulation in Qbasic
Last month, we told ya guys 'bout an NES emulator in QBASIC called "uNESsential", due to it's speed compared to Nesticle. At that time, version .14 was out, and didn't properly do color. The newest version, v.20, does NES perfect color matching, although it is still a bit slow. But, hey, it's done in qbasic, and is done without libraries! Look for some dope stuff from this group soon

But that's not all. Yet another emu has come from QBASIC. Called DRR-NES, this emu reportedly gets in the area of 15fps on a 233 and can crank out most of the NES functions. 15fps? Hey, that's good for qb! So there! Check out the Mario 3 goodness at move begins
In issue 7, you guys learnt that ChUcK's was soon to be the home of many of qb's top sites. Well, the move has begun. In the past 28 days, qb:tm, Neozones, and Alternate Logic have all turned anchor and headed to the ad-free pasture of the new server. Look for more of your favorite sites to appear there soon! Also, look for me to stop making metaphors that make absolutely no sense!


Qlympics: Countdown continues
The Qlympics are a bit behind scheduele in announcing the finalists for this year's competition. Maybe that's because they have 200 entries! S'Increduble! Although the finalists were not known at print time, qb:tm has learned that some of the better programs at the point we got our information included...

  • TF Checkers
  • Wetspot 2
  • SFB2
  • Artechos OS
  • Bricks
Look for some of these titles to be future Must Downloads add-ons. Remember that this is only a partial list, and is not the official Qlympics Finals choices.


PMODE compiler?
Pyrus is slowly but surely working on a PMODE compiler for qb4.5 programs. As most of you know, PMODE stands for Protected Mode, a type of extended memory (similar in ways to EMS/XMS) that allows nearly infantisimal use of a system's memory resource (read: tons 'o array space). The compiler would not be an interpreter (i.e., it you tried to run the .bas version of a prog in qb4.5, you'd still get the same kinda errors), but when you compiled, those pesky "Out of Memory" and "Program Memory Overflow" errors would pretty much be rid of. Look for more info on this in the future.


The latest on the libs.
It was a thriller (oooooh!) this month for the two big qb libs this month. An amazing no updates. The dqb1.5 version is finished, supporting features such as a great sound engine and pixel-perfect collision, but is publicly unavailable due to ftp problems at Enhanced. It should be up any day now, though, and we hear that it BY FAR the best version of the lib set to come out. Dash hasn't been updated since 6 Feb, but we hear it's because of computer problems over at VirtuaSoft. And -10 points if you can recite the rest of that Michael Jackson song ^_^.


No more qb from 3d master Marko?
Our 'migo Marko Dokic stated recently on a webboard post that the only future qbasic development he'll be doing would be for contests such as the Qlympics, as he is now mainly a c++ kinda guy. Marko is, of course, known for his great 3d code such as the fastest gourard lighter, phong lighter, bilinear filter, and more.


SVGALib schtuff
Zephyr Software's SVGAlib, which brought 16bit real mode svga to qbasic in 1993 (a couple years before it came to c++, thank you very much!) has cruised along to v2.5, and although it has reached it's final update, Zephyr is committed to supporting users for years to come. In recent talks with Daniel Sill of Zephyr, we learnt of their future plans, most of which don't concern qb. However, Dan did say that perchance there is a Pmode qb compiler, it might be feasible to port zsvga (a buff protected mode version of the svga lib) over to qb. We certainly hope this comes true!


Oh .. my .. God. We only made ONE error last month (at least that we know about ^_^). As a few of you pointed out, "Eldim" is not developing Bubble Fighters. That'd be Enigma. Hey, they both start with "e", right?

God willing, this issue will be error free. Fell free to berate me with all the things we did wrong at this address


SFB2v1.04, MOTG Project, and tons more!


Latest Game updates:
Soom Primal's RPG "The MOTG Project" has been gathering a lot of attention lately, as it managed to do very well in this month's Survey. Focusing on it's ultra cool art and a well-defined story, this svga rpg looks like one to watch. Check out the screens at Soom's page.


A couple of games caught a cancellation this month. Firstly, in all sadness, my beloved project Charter Magica has been canned. Por que? Becuase... um... I'm sick of the really bad code that I did when the project was started long ago. Look for a post mortem article in some upcoming issue. Also, Entropy's project THEM is on the borderline of the end, as, according to him, he doesn't like the "non-programming aspects" of it, such as art, music, etc.. Hey, that's my favourite part! Anyways, this game is not totally gone, so email Entro and tell him you don't want it to join the qb dustbin!

Our hombre Pasco sent us some info on his terrific trio of France '98, Vertical Scroller, and Groov 2. Here's what we can tell you:

France 98 is having an overhaul in the way shooting passing is done. The characters now have full rotation, which should make the shooting less of watching the two colored lines, and more of grippin' and rippin'. Groov 2 has some dead fast rotation code. I've seen a test-wireframer, and I believe it was moving around 30,000 points a second. The texture-mapper is almost done (the entire game will be filled poly's, unlike Groov 1's wireframe 3d), and this product should move up the queue soon. Finally, Vertical Scroller is lookin' amazing. A few different AI's and some lovely graphics set it apart. Check out a new shot in the Gallery

SFB2v1.04 came out recently, making rave reviews with me for a beefed-up AI and the removal of the cheap "kick out of the ring" Win. Add in better lookin' blood and some more particle effects, along with a secret character, and this is a must upgrade.

Another update occured with Seav's "The Labyrinth". This "first person tile engine" (think Legend of Lith 2, but as smooth scrolling as Doom) went to v1.7 with the latest update, which added 5 groovy tilesets, and a better interface. We're looking forward to the version with the guns, though. ^_^

Unbelievable, but Mr. TheGame himself Tsugumo has returned to QB! He's workin' on a game called "TheifGame" which is essentially a mini-RPG about a Theif. Not only can you steal stuff from people's houses, but the spell effects for this one are utterly amazing! The fire spell causes two huge blasts of fire to literally erupt from the ground and splash the enemy in a 2d delight. Check out more on this game at Tsugumo's Lair

This month in the shorts section: Work progresses on the Dark Ages 2 juggernaut. This month hasn't seen many major breakthroughs, but work on NPC's has been progressing smoothly. I personally can not wait for the much touted battle system to be in effect. A new engine demo has been released featuring moving NPC's. Get it at the the DA2 Page... Magik's Last of the Legends RPG has seen some new work, mainly in the area of map dev, being completed. Look for a demo of this title to be released soon... Dunric is workin' on an RPG called Twilight of the Valkyries. We're gonna keep our eye on this, as it has a fly looking battle engine in the works.... Finally, Project RT, the much touted game from Enhanced has seen some major breakthroughs. First, they've decided to make it into an "action strategy RPG" starring Cuby and Coby, the two crabs from Wetspot 2. The first level will be a bloody affair where you must kill all the monkeys on the level with only your pincers. Yeah!... Then again, this month is the "April" issue. What's so special about April 1st again?

That's all the latest news for this month. Remember, if you have any news or rumours, send them along to Qbasic: The Magazine


Back to Top

Input: Your Stuff

By ZKman



Here's what you had to say about last month. Be sure and send us more letters soon!

Buy, Shop, Download, anything. Just tell me where to get it. A decompiler for basrun.exe compiled files.

Juvenal Brenes

Sorry, pardner. Despite your enthusiasm, decompiling an .exe is, as Tek has put it, much like trying to take a cow out of a hamburger. You might end up with the parts, but you won't be able to milk it, if ya know what I'm sayin'.



I just thought I would comment on the February issue and a few of the other letters featured in it.

First off, the assembly tutorial is really good, and I still think it should be compiled into a stand-alone tutorial.

Now, there were some comments about libs in there, and you know I can't resist that kind of argument, so here goes:
Okay. We are in the golden age of QB here. The reason: libs. Plain and simple. Now, I don't agree with every newbie who's trying to do something gets told to go use Dash or dqb. That's just counter-productive, but I don't think it's wrong for the amatuer, "Can do more than PRINT" coder to use a lib or two. I mean, some of these anti-lib guys say that you should write everything yourself, which is honorable in it's own way, but I hope they don't think they'll get a job thinking like that. If Corel had to rewrite all it's stuff for each Photoshop version release, they'd never be past version 3. It takes too long to do stuff like that, or rewrite everything over and over, and not use old code because someone else wrote it.

Second Point: What language are we programming in here? Hmm...QB. Right. What language are these libs being written in? Hmmm...ASM. Right. Now, if we program all our QB Code ourselves, and then use an ASM lib, then who coded all the QB part of it? Hmm...ourselves! Take a lib, a lib, and a lib, link them together, and run it. It's just a blank screen. Why? Because libs don't make programs, we do.

Now I'm not saying that we shouldn't learn assembly because there are all these libs out there, but I also don't think we should HAVE to learn asm just to make a slightly faster qb program. It should be up to the individual. What is the individual doing? Well, I'm using libs for my programs, while at the same time learning asm, so by the end I'll still have a game and know enough asm that when I go on to do my next game, I cna use my own assembly. Then I would be a QB Programmer and an asm programmer. Schweet.


First off, about your suggestion for an "asm article collection". Expect to see one very soon, once the last of Petter's series trickles in. Just don't complain if the page count is 75-100 pages ^_^.

Next, I pretty much agree with you on libs. Although I have yet to use one on my progs, that super-fast asm is pretty enticing. If c people can use Allegro, why can't we use dqb?




Hey! BlueChocobo here.

I would like to make a suggestion. Are you ready?

I think that you should send the mag out over email. You could use ListBOT or ONElist or something. Then we could read the mag without having to go to the website and print it out!

Anyways, just a suggestion. PS: Beware of the Chocobos!


hmm...people send to send some version of this letter every month. Guess I'll explain why this won't happen.

Firstly, the mag is...oh...say 300kb zipped. The free lists have a maximum of 10kb or 20kb per mailing. To send out big files would cost mucho dinero, which I don't gots.

Secondly, we're hit-greedy, and you're gonna come to our website a lot or else no chocobo greens for you!




In a recent QbasicMagazine, under the hot list (compiled by zkman), you mentioned an emulator written in qbasic, uNESsential. I'm one of the programmers (I recently joined the team, so my neame isn't on the credits yet) and I can answer the questions asked.

It (uNESs... see last month's news -ed) was designed in SCREEN 12 (640x480x16) because the NES's resolution is 256x240. SCREEN 13 is 320x200, which is 40 pels too short. We wanted it to be truly written in 100% basic - no assembly or libraries. Therefore, we chose not to use tweaked 320x240 modes or other popular graphic libraries. The NES is only capable of displaying 16 colors (out of a 64color palette) at a time, so screen 12 works out perfectly. A VGA mode was required to properly emulate the 64 NES colors. We also wanted there to be room for onscreen palette tables and debug information. The next release may include (among other things) an interesting feature that speeds up the CPU time by two, as well as a major fix in opcode.

Matthew Leverton

Thanks for the clarification. Last month we also said that the colors didn't properly emulate with uNESs. The newest version, v.20, has perfect color



qbasic: the magazine reserves the right to edit any letter recieved for purposes of clarity and space. But you already knew that.


Here's the results of the Issue 7 survey from Qbasic: The Magazine! We need more people to Vote! So do it!

 Favourite Game     |  Last Month  | Change    
 1. Wetspot 2              1           <> 
 2. Dark Ages (t)          2(t)        <>
 2. Mono. Shooter (t)      2(t)        <>
 4. MiniRPG3               --          --
 Comments: SFB2 drops off the list for the first
  time in quite a while. MiniRPG3 rejoins the
  top, and Monospace continues to do well.

 Favourite Utility  |  Last Month  | Change
 1. DirectQB               1           <>
 2. QMIDI                  2           <>
 3. SpriteShop (t)         3(t)        <>
 3. PP256 (t)              3(t)        <>
 5. SvgaLib                --          --

 Comments: For the first time in memory Dash
  didn't crack the Top 5. Can the XMS version
  (i.e. Dash2) save it? 

 Best Upcoming      |  Last Month  | Change
 1. Dark Ages 2            1(t)        <>
 2. MOTG Project           3           U1
 3. "Project RT" (t)       1(t)        D2
 3. TheifGame (t)          --          --
 Comments: Project RT drops a bit. Sorta strange.
  You guys weren't impressed by those shots last
  month? ^_^. MOTG does well, and bunches of titles
  fall just short of making the list.

Vote today by emailing your votes for:
favourite game
favourite utility
Game/utility that you're most looking forward to.
Send your vote here


You need this stuff!

Must Downloads returns for another showing. Nothing new made it to the list this month. Mebbe next time. Everyone knows that there are a god-awful amount of qb games out there, but what's worth downloading? These, my friend. Here, you'll find a list of the best of the best in QB progs. See something that should be here? Tell us and we'll check it out. You HAVE to have this stuff!

A puzzle game by The Brain where you must flip numbers in rectangular groups until you get them in order. Decievingly addictive!

Absolute Assembly
Petter Holmberg of Enhanced Creations's assembly converter. By typing in normal asm, this proggy will convert the asm into goods that qb will understand. Super-spiffy!

Dark Ages
One of the most engaging QB games ever, as well as one of the only complete rpg's. This was featured in PC Gamer! Check it out!

Groov Buggies
The best QB racer ever. Although it has some control problems and some clipping glitches, this wireframer set a new standard. the Dark Crown
Darkdread best and most complete game. Many hours of gameplay, and featuring a battle system that's been imitated in countless qb projects since. Not to mention you get cat food from the enemies!

Monospace Shooter
Gradius' 2 color side-scrolling space shooter. Featuring very detailed enemies, flickerless animation and a devious AI, this game is a classic

Wetspot & Wetspot 2
Wetspot, the bubble-bobble like block-pushing action game was one of the best QB games when it came out, but W2 is just incredible. Super midi sound, great fast graphics, tons of variety, an insane number of levels...everything you could want. GET this game. Now.

The BEST qbasic fighter. Ever. Even though it's wireframe, it has cool particles and smooth animation as well as rippin' gameplay.

Called the best tile editor in QB ever, PP256 has loads of tile-editing options at your disposal. If you use tiles in your game, you can't live without this.

These 3 sets of libraries take QB graphics to the extreme. They all have strenghts and weaknesses, but you should check them all out before you start a big project. You'll save a lot of coding plus get a big speed increase (and in DQB's case, save memory). Try these now.

This is the best version of Qmidi. Play .mid's in your game! The new qmidi4.1 rules! It has tons of features and a "light" version. Get it now!

Marko Dokic's 3d routines. Phong shading, gourard shading, environment mapping, you name it- it's here. If you want to see how to do good 3d, come here!


Basix Fanzine

Heyas. This month's Site of the Month is a very special one to me, because, it's disapearence led to the beginning of qb:tm. Back in the day, when PowerBasic was something that was coming soon, and svgaqb was godly, there was a fairly popular text-based Basic magazine called Basix Fanzine. After issue 7, though, it seemed to disapear and I was unable to find it. So, as a replacement, I began qb:tm.

Welp, it turns out the Basix Fanzine did NOT vanish (although there hasn't been a new issue since last October...mebbe it "vanished" a second time!), as I found it recently at a site's links page. Although more focused on the newsgroup and powerBasic set (with a lotta math intensive articles chucked in), it's still a good read. And check out the "old" issues to see what the peak of qb tech was.

qb:tm's inspiration...

Back to Top

assembly tutorial

By Petter Holmberg

This month: Hardware I/O

Welcome to the fifth part of my assembly tutorial series! During the last months, we've gone through some rough material, but now you should know almost all the basics of assembly programming. We've looked at calculations, memory transfers and flow control. This is enough to make some really cool assembly routines. But there are other aspects of programming in assembler. In this part we will look at the possibilities to do a little more with the assembly language. You can certainly do more than manipulate numbers and the memory in asm, so that's what we're going to look at this time. And maybe we'll also pick up some extra info that we lost on the way here...


Grab Absolute Assembly 2.1

Communicating with the outside world:
The demo program in the last part gave a taste of what you can do with nothing more than some simple assembly code. It only moved data between two memory positions, but the destination happened to be the video memory in the VGA mode 13h- or SCREEN 13 as QB programmers knows it. Thus, we could detect the result on the output device known as the monitor. But there are other devices that it would be cool to be able to access in asm, such as the keyboard, mouse and the sound card. Can we do this? Certainly!

The ports:
The I/O devices on the PC can be accessed through something called the I/O ports. Through these, the CPU communicates with the different parts of your computer. By reading data from, or writing data to these ports, we can access the hardware we want to control. Doing this in assembly is very easy. There are two asm instructions that you use for this task, and their names are simply IN and OUT. The syntaxes for these instructions are:

IN source, destination
OUT destination, source

These instructions have equivalents in QBASIC. There you use them in a very similar way. In QBASIC they're called INP and OUT, and the syntax is the same too!

The source for IN and destination for OUT are values specifying the port number. There are many I/O ports, so the number is 16 bits long. You can use a direct number as destination, but it's also common to MOV the number to DX first and use it as source/destination. The values used as destination for IN and source for OUT can be either bytes or words. You should use AX or AL for these tasks. As an example: Suppose you want to write a 1 to port 0487h and then read a value from the same port. Then you could use this program:

MOV DX, 0487 ; Port number.
MOV AL, 1 ; Number to write to the port.
OUT DX, AL ; Write to the port.
IN DX, AL ; Read from the port.

Don't test this program!!! It was only an example and it won't work. It's a bad idea to experiment with the I/O ports at all if you don't know what you're doing. If you're unlucky you may damage your hard drive or some other sensitive piece of hardware in your computer.

Another important thing to know is that you can't read and write to all ports. Some only works one way, so you may be able to read values from them but not write, or write to them but not read. This depends on the purpose of the port. Some devices, such as the VGA card and many soundboards uses only a few I/O ports for dozens of system functions. This is made possible through a clever technique called indexing: One port is used as an index port and another as a data port. First you write a value to the index port telling the hardware what system function you want to use, and then the data port can work in different ways depending on the value sent to the index port. Exactly how this works varies with the device you're accessing.

Ok, now I've rambled on enough. What can we do with IN and OUT then? Well, you can do really much, but the problem is that it's REALLY hard to control most I/O devices in this way. But let's look at an example anyway:

In VGA mode 13h (SCREEN 13), color 0 is usually black. But it's possible to set color 0 to white, red, yellow or any other color by changing the palette registers. The palette tells the VGA card how much red, green and blue to put on the screen for a certain color index. As default, the intensities are set to 0 for red, green and blue. Using two I/O ports assigned to the VGA card we can change the palette setting of color 0. It's not difficult at all. First, we write a 0 to port 03C8h, telling the VGA card both that we want to change the palette and that we want to change color 0. Then we write three successive values to port 03C9h, telling the VGA card the new intensities for the red, green and blue components of the color. The maximum value possible is 63, so if we set all three components to 63 we'll make color 0 look white:

MOV DX, 03C8 ; Set the port number to 03C8h
OUT DX, 0 ; Tell the VGA card that we want to change color 0
INC DX ; Set the port number to 03C9h
OUT DX, 3F ; Set red component to 63
OUT DX, 3F ; Set green component to 63
OUT DX, 3F ; Set blue component to 63

If you happened to be in screen mode 13h looking at a blank, black screen, it would become bright white after these six lines of asm instructions.

All right, now you know that you can program I/O devices through the I/O ports. Some I/O devices are fairly simple to program this way, but most I/O devices are a nightmare to access this way. The mouse is a great example of such a device. You must access it in different ways depending on what type of mouse you have, what port it's connected to your computer through and so on. Is there a solution to this problem? Luckily, the answer is yes!

The solution to the problems you may have accessing different I/O devices is something called interrupt calls. But interrupt calls are so much more than that, as you soon will see.

Basically, interrupts are small machine language routines stored in the conventional memory. Most interrupts controls different I/O devices in your computer, but there are interrupts with other functions too. Some interrupts are initialized by the BIOS of your computer when you start it up, some are initialized by the operating system and others are initialized by different device drivers.

Interrupts can be called by a program like we will do, but they may also be called automatially by the computer when something special happens, such as the pressing of a key on the keyboard. Interrupts called by programs are called software interrupts and interrupts called by the hardware are called hardware interrupts. You can have use of both types.

When an interrupt is called, the computer immediately interrupts what it's doing and runs the interrupt code. When the interrupt code has been executed, the control is returned to the code that was interrupted. Guess why they call it interrupts ;-)

Calling interrupts in assembler is easy. You can do it through an instruction called INT. The syntax is:

INT number

The number you pass with the INT instruction is the number of the interrupt you want to call. The number should be a one byte immediate number. Many times you can access many different subfunctions through one interrupt. By setting some of the registers before the INT call, you can tell the interrupt code what subfunction you want to access and what parameters you want to pass to that subfunctions. Like with the I/O ports, some interrupt routines returns data to you, some wants you to send data to them and some works both ways. You can do many different things with interrupts, but let's begin by using them to access an I/O device that you usually cannot access in QB: The mouse!

The mouse can be controlled through interrupt 33h. There are lots of subfunctions assigned to that interrupt. You tell the interrupt what subfunction you want to use by putting a number in the AX register. Additional registers can be used to pass parameters to the subfunction. The subfunction may also return data to you in the registers.

First of all, it would be nice to know if we have a mouse installed and a working mouse driver. The very first subfunction of int 33h can do this test for you. This subfunction has the number 0, and you don't have to send any parameters with it. This asm code would make the call:

MOV AX, 0 ; Access subfunction 0 of int 33h (detect mouse)
INT 33 ; Make the interrupt call

When the interrupt code has been executed, the line after the INT call would be called. The interrupt has now returned the current status of the mouse in the AX register, and additional information in the BX register. The AX registers tells you if there's a mouse installed. If it's set to -1, a working mouse was detected. If it's 0, you're apparently without a working mouse. If the subfunction found a mouse, you can also see how many buttons the mouse has by looking at the number in the BX register.

When you have detected a working mouse, you might want to display the mouse cursor on the screen. This can be done with subfunction 1. All you have to do is to set AX to 1 and call the interrupt:

MOV AX, 1 ; Access subfunction 1 of int 33h (display mouse cursor)
INT 33 ; Make the interrupt call

If you want to hide the mouse cursor, you can call subfunction 2 in the same way.


Earlier parts of Petter Holmberg's assembly series can be found in the Archive. They are in issues 4 thru 7.

Now you may want to get some information about the current position of the mouse cursor and the state of the mouse buttons. Subfunction 3 takes care of this for you:

MOV AX, 3 ; Access subfunction 3 of int 33h (get mouse status)
INT 33 ; Make the interrupt call

After this call, BX should tell you the current state of the mouse buttons. If bit 0 of BX is set, the left button is currently being pressed, bit 1 returns the state of the right button and bit 2 returns the state of the middle button if you have a three-button mouse.

The current coordinates of the mouse cursor can be found in CX and DX. CX contains the horizontal coordinate and DX contains the vertical coordinate.

Another interesting interrupt is interrupt 21h. This interrupt has tons of subfunctions installed by DOS. And don't worry Win95/98/NT users, you have them too!

Int 21h has many DOS-assigned subfunctions. For example, you can change the default directory, open and read/write data in files and so on. But there are also a couple of functions that does some other neat things for you, such as printing a string of text on the screen. Let's try it out!

Int 21h, subfunction 09h can be used to print a string on the screen. Here, you'll need to put the number 09h in the AH register, the segment address of the string in DS and the offset address of the string in DX. The string must be terminated with a $ sign, so if you want to print "Hello World!" on the screen, the string should be: Hello World!$. This example routine could print such a string on the screen for you.

MOV DX, [BP+08] ; Set DX to offset address of string
MOV BX, [BP+0A] ; Set DS to segment address of string
MOV AH, 9 ; Call the string printing function
INT 21

Another interesting interrupt is interrupt 10h. It contains subfunctions initialized by your BIOS. This is a great way of creating compability between different PC:s Even if two PC users have different BIOS manufacturers with hardware working in different ways, they can both access the same functions in the same ways, using the same interrupts. The difference lies in the actual machine language routines assigned to those interrupts, but the user doesn't need to worry about them.

Int 10h contains many interesting subfunctions for accessing video services, such as the ones you use to change screen modes. Through int 10h you can access many more screen modes than you can do using the SCREEN keyword in QB, such as hi-res VESA modes!

The ultimate resource when you need to look up information about interrupts is Ralph Brown's interrupt list. No assembly programmer should be without it! It tells you what the different interrupts and subfunctions do, what you should set the registers to when calling them and what data they return. You can download it from the resources section at the Enhanced Creations website, or from many other sites. Just search for it and you'll get thousands of hits! It's a big download, but it's worth it!

I remember when I first discovered interrupts. It was so much fun, because I instantly got access to functions and I/O devices that aren't possible to access in QB. Interrupts give you a lot of power as a programmer, and they make life a whole lot easier for you if you program in assembler. I'll leave you to explore the world of interrupt functions yourselves now. It can be really fun! Int 10h, 21h and 33h are good starting points.

I said before that it was possible to do much more with interrupts than this. Actually, you can do a lot more. It's possible to tamper with hardware interrupts as well, and if you want to, you can make your own interrupt functions and override existing interrupts with your own routines! The example program this month is a good example of advanced interrupt handling. I though we should write our own keyboard handler. As you may have noticed when working in QBASIC, the keyboard handling is great, unless you want to make an action game. It's impossible to detect multiple keypresses with INKEY$, something you often need in games.

We can solve this problem with a little assembly code. The keyboard is controlled through int 09h, and it's a hardware interrupt that is called whenever a key is pressed or released. If we can override this interrupt routine with a keyboard routine that we write ourselves, we should be able to handle the keyboard input in a more game-oriented way.

The first thing we need to know is how we can make int 09h run our assembly code instead of the one it's running right now. Well, believe it or not, there is an interrupt subfunction assigned to make just this change for you! This subfunction lies under interrupt 21h. It can change the memory address of the code that should be executed when you make a certain interrupt call. However, it would be nice to know the address of the old keyboard routine so we can give it the control back when we exit our program. There's a subfunction under int 21h for that too. But let's begin by writing the actual keyboard handler:

Keyboard code:
The first thing we need to keep in mind is that the keyboard handler can be called at any time during the execution of our program. It's you who decides by pressing or releasing a key on the keyboard. This means that whatever our keyboard handler does, the registers MUST be returned in the same state that they were before the interrupt call. Suppose the program you are running at a certain moment needs AL to be 3. But then the keyboard handler is called and changes AL to 2. When the interrupt routine ends, the program that was currently running will crash, since AL suddenly got changed. This problem is easy to fix though. All we need to do is to push all the registers we want to use in the beginning of the interrupt routine and pop them at the end. Even the flags must be left untouched, but the interrupt call iself pushes them for us so we don't have to worry about it. I don't think I've mentioned this before, but if you actually need to push and pop the flags sometime, you can do this with the instructions PUSHF and POPF.

PUSH DS ; Push everything we're going to modify on the stack

This is how I've thought the keyboard handler should work: We'll create a QB integer array with 128 elements. Each one will work as a keyboard "flag". So if a certain element is 0, the key with that scancode is not being pressed at the moment, but if it turns to 1, that key is being pressed. All QB arrays has a default offset address of 0, so all the asm routine needs to know is the segment address of the array. But how can we pass this value to the keyboard handler from BASIC? This is not a routine we call with CALL ABSOLUTE. Well, there's a really smart solution to this, but we'll wait with this problem. All we do is to set BX to an immediate number for the moment:

MOV BX, 1234 ; DS = SEG keyboard flag array (currently not implemented)

Now comes the reading of the keyboard status. How do we do this? Through I/O ports of course! Exactly how this works is beyond my knowledge, I've only looked at other keyboard handlers to see how they do it. First we should read a byte from port 60h. We will also set AH to 0 for later use. If the value read from the port is bigger than 127, a key has been released. But first, we'll take care of keypresses:

IN AL, 60 ; Read a value from port 60h
XOR AH, AH ; Set AH to zero
CMP AL, 7F ; If a key has been released, jump to some later piece of code
JA Release

Now it's time to take care of the keyboard array. If the jump to the Release label wasn't done, it means a key has been pressed and we should set an element of the keyboard array to 1. The array consists of two-byte integers, and the number currently in AL is between 0 and 127. So if we multiply AL by two, we should get the correct offset address in the array. We want to write a 1 to this offset address since a key was pressed, so that's what we'll do:

SHL AL, 1 ; Multiply AL with 2 to get the correct offset address
MOV BX, AX ; BX = OFS keyboard flag array
MOV [BX], AL ; Write a 1 to the keyboard flag array
JMP Endkey ; Skip the keyrelease code

All right, that should take care of keypresses. Now it's time for releases. If a key is released, AL will contain a number between 128 and 255. If we subtract 128 from this, we get a number between 0 and 127 which is what we want. But we don't need to use a SUB here. All we need to do is to mask out the highest bit of AL, and we'll get the same result. We can do this by ANDing the number with 127. Then we'll do the same as we did with keypresses, except that we write a 0 to the array this time:

AND AL, 7F ; Get rid of highest bit in AL
SHL AL, 1 ; Multiply AL with 2 to get the correct offset address
MOV BX, AX ; BX = OFS keyboard flag array
MOV [BX], AL ; Write a 0 to the keyboard flag array

Now we're done with the array handling. All we need to do is to exit our interrupt handler and return to the code that was interrupted by it. Well, not really. The keyboard needs some more attention before we can leave it. We need to tell the keyboard that we recieved the information it sent us and that we want it to tell us of later keystrokes too. This is also a matter of I/O port programming that I don't really understand. So let's just do it without asking any questions :-)

IN AL, 61 ; Reset the keyboard
OR AL, 80
OUT 61, AL
MOV AL, 20
OUT 20, AL

All right, NOW we can exit the keyboard handler. All we need to do is to POP back the registers we used and then we use the Interrupt Return instruction, IRET, to end the routine:

POP BX ; Pop back all the registers we used
IRET ; Exit the interrupt routine

All right! Now we have a complete keyboard handler except for that problem with the array segment. For some strange reason, this keyboard handler won't run through Absolute Assembly, even though I've made it work before. Since I didn't have the time to fix this problem, I'm going to give you the BASIC code as it would have looked if it had been working. We'll also add the keyflag array to the program. Save this in a file called KEYDEMO.BAS:

' A demonstration of advanved interrupt handling in QB:
DIM keydata%(127) ' Keyboard flag array
' ------ Created with Absolute Assembly 2.1 by Petter Holmberg, -97. ------- '
newkey$ = ""
newkey$ = newkey$ + CHR$(&H1E) ' PUSH DS
newkey$ = newkey$ + CHR$(&H50) ' PUSH AX
newkey$ = newkey$ + CHR$(&H53) ' PUSH BX
newkey$ = newkey$ + CHR$(&HBB) + CHR$(&H34) + CHR$(&H12) ' MOV BX, SEG keydata%(0)
newkey$ = newkey$ + CHR$(&H8E) + CHR$(&HDB) ' MOV DS,BX
newkey$ = newkey$ + CHR$(&HE4) + CHR$(&H60) ' IN AL,60
newkey$ = newkey$ + CHR$(&H30) + CHR$(&HE4) ' XOR AH,AH
newkey$ = newkey$ + CHR$(&H3C) + CHR$(&H7F) ' CMP AL,7F
newkey$ = newkey$ + CHR$(&H77) + CHR$(&HA) ' JA Release
newkey$ = newkey$ + CHR$(&HD0) + CHR$(&HE0) ' SHL AL,1
newkey$ = newkey$ + CHR$(&H89) + CHR$(&HC3) ' MOV BX,AX
newkey$ = newkey$ + CHR$(&HB0) + CHR$(&H1) ' MOV AL,01
newkey$ = newkey$ + CHR$(&H88) + CHR$(&H7) ' MOV [BX],AL
newkey$ = newkey$ + CHR$(&HEB) + CHR$(&HA) ' JMP Endkey
newkey$ = newkey$ + CHR$(&H24) + CHR$(&H7F) ' Release: AND AL,7F
newkey$ = newkey$ + CHR$(&HD0) + CHR$(&HE0) ' SHL AL,1
newkey$ = newkey$ + CHR$(&H89) + CHR$(&HC3) ' MOV BX,AX
newkey$ = newkey$ + CHR$(&HB0) + CHR$(&H0) ' MOV AL,00
newkey$ = newkey$ + CHR$(&H88) + CHR$(&H7) ' MOV [BX],AL
newkey$ = newkey$ + CHR$(&HE4) + CHR$(&H61) ' Endkey: IN AL,61
newkey$ = newkey$ + CHR$(&HC) + CHR$(&H80) ' OR AL,80
newkey$ = newkey$ + CHR$(&HE6) + CHR$(&H61) ' OUT 61,AL
newkey$ = newkey$ + CHR$(&HB0) + CHR$(&H20) ' MOV AL,20
newkey$ = newkey$ + CHR$(&HE6) + CHR$(&H20) ' OUT 20,AL
newkey$ = newkey$ + CHR$(&H5B) ' POP BX
newkey$ = newkey$ + CHR$(&H58) ' POP AX
newkey$ = newkey$ + CHR$(&H1F) ' POP DS
newkey$ = newkey$ + CHR$(&HCF) ' IRET
' ------ Created with Absolute Assembly 2.1 by Petter Holmberg, -97. ------- '

Now, take a look at the line of asm code that was supposed to set BX to the segment address of keydata%() but only set it to 1234h:

newkey$ = newkey$ + CHR$(&HBB) + + CHR$(&H34) + CHR$(&H12) ' MOV BX, SEG keydata%(0)

Look at the hexadecimal numbers: First comes a BBh, which is the machine language equivalent to MOV BX, immediate number. Then comes the numbers 34h and 12h. Didn't we just set BX to 1234h? There's an obvious pattern here! Here comes the trick: What if we changed the numbers a little here. If we put other numbers there, it wouldn't make any difference to the routine. What if we put the actual segment of the keyflag array there. It would work as good as any other number, but we would also get a working routine then. Of course we need to convert that number into a string, but that's really easy here. Just change the line to this:

newkey$ = newkey$ + CHR$(&HBB) + MKI$(VARSEG(keydata%(0))) ' MOV BX, SEG keydata%(0)

And that's it! Everything will work fine now.

Ok, that was the actual keyboard handler. But we haven't installed it yet. We need one routine that can install it, and antother one that can remove it. Let's begin with the first one:

The routine that initializes the keyboard handler will use two subfunctions of int 21h. The first one, subfunction 35h tells you the address of the current interrupt handler. If we set AH to 35h and AL to 9h (telling the subfunction that we want to get the address of int 9h, the keyboard handler) and call int 21h, it will return the current address of int 9h in ES:BX. All we need to do is to fetch these values and return them to two QB variables for storing. The other subfunction is subfunction 25h, which changes the address of the interrupt. (Actually, it's usually called the interrupt vector.) We only need to set AH to 25h, AL to 9h, DS:DX to the address of the newkey$ string and call int 21h. When this is done, our keyboard handler has taken over the control. I'm not going to describe every step in this routine like I've done before. There's nothing new in this routine, so I'll leave you to figure out how it works. It can be a good exercise for you to figure out exactly how this routine works. Regardless if you care or not, just add this code to the BAS file:

newseg% = VARSEG(newkey$) ' Segment address of our keyboard handler
newofs% = SADD(newkey$) ' Offset address of our keyboard handler
' ------ Created with Absolute Assembly 2.1 by Petter Holmberg, -97. ------- '
initkey$ = ""
initkey$ = initkey$ + CHR$(&H1E) ' PUSH DS
initkey$ = initkey$ + CHR$(&H55) ' PUSH BP
initkey$ = initkey$ + CHR$(&H89) + CHR$(&HE5) ' MOV BP,SP
initkey$ = initkey$ + CHR$(&HB4) + CHR$(&H35) ' MOV AH,35
initkey$ = initkey$ + CHR$(&HB0) + CHR$(&H9) ' MOV AL,09
initkey$ = initkey$ + CHR$(&HCD) + CHR$(&H21) ' INT 21
initkey$ = initkey$ + CHR$(&H89) + CHR$(&HDF) ' MOV DI,BX
initkey$ = initkey$ + CHR$(&H8B) + CHR$(&H5E) + CHR$(&HE) ' MOV BX,[BP+0E]
initkey$ = initkey$ + CHR$(&H8C) + CHR$(&H7) ' MOV [BX],ES
initkey$ = initkey$ + CHR$(&H8B) + CHR$(&H5E) + CHR$(&HC) ' MOV BX,[BP+0C]
initkey$ = initkey$ + CHR$(&H89) + CHR$(&H3F) ' MOV [BX],DI
initkey$ = initkey$ + CHR$(&H8B) + CHR$(&H56) + CHR$(&HA) ' MOV DX,[BP+0A]
initkey$ = initkey$ + CHR$(&H8E) + CHR$(&HDA) ' MOV DS,DX
initkey$ = initkey$ + CHR$(&H8B) + CHR$(&H56) + CHR$(&H8) ' MOV DX,[BP+08]
initkey$ = initkey$ + CHR$(&HB4) + CHR$(&H25) ' MOV AH,25
initkey$ = initkey$ + CHR$(&HB0) + CHR$(&H9) ' MOV AL,09
initkey$ = initkey$ + CHR$(&HCD) + CHR$(&H21) ' INT 21
initkey$ = initkey$ + CHR$(&H5D) ' POP BP
initkey$ = initkey$ + CHR$(&H1F) ' POP DS
initkey$ = initkey$ + CHR$(&HCA) + CHR$(&H8) + CHR$(&H0) ' RETF 0008
' ------ Created with Absolute Assembly 2.1 by Petter Holmberg, -97. ------- '
' Save the state of the keyboard flags:
keyflags% = PEEK(&H417) AND &H70
' Install our new keyboard handler:
offset% = SADD(initkey$)
DEF SEG = VARSEG(initkey$)
CALL ABSOLUTE(oldseg%, oldofs%, BYVAL newseg%, BYVAL newofs%, offset%)

The three lines above the CALL absolute code may seem confusing, but here's the explanation: Before installing the new keyboard handler it's a good idea to save the state of the keyboard flags. Don't mix this up with the flag array that we created for our own keyboard handler. The keyboard flags I'm talking about right now are a couple of bits stored at a fixed position in the memory by the default keyboard handler. They tell you the state of some of the keys on your computer, such as the Num Lock, Caps Lock, Scroll Lock, Alt, Ctrl and Shift keys. If we don't save the state of these keys before we install our keyboard handler and we hold down, say, a shift key before the new keyboard handler is installed and release it while this keyboard handler is in control, the old keyboard handler will think it's still being held down when we remove the new keyboard handler. Wow, that DID sound confusing, didn't it? Well, don't mind about it. We just save the state of these flags before installing our keyboard handler so we can set it back to that state afterwards.

Now all we have left is the routine that removes our keyboard handler and returns the control to the old one, and some additional BASIC code to test our keyboard handler with.

We'll just make it a simple demo that constantly prints the state of every element in the array on the screen so we can see if our keyboard handler reacts on the pressing and releasing of different keys. It's also important that we end the loop when we see that the ESC key is being held down, or otherwise we'll get stuck in the demo program. With our new keyboard handler it's not possible to use Ctrl-Break to abort the program. Our keyboard handler has the full control over the keyboard, remember? The state of the ESC key is stored in keydata%(1), so all we need to do is to wait for it to become 1. The routine that removes the handler only needs to use in 21h, subfunction 25h to put the old interrupt address back. And finally, we must set those keyboard flags to the state that they were in before we installed our keyboard handler:

PRINT "Multiple keypress demo. Press the ESC key to exit..."
' Display all of the keyboard flags on the screen:
FOR i = 0 TO 127: PRINT keydata%(i); : NEXT i
LOOP UNTIL keydata%(1) ' Exit from the loop if the user presses the ESC key
' ------ Created with Absolute Assembly 2.1 by Petter Holmberg, -97. ------- '
remkey$ = ""
remkey$ = remkey$ + CHR$(&H1E) ' PUSH DS
remkey$ = remkey$ + CHR$(&H55) ' PUSH BP
remkey$ = remkey$ + CHR$(&H89) + CHR$(&HE5) ' MOV BP,SP
remkey$ = remkey$ + CHR$(&H8B) + CHR$(&H56) + CHR$(&HA) ' MOV DX,[BP+0A]
remkey$ = remkey$ + CHR$(&H8E) + CHR$(&HDA) ' MOV DS,DX
remkey$ = remkey$ + CHR$(&H8B) + CHR$(&H56) + CHR$(&H8) ' MOV DX,[BP+08]
remkey$ = remkey$ + CHR$(&HB4) + CHR$(&H25) ' MOV AH,25
remkey$ = remkey$ + CHR$(&HB0) + CHR$(&H9) ' MOV AL,09
remkey$ = remkey$ + CHR$(&HCD) + CHR$(&H21) ' INT 21
remkey$ = remkey$ + CHR$(&H5D) ' POP BP
remkey$ = remkey$ + CHR$(&H1F) ' POP DS
remkey$ = remkey$ + CHR$(&HCA) + CHR$(&H4) + CHR$(&H0) ' RETF 0004
' ------ Created with Absolute Assembly 2.1 by Petter Holmberg, -97. ------- '
' Remove our keyboard handler:
offset% = SADD(remkey$)
DEF SEG = VARSEG(remkey$)
CALL ABSOLUTE(BYVAL oldseg%, BYVAL oldofs%, offset%)
' Restore the keyboard flags to their old state:
POKE (&H417), keyflags%

And that's it! This was an example of advanced assembly handling, but it wasn't too hard to understand, was it? There's much you can do with custom interrupt handlers. You can also make interrupt handlers that doesn't take over the control of the old one, but merely runs first and then passes over the control to the old interrupt handler. Some old computer viruses worked like this, but you should use them for nicer things :-)

I think this is enough for now. I'm sure you already have ideas for what you want to use this newly aquired knowledge for. If you want to look more at I/O ports I suggest that you go and find some document about advanced soundcard- or video programming. Just don't blame me if you fry your monitor ;-) If you want to play with interrupts more, download Ralph Brown's interrupt list and start with something easy, like making mouse routines or using basic DOS interrupts. If you want to do something more advanced, try writing interrupt handlers for other things than the keyboard. One thing you can do is to make interrupt routines that are called automatically 18.2 times per second. This can be useful for many things.

If you run into problems, (it's easy to do when programming these things) try hard to find them on your own. I've discovered that it's easy to start making the same, small mistake over and over again when you're writing asm code. If you find these annoying habits of yours by your own, you will usually never make them again. It's easy to forget simple things, such as that a certain register won't work in certain conditions, how the stack looks at the moment and that you accidently deletes some important number by overwriting it with another. The only thing you can do is to practise and practise until you get so used to assembly programming that you know where the potential traps are.

I'm planning to write a part 6 of this tutorial series, but I still haven't figured out what it's going to be about. Probably a lot of things that I haven't discussed yet. So see you next time, and good luck with your coding!

Suggest to Petter that Assembly 6 be on Chaos Theory bit-passing at this address.


Back to Top


By Zkman


Boogle Boo. This month's gallery shot comes from the impressive Vertical Scroller demo by our main man Pasco, who is already cool cuz he's from the Land of the Kangaroos. Go grab the latest demo at Pasco's Page.


Vertical Scroller


Back to Top

3d: Part III



(Editor's Note: This article is presented in the form of a commented program. You can download the full source and check it out for a full primer! This article includes the fully code and comments, too)

Welcome back! In this installment I'm going to simply show you a way to have more then one "entity" on screen. To do this I'm just going to make some modifications [actualy a re-write :) ] of the previous example. Last time I showed you a basic example of rotation and translation that involved recreating the player's ship from ASTEROIDS. This time we are going to add some asteroids, and a variety of ways for the "player" to control what happens to them...

ASTEROIDS as a primer to 3d


To do this we are going to use 2 different 3D line models [or 3D objects that are constructed from lines rather than polygons]. The models will be in 3D, but we are still only going to rotate along the Z-axis. We are also going to use a very simple way to project the 3D points to the screen.

To begin, we are only going to use these three subs:

DECLARE SUB makemodels ()
DECLARE SUB drawframe ()
DECLARE SUB moveent ()

Sub makemodels initiates the data array fields to the begining values. Sub drawframe performs all the transforms, projection and drawing to the screen. Sub moveent performs all the "physics" on the entities.

We will also use these data types:

TYPE pntdat

Type pntdat is used by the entities to hold location and vector values we also used it last time in holding the "model" of the space ship.

TYPE ddddat

Type ddddat [for "3D data"] is used to hold a 3D point. this is only used by the 3D line models to hold the endpoints of each 3D line.

TYPE lndat
v1 AS ddddat
v2 AS ddddat

Type lndat [for "line data"] holds one 3D line. the "v1" and "v2" contain the endpoints while "c" contains the line color.

TYPE entdat
location AS pntdat
vector AS pntdat
turnspd AS INTEGER

Type entdat [for "entity data"] contains all the information we will use for a single entity. "scale" [a floating point value] is used to "resize" the entity when transforming it's "object" [or model] to "world space" [a scale of < 1 but > 0 will make it smaller, while > 1 makes it larger] Location and vector hold the 2D [x and y] point used to describe where the entity is and where it is going. Angle indicates what direction the entity is faceing, and turnspd is used to make the entity turn. Thrust is used with the vector to get the entity moveing.

DIM SHARED obj(2, 12) AS lndat

Now then this array called obj [for "object"] is what will hold the 3D line models. The "2" indicates that we have two models and the "12" means that they each contain twelve 3D lines.

DIM SHARED ent(10) AS entdat


The ent array contains the entity values, and player is basicaly used as a "pointer" to the ent array. It indicated which entity the player is in control of [by doing this the "player" can control any one of the entities]

player = 1

But we will start by setting entity number 1 under the player's control

SCREEN 7, 0, 1, 0
WINDOW (-160, -100)-(160, 100)

You may remember this from last time. We are going to use SCREEN 7 for this simply because it allows us to have "flickerless" animation. The Window command is used to help convert "world space" to "camera space" [we won't use this once we get to performing real 3D...I promise:)]


Okay we start by setting up all the models and getting are start-up values.


Now we are in the "main-loop". The basic idea is to [step 1] draw a frame. Then [step 2] get the player's input from the keyboard. Finaly [step 3] perform the neccessary calculations to each entity [including anything special that the player indicated to do in step 2] Repeat back to step 1 until time to quit...


Here we perform step 2:


Player presses up...meaning thrust forward

CASE CHR$(0) + CHR$(72)
ent(player).thrust = -1

Press down...reverse thrust

CASE CHR$(0) + CHR$(80)
ent(player).thrust = 1

Turn left, then turn right

CASE CHR$(0) + CHR$(75)
ent(player).turnspd = -8

CASE CHR$(0) + CHR$(77)
ent(player).turnspd = 8

Player presses [SPACE BAR]...we stop the entities movement

CASE " "
ent(player).vector.x = 0
ent(player).vector.y = 0
ent(player).thrust = 0

Pressing this will shrink the model, the next will enlarge the model

CASE "-", "_"
ent(player).scale = ent(player).scale - .1
IF ent(player).scale < .01 THEN ent(player).scale = .1

CASE "+", "="
ent(player).scale = ent(player).scale + .1
IF ent(player).scale > 2 THEN ent(player).scale = 2


Ma Snart shows ya how to make this


This will "toggle" player control to the next numericaly lower entity

CASE "[", "{"
player = player - 1
IF player < 1 THEN player = 10

And this will make the next numericaly higher entity fall under the player's control

CASE "]", "}"
player = player + 1
IF player > 10 THEN player = 1

The quit key...

quit = 1

Then were on to step 3:

LOOP UNTIL quit = 1

All done....


In the program is the 3D line model data for both objects.. The player's "ship" is first, followed by the "asteroid" Each model has 12 3D lines; each DATA statement contains one line in this format:

' X1 , Y1 , Z1 , X2 , Y2 , Z2 , Color

Remeber each 3D point is in reference to the center [or zero] of "object space". In THIS program:

  • a +X is to the right while a -X is to the left
  • a +Y is to the bottom while a -Y is to the top
  • a -Z is to the TOP and a +Z is to the bottom

NOTE: because each model must have 12 3D line BUT the "ship" only needs 8, I included the 4 "zero-value" 3D lines to keep the program from crashing

'Player's "ship":
DATA -10,10,10,10,10,10,15
DATA -10,10,-10,10,10,-10,15
DATA -10,10,10,-10,10,-10,15
DATA 10,10,10,10,10,-10,15
DATA 10,10,10,0,-20,0,15
DATA 10,10,-10,0,-20,0,15
DATA -10,10,10,0,-20,0,15
DATA -10,10,-10,0,-20,0,15
DATA 0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0

'The "asteroid"
DATA -20,20,20,20,20,20,6
DATA -20,20,-20,20,20,-20,6
DATA -20,-20,20,20,-20,20,6
DATA -20,-20,-20,20,-20,-20,6
DATA -20,-20,-20,-20,20,-20,6
DATA -20,-20,20,-20,20,20,6
DATA 20,-20,-20,20,20,-20,6
DATA 20,-20,20,20,20,20,6
DATA -20,-20,-20,-20,-20,20,6
DATA -20,20,-20,-20,20,20,6
DATA 20,-20,-20,20,-20,20,6
DATA 20,20,-20,20,20,20,6
'end of data...

Ok. This is the first sub, drawframe. It will, as you probably supposed, draw the objects to the screen using SCREEN 7 stuff.

SUB drawframe


Start by clearing the frame

FOR i = 1 TO 10

Here we start a FOR/NEXT loop to transform the "object space" to the "world space" for each entity.

ex = ent(i).location.x
ey = ent(i).location.y
ea = ent(i).angle

Then put the entities values into temporary variables

sv! = SIN(ea * 3.141593 / 180)
cv! = COS(ea * 3.141593 / 180)

Because the entity is facing the one angle we can calculate the SIN and COS before we rotate each point of the model.

sc! = ent(i).scale

Put the entity scale value into a temp variable

IF i = player THEN
model = 1
model = 2

This sets up which object we will transform based on the player value.

FOR j = 1 TO 12

We will now transform each line of the object to "world space"

dx1 = obj(model, j).v1.x * cv! + obj(model, j).v1.y * sv!
dy1 = obj(model, j).v1.y * cv! - obj(model, j).v1.x * sv!

dx2 = obj(model, j).v2.x * cv! + obj(model, j).v2.y * sv!
dy2 = obj(model, j).v2.y * cv! - obj(model, j).v2.x * sv!

First we rotate each end-point of the objects line

dx1 = (dx1 * sc!) + ex
dx2 = (dx2 * sc!) + ex
dy1 = ((dy1 + obj(model, j).v1.z) * sc!) + ey
dy2 = ((dy2 + obj(model, j).v2.z) * sc!) + ey

Then transform it by adding the vector created by the location of the entity in "world space". But before we did that we multipliyed the point by the entities scale value [this is what makes it different sizes]. And before doing that to the Y values we add the corisponding Z [this is what causes the 3/4 or isometric view to work, and could also be considered "projection"].

So a basic isometric "projection" formula would be:
projectedX = rotated_and_translated_X
projectedY = rotated_and_translated_Y + pointZ

LINE (dx1, dy1)-(dx2, dy2), obj(model, j).c

then we draw the line [remember our transformation of "world space" to "camera space" is being handled by the WINDOW statement earlyer]



PCOPY 1, 0

All we show what we did :)


This next sub draws out the models based on the DATA

SUB makemodels


Here we are going to put the 3D line models together by reading the values in from the DATA statements.

FOR i = 1 TO 2
FOR j = 1 TO 12

READ obj(i, j).v1.x
READ obj(i, j).v1.y
READ obj(i, j).v1.z

READ obj(i, j).v2.x
READ obj(i, j).v2.y
READ obj(i, j).v2.z

READ obj(i, j).c


Now we set up the entities by picking random numbers for most of the fields.

FOR i = 1 TO 10

ent(i).scale = .5

I'll start by making each entity's object 1/2 size

ent(i).location.x = INT(RND * 320) - 160
ent(i).location.y = INT(RND * 200) - 100
ent(i).angle = INT(RND * 360)

And face them in a random direction at a random point

ent(i).turnspd = INT(RND * 16) - 8
ent(i).thrust = INT(RND * 6) - 4

And a random turning speed and thrust [remember a negative value means they are going forward]



Almost done.

SUB moveent

Here we perform entity calculations. This is the segment of the engine where collision detection and other "physics" would be calculated on the entities.

FOR i = 1 TO 10

ent(i).location.x = ent(i).location.x + ent(i).vector.x
ent(i).location.y = ent(i).location.y + ent(i).vector.y

First we move each entity by the entities vector

ent(i).vector.x = ent(i).vector.x + (ent(i).thrust * SIN(ent(i).angle * 3.141593 / 180))
ent(i).vector.y = ent(i).vector.y + (ent(i).thrust * COS(ent(i).angle * 3.141593 / 180))

We then calculate the vector given the value of thrust. Notice the formula seems new, but it isn't Remember our Z-axis rotation formula from last time?
rotatedX = pointX * COS(angle) + pointY * SIN(angle)
rotatedY = pointY * COS(angle) - pointX * SIN(angle)
Well, in our little ASTEROIDS engine to move forward you decrease Y [or -Y] and to move backwards you increase Y [or +Y]...anything in X would mean we were moving sideways...What we are doing is rotating a vector [in this case thrust] to the orientation of the entity [measured by the angle]

In this case or thrust vector's X = 0...and anything mutiplyed by 0 = 0 So we can optimize the formula by removing the need to multiply 0 by the SIN and COS of the angle [the result would be 0 anyway].

So our thrust vecter formula is:
thrustX = thrust * SIN(angle)
thrustY = thrust * COS(angle)

ent(i).angle = ent(i).angle + ent(i).turnspd

Here we change the angle by the amount of turnspd [if turnspd = 0 the angle remains the same]

IF ent(i).location.x > 160 THEN ent(i).location.x = ent(i).location.x - 320
IF ent(i).location.x < -160 THEN ent(i).location.x = ent(i).location.x + 320
IF ent(i).location.y > 100 THEN ent(i).location.y = ent(i).location.y - 200
IF ent(i).location.y < -100 THEN ent(i).location.y = ent(i).location.y + 200

IF ent(i).angle > 360 THEN ent(i).angle = ent(i).angle - 360
IF ent(i).angle < 0 THEN ent(i).angle = ent(i).angle + 360

Here we are keeping our entity in the bounds of our "world space" and keeping the angle between 0 and 360.

IF ent(i).thrust > 0 THEN ent(i).thrust = ent(i).thrust - 1
IF ent(i).thrust < 0 THEN ent(i).thrust = ent(i).thrust + 1

Here we are returning thrust to 0. If thrust always stayed at some value other then 0 the entity would always be accelerateing [we don't want that!]

IF i = player THEN
IF ent(i).turnspd > 0 THEN ent(i).turnspd = ent(i).turnspd - 1
IF ent(i).turnspd < 0 THEN ent(i).turnspd = ent(i).turnspd + 1

Here if the entity is the same as the one pointed to by player. We are going to return turnspd to 0 [if 0 the entity isn't turning] this is done so that the player has control over where the entity is faceing [else the entity is constantly rotating if not equal to 0]



That's all for this time. Run the program and have fun with it. Check back in April for more!

Find out why MA SNART doesn't cover the easy stuff like Bezier curves in this series by emailing him here.

Download the fully commented program!

Back to Top

Graphical Memory

By WhiteShadow

Virtual Screens, memory addressing, and more!

What is memory?
Memory is where your computer stores information, like the variables in your programs. So when you type,

foo = 5

you're actually telling the computer to put 5 into the place in memory which has been 'given' ( or more technically, allocated ) to foo.


How does memory work?
Memory is very much like a street of houses - as each 'house' has it's own address, each place in memory has it's own address. These addresses have two parts - called segments and offsets ( for a more detailed explaination about segments and offsets, read Thav's article in issue 5). So an address in memory will have a segment and an offset.

How do I access them in QBASIC?
Here's how you access memory in QBASIC -

DEF SEG = Segment
POKE Offset, Value
Value = PEEK(Offset)

There, that simple! Now, let me explain in more detail. DEG SEG sets the segment to use when you use commands such as POKE and PEEK. POKE writes the value of Value to the Offset specified, and PEEK reads the value at Offset into Value. At the end we call DEF SEG again ( this time without an = after it ) to return to the default QBASIC segment ( because if we didn't then QBASIC might start writing stuff into the wrong places in memory and your computer might crash - now we wouldn't want that to happen would we? ).

101 ways to trash your PC
Wait! You can't just start writing stuff all over memory like that ( unless you really know what you're doing! )! You might start writing stuff over important things in memory. For instance, you might accidentally write over your character's x and y positions in your game and he might start jumping around the screen insanely! Or you could write over even more serious stuff. This is called trashing your PC! Now you know how to do it - DON'T DO IT!!! ( Oh yeah, like you won't! )

How to find a safe place to write to
When you create a variable, or DIMension an array, QBASIC creates a space in memory for it. This space in memory belongs to you, and you can do what you want with it! But how do you find it's address so that you can write to it? Use VARSEG() and VARPTR() of course!

' Get the segment of foo
Segment = VARSEG(foo)
' Get the offset of foo
Offset = VARPTR(foo)

Now you can change to the segment using DEF SEG, and read and write to it using PEEK and POKE! Finding the offset of the string however, is a little different. Instead of using VARPTR(), use SADD(). Note: When finding the segment and offset of an array, always tell it to find the address of element 0 eg. VARSEG(foo(0)), VARPTR(foo(0))

Saving your memory to a file -
QBASIC comes with two commands that enables you to save your memory to a file and load a file into memory! This is done by BSAVE and BLOAD. Here is how to use them -

DEF SEG = Segment
BSAVE FileName$, Offset, Length
BLOAD FileName$, Offset ' Offset is optional

Now why would you want to do this? Well for instance, you draw a sprite for your game and you GET it. However, drawing it and getting it every time you run the program makes for ugly and big code, so you save it to file and load it up at the start of the program instead! The important thing to remember is to create an array that is the size of the sprite to load it into ( remember what I said before - that you have to allocate the memory before you can use it ). Here's an example:

DIM Sprite%(51) ' The Array to store the sprite in
GET(0, 0)-(9, 9), Sprite% ' Get a 10 * 10 sprite
DEF SEG = VARSEG(Sprite%(0)) ' Set the segment to Sprite's segment
BSAVE FileName$, VARPTR(Sprite%(0)), 104
' Save the 104 bytes ( an integer in
' QBASIC takes up 2 bytes ) of memory
' allocated to Sprite into FileName$
DIM Sprite2%(100) ' The array to store the sprite in
BLOAD FileName$, VARPTR(Sprite2%(0))
' Load FileName$ into the place in
' memory allocated for Sprite2%

OK, you might have a few questions. First, you don't need to set the segment when you use BLOAD. Usually. Secondly, about the length of memory and the size of the array ( WARNING - this is complicated. Don't read unless you're ready for a challenge ). An integer in QBASIC takes up two bytes, OK? So, as our array is 51 integers long, it takes up 104 bytes in memory. In SCREEN 13 each pixel takes up one byte in memory, so we need 100 bytes ( 10 * 10 ) for that. But what's with the extra 4 bytes? Well that's the space that QB needs to store the sprite's width etc. ( You can visit QBASIC Help for info on getting your array exactly the right size for GET - or, if you use DirectQB, use DQBsize(x1, y1, x2, y2) and divide the result by 2! )

Writing directly to video memory
PSET and POINT are too slow. You don't want to use those evil libraries polluted with ( would you believe it? ) NON-QB CODE! How do you write to the screen quickly? Well, this is where all the junk I've bombarded you with in the previous chapters really makes sense in game programming. There is a specific place in memory reserved for the video screen. All that PSET actually does is write to the place in memory where the video screen is stored. So if you could write directly to that buffer, then it wouldn't it be much faster?

First thing to warn you about, is that you should only really do this in SCREEN 13 because in other modes it's much more complicated. This is one of the reasons why SCREEN 13 is so popular - because it's easy to use.

OK, you need the segment of where the video memory is. It's at &HA000, so,

DEF SEG = &HA000

Then you need the offset. Thankfully that's 0! Right then, now we just have to write to the memory. Some of you may have recognised a problem though. Memory goes in straight lines ( 1, 2, 3, 4 etc. ), but a SCREEN is two-dimensional! So, we have to work out how to convert 2D into 1D. This is actually easy. The SCREEN info is stored in bytes 0 - 319 for the first line, 320 - 639 for the second etc. The formula to work this out is ( prove this yourself if you want to ):

Offset = (Y * 320) + X

Now you have it! You can write quickly to video memory! Here's a few lines to help you out though...

DEF SEG = &HA000
Offset = (Y * 320) + X
Colour = PEEK(Offset) ' Like the POINT command
Offset = (Y * 320) + X
POKE Offset, Colour ' Like the PSET command

Saving Pictures
Using BSAVE and BLOAD, you can easily save and load pictures quickly. How? Well, tell BSAVE to save the contents of the video memory! This is great for storing pictures for your game. Here's some code...

DEF SEG = &HA000 BSAVE "picture1.bsv", 0, 64000 ... BLOAD "picture1.bsv"

There! You've saved a picture, and then, very quickly loaded it up!

Video screens
Here's the final thing that I'm going to teach you - how to make video screens in plain QBASIC, so you don't have to use libraries! Right, as you might already have guessed, you could create an array with 64000 elements and treat each one as a pixel. The only problem is that QBASIC won't let you do that, as it's too big! What can we do? Well, remember that an integer takes up 2 bytes, and 1 byte equals 1 pixel. So, if you create an array of 32000 integers, it's 64000 bytes in size so you can treat each byte as a pixel. However, to access each byte, you're going to need to use PEEK and POKE! Here's some code...

DIM VScreen(32000) ' Create the virtual screen
DEF SEG = VARSEG(VScreen(0)) ' Change to the segment of VScreen()
Colour = PEEK(VARPTR(VScreen(0) + (Y * 320) + X) ' Read from it
POKE(VARPTR(VScreen(0) + (Y * 320) + X), Colour ' Write to it

It's not wise to use too many virtual screens though as they will take up too much memory.

So, this is the end to your comprehensive guide to memory and QBASIC. Hopefully you will find BSAVEing and BLOADing sprites and pictures useful, as well as being able to write directly to video memory and even make your own virtual screens. However, I have gone over everything as quickly as possible to make this article short. If you need help on a particular topic, look at QB Help. You can also contact me on ICQ - my UIN is 25105257! I'm looking forward to hearing from you!

Feel free to email WhiteShadow to find out how to get color in SCREEN 0 at this address.


Back to Top

prt preview

By Zkman
Thanks to DarkDread

  He stunned the qb world with Legend of Lith, and Lianne ... in the Dark Crown. Although they seem less than golden in the age we now live in, those two games from DarkDread nevertheless have proved influences for many of qbasic's great titles. Now, after a great recession from the world of coding, the man is back, with a new RPG: Eternal Frost. And qb:tm is here to bring you the exclusive info.

The RPG from a QB Legend...


Darkdread calls this new venture a "Story RPG"; i.e., money, battle systems, non-linearity are not nearly as high a concern as the progression of the story. And although each of the above listed parts are all very cool, the story is what you will remember. The twists and turns of the story are being chopped out as you read this, but here's a basic summary of the novel turn.

A young vampire by the name of Ravyn is trying to deal with his humanity and understand who he is. Unfortunately, two strong vampires named Id and Ego have set into motion a chain of events, which, left untouched, would bring eternal winter, and Cheriore, eternal darkness. Por que? Because Id and Ego believe that doing so will turn them into gods. Unfortunately for them, Rosalyn, a third "strong" vampire, does not agree with Id and Ego, and she seeks a strong willed human to aid her. Unluckily for Ravyn, she is picked, and is turned into a vampire by Rosalyn.

There's the backstory of the game. But this is basically like saying Final Fantasy 7's story is about some guy named Cloud being picked into a military group to save the world. There will be many more twists and turns during the game that you'll have to deal with. As not to spoil the experience, we won't reveal those here, but be aware that they are of the highest quality. I was also luckily enough to receive a pre-release demo, and the text quality is very high, and novel-like. It seems as if the story and character development are a concern, and not an afterthought, an idea many budding designers should take note of.


Talk to me, baby!


The gameplay is similar in many ways to prior Darkdread games. Using a first person perspective still, the game does not seem as "deja vu-ish" as walking around in previous DarkDread games. There is a wide variety of tilesets, and interesting people and shops for you to see. Also, with a world much bigger than Legend of Lith, an overhead view map can be used to select a location where you would like Ravyn to travel to. Two towns, Arren and Tidia, and the areas between them can be visited

The spell system works different from LoL as well. To get a spell, you sometimes can find scrolls (some hidden shrewdly and some out in the open). If your character is of a high enough level, you can "read" the spell and you will be able to use it. Look for spells common to RPG's such as Heal, Fire, etc., but also look for some "super-powerful" spells to make an appearance to, featuring some new concepts.


The town of Arren

  The battle system, aside from the new spells, isn't very different. It still has a turn based system, with features such as "fight", "cast", "run", etc.. Expect to meet many undead enemies such as zombies, skeletons, and ghosts as you travel. The coolest new feature to the battles, though, is a theme-based one: If you're in a fight with a human, you can drink it's blood to gain HP! Yes! You can also expect to see our favourite arch-nemesis Id and Ego throughout the game.

Although some parts might be slightly linear, the backstory, long playtime, scroll system spells, and NPC Interaction will make this a must download for a serious RPG player. Expect to see a demo in the coming months, and be sure that qb:tm will keep you up to date on DarkDread's latest project.

Since I write the little end of article messages, you can't bug me! Ok, since you said please, I'm at this address.


Back to Top

Midi Composing 2

By 19Day

Part I of the MIDI series is in Issue 7

(If any of you find this tut useful, do me a favor and write me a 3D Studio Max R2 tutorial, I wanna make my own cutscenes and stuff for MazeRPG.)

When I left you last time I had just finished explaining the basics of Midi creating, using WinJammer. Hopefully, you have been experimenting with Midi's and trying them out on your friends. If you want to send a sample my way, although, not for critique, I don't usually feel right telling someone thier peice of music isn't good, because it's all subjective, but if you want me to hear something or have a specific question, you can always ask a question of me through QB:TM or email me directly at


Download WinJammer!

Now, I'm just going to go off on a tangent here, I'm going to explain what you can do with your experimental midi's after you've made one. The only QB midi playing lib out there right now is QMIDI, and I'll briefly describe a few of the quirks with it.

First of all, I use QMIDI 3, as QMIDI 4's stuff doesn't like my ultra new sound card, so all my instruction is involving version 3, which is basically the same as 4, with some small differences.

First, you need to load the driver. The driver is what hooks every- thing up and it's name is SBMIDI, if you try to use QMIDI without this driver, your computer will do very naughty things. Now, for the extended functions like PAUSE and stuff, you have to load SBSIM after you load the driver. But since I don't particularly care about pausing, I don't load SBSIM, it just wastes memory.

Now, what you have to do is make an array to hold the music, and then load the midi into that array and then play it, it's pretty well explained in the docs that come with QMIDI, a few things to note though: If you have a Wavetable-able card, load the SBMIDI driver with the /3 parameter, your midi's will sound much nicer. Use a batch file to load and unload the SBMIDI driver so your users don't get angry at you for crashing their computers.

Your music may sound different in Qmidi than in windows, signifigantly different, as if it were using a different instrument. I suggest using different combo's to achieve the best effect in QMIDI, screw windows. Some things just don't make as loud a sound as you thought, so try increasing the volume of that track

Okay, back to WinJammer stuff.
(I suggest you read this bit completely and carefully, as I explain quite a few things, and then admit my ignorence, and discover a few things myself that for some reason, when I really could have used them, I couldn't find.)

Making effects is pretty damned hard to do, I suggest you avoid them, as you have to change most of your notes manually. WinJammer isn't very good for thigns like that, but just straight music it can do well.

If you want to change the sounds of your notes, like the velocity and stuff, place the note, and then double click on it.

You'll see a new window called Note Event. Basically Midi works by events, everything it does is an Event in the midi file, and it just works out what each event means.

The top entry is Time, that's just where the note starts in the musical peice. The Channel indicates, you guessed it, the channel the midi is on. Key is the note played, and changing this is one of the few ways you can get sharps and flats, and the other way is stupid, so use this way. (Note: Wrong again, I am stupid today, I never really experimented with this thing. YES, you can get sharps and flats real easy, at the bottom of the sidebar, there is a wierd square will trailing lines, and below it, a pound (#) sign, clicking of the # lets you place a note like that, although it doesn't seem to differenciate between a sharp and a flat, it does then, provide DOUBLE the number of tones the track will allow, neat.)

Velocity, this tells how must pressue there was on the note, and I can't find a good way to get this high and STAY high, WinJammer is stupid here, cause I find many patches are too soft, and I can't seem to change a whole bunch of notes' Velocities at once. (Yes you can, I stupid, keep reading to find out)

Length, if you don't like being restricted to the note lengths given on the side bar, use this to change things around, make it slightly longer or shorter according to your needs.

Shift following events, basically, this means if you move the note around, will everything after it move the same amount of space too, usually, yes.

Now, if you want to change a few other things, but not Note events, you should use the button next to the synth keyboard button at the top, it looks like a small list (next to the tempo)

This shows you the raw events in your midi, like controller changes and such. Click on the window to activate it and left click, then say insert (new event). There are quite a few options here.

Note On: You basic musical note.
Key Pressure: Not really used, not decernable effects.
Controller change: Can do a few things, but I mainly only use this to increase the volume of a channel.
Patch Change: Have the channel change it's patch midway through.
Channel Pressure: Like Key Pressure, not used.
Pitch Bend: Suppose to bend the pitch up or down, doesn't do anything for me.
The other ones are for the really hardcore Midi'ers that want to use MCI commands and wavs, but since this is for Qmidi, and Qmidi doesn't care about those things, no point in using them.

Notice, in that entire set, there is no Channel Velocity, bastards! (Please keep reading, I'm a dumbass, look below, I was wrong)

There are quite a few useful things in the Track menu.
Clear Selection, Enter time 1 and time 2, and it will delete everything in between.
Track filter will kill everything from channel # to channel #.
Filter Events will kill all the event fitting whichever boxes you click.
Information will tell you a little bit about your current midi track.
Merge will combine the events on one track with another.
Quantize will take the current track and depending on the note size you give it, it will respace and reshape everything in the track to fit that size.
Replicate will copy from Time1 to time2, pasting it at Time4, num4 number of times.
Split will split the channel up depending on the parameter you give it.
Transform will Transform an aspect of the track by adding or subtracting num1, multiplying by num2, diving by num3 and then adding or subtracting num4. This can change a lot of things including NOTE VELOCITY, the thing I've been looking for, figured I would only ever figure it out when I write a tutorial, heh.

And finally, the Transpose it useful too, you can do chords. It takes all the notes and moves them up or down a certain number of semitones, experiment.

Now, a little trick. I've found that quite a few of the patches are too soft to hear generally, and sometimes even in Qmidi, and we now know how to increase the Tracks overall velocity. But if you add a bunch of notes and stuff, you need to do it to them too, but the Transform does to to the entire track, so whaddya have to do is, see, transpose all the velocities to 0 by multiplying by 0, and then "finnaly adding or subtracting" the number you want, like 100, there ya go.

Okay, enough WinJammer, but here's some of my favorite patches, I suggest you experiment with them, they are nice and seem to work well together:

  • Banjo - good tangy sound
  • Choir Aahs - really goosepimply feeling sound
  • Church Organ - very nice
  • Flute - I love flutes
  • FX 5 (brightness) - kinda harsh, but I like it.
  • FX 8 (sci-fi) - Really neat sound, I used it for everything in the past.
  • Ocarina - sickningly sweet at times
  • Music Box - Another goosepimply feeling
  • Pad 4 (choir) - like choir aahs, nice.
  • String Ensemble 2 - very good for background melody
  • Tubular Bells - love it, its a great patch
  • Voice oohs - like choir aahs, but lower, less sweet.

If you doubt my ability (heh, _I_ doubt my ability ^_^;; ) then why not listen to what I have made, then decide for yourself, do I know what I'm talking about, or could you do better without my interference. If you want a listen, goto...

There, you will find about 14 midi's, all named SBH##.MID, you can see the increasing complexity and better sounding melodies, remember, I've only been doing this for 5 months, I'm learning too =P

Notable Games that used/are using MIDI:
WetSpot2 - Not sequenced by Angelo himself, but he has good takes (Take Me On by A-HA)
MazeRPG (heh, um, 19day's game, the OTHER 19day, heh)
Space-a-Roo - had some great music in it, I still hum it.
Puz - neato music, I liked the feeling, puzzled.
Try them and see how well it works.

Making a simple midi:
First of all, make a new channel and track for it, and choose a patch, this will be the baseline. You can use Channel 10 to get drum effects. Generally, baselines are 1, 2 or 4 (sometimes) staff that have a constant rythym that repeats for the rest of the midi.

So just plunk down a few notes, and then use Track -> Replicate to copy it. Copy it about 10 or 20 times, we can trim it later.

Then make a new track, this will hold the melody. You usually have a few staves of beat, and then the melody rises from it, climaxes, and then dissolves again. So make a simple melody (the hardest part) with a good intro and exit.

Then I usually make a "backup singers" track, where I put a repeating non-baseline track, just to provide some sound other than the melody and the baseline, you'll see that in my midi's alot.

Looping Midi's:
This section will deal with two things at once, the looping of the midi music in the MIDI, and then looping of the MIDI in Qmidi.

The first point is short and simple: When you make your midi, begin it like you would a simple peice of music, usually not very complex, but the have the complexity increase as the song goes on, usually in sets of 4 segments. Then, have the music end abruptly, as it will be, in Qmidi, looping over and over, and if you build down the song and then have your baseline doing the same thing it did at the beginning, then when you hear it looped, that baseline or whatever low-complexity music you have going, it's length will double, because you hear it at the end, AND at the beginning again...

As for Qmidi, I have vers. 1 and 3, I hear 4.x might fix this problem, but I'm not sure. But according to the information in the past Qmidi's, there was no real easy way to check for the length of a midi in seconds, and have it repeat when it was over. What I did was use the MIDITIME! function, and made a sub called CheckForMidiTime, that is put in my RPG's main loop. It goes into the sub that has the the second times for the midi's I plan to use, and it's just an if statement that decided on the number of seconds to be put into a temporary variable. Then it checks to see if MIDITIME! is higher than that variable.

The reason I use a varaible as opposed of just doing the checking directly, is so I can add a new midi easily and hear it to see if QMIDI likes it, I just set the temporary var to 60 (1 min) before all the 1 line IF statements, so I can at least hear 1 minute of my new MIDI before making an IF statement for it.

Are you a newbie who is trying to splice Beethoven's 5th with "midgar.mid" from Final Fantasy 7? Go bug 19day until he does it for you!


Back to Top

next issue

By Zkman


Gracioso for readin the latest copy of qbasic's best e-zine. Check back in on April 17 for Issue #9. And why should you do that? Umm...because I said so! So there! Also, don't forget to register your QB company with the Visionaries Exchange. Till next time... END

We got next...

Back to Top