█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█ ▌ISS 3 VOL 1 December 1997 ▐ ▌ █\ /█ /███\ ███\ /█\ ███\ █ /█\ ██\ █ ▐ ▌ \█\ /█/ /█/ \█\ █><█ /█^█\ █/ █> █ /█^█\ █\█\ █ ▐ ▌ \█\ /█/ <█< >█>███> /█/ \█\ █▄█< █ /█/ \█\ █ \█\ █ ▐ ▌ \█V█/ \█\ /█/ █><█ /█▀▀▀▀▀█\ █/\█\ █ /█▀▀▀▀▀█\ █ \█\█ ▐ ▌ \█/ \███/ ███/ █/ \█ █ \█ █ █/ \█ █ \██ ▐ ▌ ▐ ▌ N E W S L E T T E R ▐ █▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█ \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ CONTENTS: Article: Description: ------------------------------------------------------------------------ Programming the SB add *real* sound to your programs Programming the Mouse better interfaces with the mouse ▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀ ############################################################################## ########### PROGRAMMING THE SOUND CARD ########### ############################################################################## It takes more and more to push your game above the rest anymore. Once you have your game engine created, go through and spice it up with sound. Not just beeps and squelches, SoundBlaster sound. On the Internet I've noticed an outbreak of SB programming tutorials, so I only thought it would be appropriate if I made a tutorial for the newsletter. In this tutorial, we will learn these topics: 1) basics 2) modulator and carrier 3) 4 basic properties 4) time to program the registers ***** BASICS ***** First, the basics. We will be learning to utilize the FM synthesis part of the sound card, NOT play *.WAV files and sample the microphone, which is a part of the DSP (digital signal processing) chip. FM synthesis doesn't allow for recording sound or voice, nor does it allow complexities of sound that we normally hear around us (people on the phone, the TV, a car driving by, etc.) What FM synthesis DOES do is allow a great way to create sound effects and play music, like a MIDI file, though we will not actually learn anything about MIDI (musical instrument digital interface) today. ***** MODULATOR AND CARRIER ***** Now for some real information. How an FM sound is made. We must learn this in order to "craft" our FM sound's shape. The FM sound is made up of two basic components, the modulator and the carrier. The CARRIER is a drone, which delivers the power of the modulator. The MODULATOR overlies the carrier and modifies it's shape to form an interesting sound, not just a tone. Basically it's like this chart: ┌───────────────────────────────────────┐ │ ------------------------- │ We need the modulator to change the │ carrier - power but just a flat sound │ carrier's shape and the carrier to │ │ give the modulator power, which are │ /\________ │ basically the same thing two ways. │ / \ │ Therefore we must program the 4 │ modulator - sound but no power │ basic properties for each section. └───────────────────────────────────────┘ We will also need to get offsets for the carrier and modulator so we can program them separately. ***** 4 BASIC PROPERTIES ***** The 4 basic properties are what we need to learn now. They are very important. They are basically just stages of an FM sound's life. Look at this chart. ┌───────────┬───────┬─────────┬─────────┐ │ /│\ │ │ │ V │ / │ \ │ │ │ O │ / │ \ │ │ │ L │ / │ \│_________│ │ U │ / │ │ │\ │ M │/ │ │ │ \ │ E │ │ │ │ │ │ ATTACK │ DECAY │ SUSTAIN │ RELEASE │ └───────────┴───────┴─────────┴─────────┘ TIME -> As you can see, the vertical axis is volume and the horizontal axis is time. As time progresses, you see the stages of an FM sound in order: attack, decay, sustain, and release. Here's each one in detail. >ATTACK. The attack is how quickly to sound rises to the peak. The attack is the first part of the sound and changes the way the rest of the sound works. The attack can be like a sharp bite or a smooth increase. >DECAY. The decay follows the attack. The decay is how quickly the sound declines to the sustain volume. Again, the decay can be like a sharp drop-off or a steady decline. >SUSTAIN. Unlike the decay and attack, sustain is not the rate at which that part of the sound happens, it is how loud it is. It is important because it not only select the sound's mid-part volume, but also sets how low the decay goes and how high the release starts. >RELEASE. The release is basically the same as the decay. During the release the volume drops from the sustain level to 0. Think of the sound like a model rocket. The rocket rises sharply during the launch (attack). Then it slows and begins to fall (decay). Then the parachute deploys and holds the rocket steady for a second (sustain). Finally, the rocket drifts down to earth (release). ***** PROGRAMMING THE REGISTERS ***** Now it's time to program, the hard part. Here are this section's sub-sections: 1) card organization 2) hexadecimals 3) find the address and offset 4) making notes 5) now do all that in QB... ***** CARD ORGANIZATION ***** Like all parts of the computer, the sound card has a base address. From there you get other addresses. The memory on the card is organized into registers (just like the CPU, if you're familiar with ASM). Each register holds 8 (count 'em, 8) BITS, enough for 1 ASCII letter. There are two addresses on the card we send data to, the register address and the data address. To the register address we send the number of the register we want to write to, and to the data address we sent the data we want stored in that register. The registers store the basic FM properties and turn channels on and off. ***** HEXADECIMALS ***** Hexadecimals are just another numbering system. We normally use decimals (base 10), but computers use hexadecimals (base 16), because they are much smaller in byte size but hold the same value. Let's dissect the number &HBD0. First of all, throw away &H. &H just signifies that BD0 is a hexadecimal, not a string or decimal. B and D are numbers, and you already recognize 0 as a number. How do we convert hexadecimals to decimals and back? 3 ways: do it with pen and paper, use a scientific calculator, or do it in QB. Here's how to do it in QB: 'BEGIN SOURCE CLS PRINT Hex_Number 'END SOURCE That will convert hex to decimal. QBasic converts the hex number before printing it. For example, if I type: PRINT &HBD0 the output will be 3024 That's simple. How about decimal to hex though? Well, QBasic can't actually work with hexadecimals, so it has to store hex numbers as a string using HEX$. Type this: 'BEGIN SOURCE CLS PRINT HEX$(Any_Decimal_INTEGER%) 'END SOURCE Just fill in the variable. Note that you must use a deciamal INTEGER, there are not hexadecimal decimal points! For example, if I type: PRINT HEX$(3024) QBasic says BD0 Just affix an "&H" and you have your number. ***** FIND THE ADDRESS AND OFFSET ***** To find the number of the register we want to program, we need some charts. First we find the register we want, then we use the offset of the channel to find the register. First let's look at the registers: 20h 40h ┌───┬──┬───────┬─┬─┬────────────┐ ┌───┬──────────┬────────────────┐ │Bit│7 │6 │5│4│3 2 1 0│ │Bit│7 6 │5 4 3 2 1 0│ ├───┼──┼───────┼─┼─┼────────────┤ ├───┼──────────┼────────────────┤ │Use│AM│Vibrato│ │ │Octave Shift│ │Use│Scaling │ Output Level │ └───┴──┴───────┴─┴─┴────────────┘ └───┴──────────┴────────────────┘ 60h 80h ┌───┬────────────┬──────────────┐ ┌───┬──────────────┬────────────┐ │Bit│ 7 6 5 4 │ 3 2 1 0 │ │Bit│7 6 5 4 │ 3 2 1 0 │ ├───┼────────────┼──────────────┤ ├───┼──────────────┼────────────┤ │Use│ Attack Rate│ Decay Rate │ │Use│Sustain Level │Release Time│ └───┴────────────┴──────────────┘ └───┴──────────────┴────────────┘ These charts are maps of the bits in registers 20h, 40h, 60h, and 80h. As you can see, 60h and 80h control the basic FM properties. 20h and 40h control extra properties which we will not discuss in detail but you can experiment with on your own for even more effects. Anyway, these will only set the carrier of channel 1. To find the registers for the modulator and other channels, we need the offset table. Here is the offset table: Offsets ┌─────────┬─────────┬───────────┐ │ Channel │ Carrier │ Modulator │ ├─────────┼─────────┼───────────┤ │ 1 │ 00h │ 03h │ ├─────────┼─────────┼───────────┤ │ 2 │ 01h │ 04h │ ├─────────┼─────────┼───────────┤ │ 3 │ 02h │ 05h │ ├─────────┼─────────┼───────────┤ │ 4 │ 08h │ 0Bh │ ├─────────┼─────────┼───────────┤ │ 5 │ 09h │ 0Ch │ ├─────────┼─────────┼───────────┤ │ 6 │ 0Ah │ 0Dh │ ├─────────┼─────────┼───────────┤ │ 7 │ 10h │ 13h │ ├─────────┼─────────┼───────────┤ │ 8 │ 11h │ 14h │ ├─────────┼─────────┼───────────┤ │ 9 │ 12h │ 15h │ └─────────┴─────────┴───────────┘ All we do is take the register we want, select a channel, select carrier or modulator, and add the two. For example, let's say we want the properties of 80h with the channel 6 modulator. We look in the offset chart for chan. 6 and modulator. The chart says 0Dh. So we take 0Dh + 80h and get 8Dh. So, we now have register address 8Dh to program. We just send the number to 8Dh. Now you know how to find the offset of any register you want, we need to learn to find the number to put in the register. Let's say we want to send an attack of 5 and a decay rate of 3 to register 80h. We will use Windows calculator in Scientific mode to do this. First start Windows calculator. Now, from the View menu, select Scientific. As you can see in the upper-left part of the window, there are 4 radio buttons labled Hex, Dec, Oct, and Bin, meaning hexadecimal, decimal, octal, and binary. Select the decimal button. Type in the number 5, for attack of 5. Now click the Bin radio button and you will see the number converted to binary. 5 in binary should be 101. Now we'll convert 3. Repeat the process. 3 should be 11. Now we have 101 and 11, but they each need to be 4 didgits long, so add 0's to the beginnings so we get 0101 and 0011. Now put them together. Make sure that the attack and decay rate are in order, following the chart of register 80h above!! So, we get 01010011 for the register value. Now, cut off the leading 0's so we get 1010011. Now select the Bin mode in calculator and press clear. Now enter 1010011 and click the Dec mode button. 1010011 in decimal should be 83. So, at last we get the value 83 for register 80h so we get an attack of 5 and a decay of 3. It's not really that hard to remember these steps even though it seems like alot. This is sometimes confusing because we are dealing with 3 number systems here, hex, decimal, and binary. Here are the steps to doing this. You may want to write them down for future use: 1) find the register and the offset, add them together to get the final reg# 2) figure out which values you want for the sections of the register 3) convert values to binary with Windows calc. 4) make each value fit the size for it, eg. in register 20h, you need 1 digit for AM select, 1 for vibrato select, 2 unused (enter 0's for these), and 4 digits for octave shift. 5) put all values in one line (10, 011011 -> 10011011) 6) remove leading 0's (00101101 -> 101101) 7) convert to decimal (101101 -> 45) 8) set the value to the register (we'll learn that next) This can be difficult to understand/remember, but I am available via email to answer your questions so don't hesitate to ask. Now we'll learn the actual procedure to do all this in QBasic, after some notes on making music. ***** MAKING NOTES ***** We've learned to set up channels and sounds and instruments and stuff, but we haven't learned to make notes. Here are some registers we haven't looked at yet: A0h B0h ┌───┬───────────────────────────┐ ┌───┬───┬──────┬──────┬─────────┐ │Bit│ 7 6 5 4 3 2 1 0 │ │Bit│7 6│5 │4 3 2 │ 1 0 │ ├───┼───────────────────────────┤ ├───┼───┼──────┼──────┼─────────┤ │Use│ 8 LSB of note │ │Use│n/a│Switch│Octave│ 2 MSB │ └───┴───────────────────────────┘ └───┴───┴──────┴──────┴─────────┘ These registers are for channel 1, but are the same for the other channels, just change the address: A0h & B0h = channel 1 A1h & B1h = channel 2 A2h & B2h = channel 3 ... ... A8h & B8h = channel 9 Anyway, here's an explnation: There are 10 bits in a notes value, and since a register can only hold 8 bits, we have to put 2 in antoher register. These 2 are the MSB (most significant bits) and the other 8 are the LSB (least significant bits). The LSB are the LAST bits of the value. In B0h, 7 and 6 are unused, 5 switches the note on and off, and octave is a value 0 - 7. There is a note chart of common music notes in SB.HTML, a file in the newsletter archive you'll learn more about later. Anyway, from that chart you would see that a D# is 198h. Take the 1st hex digit for the MSB and the last two for the LSB. So, D#'s MSB would be 1h and it's LSB would be 98h. Now convert the hexadecimals to decimal in QB, then from decimal to binary with calculator, then just use the numbers like you did in the last section: combine all the binaries and convert to decimal. ***** NOW DO ALL THAT IN QB ***** If you've read this article straight through so far, take a break and come back in a minute. Now to work: We have learned what we need to, how to craft our sound, how to then choose values for properties of FM sound, find the register number we need, and convert the values to a number we will apply to the register. The last step is where this final section comes in: writing those values to the registers. We will use the OUT command to write to the hardware. If you aren't familiar with OUT, look it up in QBasic help. Anyway, normally we'd have to write the register number to the register port and the data to the data port, requiring two OUT statements, but we can take a shortcut to do that for us. We will use a SUBroutine. Insert this code exactly into your program: (open this file in qbasic and use copy/paste to do this) 'CUT HERE ######### SUB SetReg (Reg, Value) OUT RegAddr, Reg OUT DataAddr, Value END SUB 'CUT HERE ######### And add this to the beginning of your program: 'CUT HERE ######### DECLARE SUB SetReg (Reg%, Value%) CONST BaseAddr = &H220 'CHANGE IF YOUR SOUND CARD USES ANOTHER BASE ADDRESS CONST RegAddr = BaseAddr + 8, DataAddr = BaseAddr + 9 'CUT HERE ######### What does that do? Well, the second section which you add to the beginning of your program declares the sub and defines some constants: the base address, the register address, and the data address, which you don't need to worry about, they're just for the sub SetReg. What does the SUB SetReg do? Well, normally we'd type: OUT 552, &H80 'send the register number to the register port OUT 553, 83 'send the data to the data port to write the value 83 to register 80h. But, with SetReg, we just type: SetReg &H80, 83 and it does the work! Here's the syntax for SetReg in case you didn't catch it in the DECLARE statement: SetReg Register_Address, Data That's it! *#*#*#*#* FINAL NOTES *#*#*#*#* For your convenience, I've written an HTML file included in this archive called SB.HTML. It includes notes and all the charts in this article. Do this: 1) open SB.HTML in your browser (Netscape works best for this but IE is okay) 2) turn on your printer and set it to black & white mode, if you wish 3) select print, and voila! a complete SB programmer's reference is printed! _,-~`~-,_,-~`~-,_,-~`~-,_,-~`~-,_,-~`~-,_,-~`~-,_,-~`~-,_,-~`~-,_,-~`~-,_,-~`~ ############################################################################## ########### USING THE MOUSE IN QB ########### ############################################################################## Well, I realize most programmers already know how to use the mouse, but I haven't seen many programs that utilize the mouse, which is kind of disappointing because a good graphic interface with a mouse really makes a program better than boring text-based menus. Programming the mouse isn't really hard at all. It's not nearly as complicated as programming the Sound Blaster because with the mouse, all the routines are written for you, you don't have to know anything about the hardware. So, to get started, you need the mouse routines. These are enclosed in MOUSE.BAS, a file included in the archive this document is from. MOUSE.BAS can also be downloaded from almost any QBasic site, one of the best of which is probably http://qbasic.com. But anyway, those are the routines, and they must be inserted in your program. There are several ways to do this: in QuickBasic 4.5 and probably earlier versions, too, you can use the "Merge" option from the File menu to insert the program. However, in QBasic 1.0 and 1.1, the version that comes with DOS, there is no merge option, you have to do it with copy & paste. But instead of copying each routine and pasting it into your program, just use Editor to do it. Here's how: start edit by typing "edit" at the DOS prompt. Open the BAS file. Highlight the entire file and press Edit -> Copy. Open a new file or the program you've already started. Press Edit -> Paste. There. But there's an even easier way to do it, if you haven't already started your program: open MOUSE.BAS in QBasic, click File -> Save As, then type a new filename. Now just put your program code after what's already there. ! Be careful ! There is more to the routines than just the SUBs, there is also some code that goes in the main module of the program. If you forget to copy this code as well, QBasic will crash and Windows will deliver a fatal error message or, if in DOS mode, your computer will produce a long beep and you will have to reboot your computer and start over, so don't let that happen! Once you have the routines loaded up, it is a very simple process of calling the routines and determining where the user clicked. To make the mouse pointer appear, type "mouseshow". To make it disappear, use the code "mousehide", which calls the mousehide sub that makes the pointer disappear. The pointer is automatic, in screen 0, the pointer is a block. In screen 12, the pointer is a nice small arrow. In screen 13, the pointer is a larger, less attractive arrow, but you get the 256 colors. Notice that changing the background color using PALETTE 0, x will also change the color of the pointer in screen modes that support PALETTE. Also, changing color 15 will also change color of the pointer fill color. DO NOT allow the pointer to be visible while changes are taking place on the screen. For example, before PRINT or LINE or any other video output command, make sure there is a mousehide statement somewhere or the pointer will be visible during the video output. The reason this is bad is because if the pointer, say, is placed over the area where changes on the screen are taking place, the old image will be stored, and when the pointer is moved, the old image will be placed over the new one in the area that the pointer was, causing a very weird and undesirable effect. Also note that PRINT statements may cause the pointer to scroll upward with the screen if not turned off before the PRINT. Be careful, however, not to use too many mousehide statements, because if you execute 2 or more mousehide's in a row without a mouseshow, the pointer may be disabled. You will also be calling the sub mousestatus alot. Rather that typing out mousestatus xmouse%, ymouse%, lb%, rb% every time you use the sub, it would be better to insert DIM SHARED xmouse%, ymouse%, lb%, rb% at the beginning of the program so all you have to type for mousestatus is mousestatus and the rest is done already. Also, however, make sure that you delete the "xmouse%, ymouse%, lb%, rb%" part from the DECLARE SUB mousestatus () and SUB mousestatus () lines or you will get an "Argument-count mismatch" error. Now that you understand the basics of putting the subs to work, we'll go through the actual procedure for making your interface. First, make sure no other parts of the program have executed a mouseshow; or if they have, use a mousehide. This is tracked easily by standardizing the mousehide-mouseshow's within parts of the program: that is, you may want to start each sub with a mousehide and end each one with a mouseshow, however you want to keep things orderly. The purpose for that is to keep you from executing too many mouseshows or mousehides and to make sure the pointer is the way you think it is. Next, after the interface is drawn, you need a loop that will keep going till the user clicks. Just use this very simple code: DO UNTIL lb% = -1 mousestatus LOOP What that does is it keeps checking mousestatus until lb% (left-button%) = -1, which indicates the left button is depressed. Or, you may also use rb% if your program will use the right button for anything. Once that is done, you need to do checking to see where the click occurred: IF xmouse% > a AND xmouse% < b AND ymouse% > c AND ymouse% < d THEN ... What that does is check xmouse% (horizontal location of the pointer) and ymouse% (vertical location of the pointer) to see if it fits inside the "hot spot" (the area that produces the effect when clicked), which is usually a box defined by a, b, c, and d, in this example. Lastly, before the program continues, it should wait for the user to let up on the mouse button. DO UNTIL lb% = 0 mousestatus LOOP That does the same thing as the loop above, just backwards- that is, it waits till lb% = 0, indicating the left button is up. You should probably insert this later loop immediately below the first one so that the next screen doesn't appear till the user releases the mouse button and so they can move the pointer off the "hot spot" and release the button if they change their mind about clicking there. Well, that was alot to take in for someone who's never programmed the mouse before, so an example would be appropriate. Here you go: DIM SHARED xmouse%, ymouse%, lb%, rb% 'make those variables global SCREEN 12 'required for obvious reasons LINE (5, 7) - (20, 22), 15, B 'draws a box from 5,7 to 20,22 wait: DO UNTIL lb% = -1 ' \\ mousestatus ' >> wait for user to press mouse button LOOP ' // DO UNTIL lb% = 0 ' \\ mousestatus ' >> wait for user to release the button LOOP ' // x% = xmouse% : y% = ymouse% 'shorten variable names for convenience IF x% >= 5 AND x% <= 20 AND y% >= 7 AND y% <= 22 THEN END ELSE GOTO wait 'checks if the user clicked inside the box. if so, the program terminates. if 'not, it goes back to the label "wait:" and waits for the user to click again One final note: if you're using QuickBasic 4.5 and some earlier versions of the compiler, you will notice that you get an error on the CALL ABSOLUTE command. To correct this, start QucikBasic with the /l option: "qb /l". This does not apply for QBasic 1.0 or 1.1 that come with DOS. Well, that's not so hard. Remember, I'm available if you have questions. Next month we will discuss creation of custom pointers, so you don't have to use the pointer built-in. This is very useful for using screen 13 but with a smaller pointer that looks better. It will also allow you to make transparent pointers, colored pointers, and graphic pointers, but you have to wait until next month for that. David Tordrup _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- -_- EOF