From: Erwin / TBR To: All who care to read this!!! Peole on the PC scene (coders mainly) have been complaining that no one has released a working source code for mod player for Sound Blaster. Well, you don't have to complain any longer! There are only two rules about using this player in you productions: ************************************************************************ *1) If you use this player, greet Black Rain in you future productions!* ************************************************************************ *2) Don't take credit for coding the sound system for yourself!* **************************************************************** I guess some kind of disclaimer would look really cool here. OK, here we go: DISCLAIMER: If this program fucks up your computer, or your your mind for that matter, that's just tough. You're using this player at your own risk! By reading this disclaimer you agree not to sue (or kick) my but off if something nasty happens. If you don't trust my abilities as a coder, don't use this program, OK?!!. The player routine is pretty optimized, though someone maybe able to optimize it some more. (Feel free to do so. Just remember to give me some credit for the player - I've spent some 7 months to develop this sound system. And please let me know about your optimizations, I'd like to know how you've done them.) This player is some 40-50% faster than GoldPlay (by the Codeblasters, greetings to them), faster than IPlay (10-15%?), and about as fast as Dual module Player (by Otto Chrons, greetings). These results were gotten by using Norton's SI.EXE. This player doesn't: - Support SB PRO (If some one knows how to play left & right channels on SB PRO, please let me know. I haven't been able to figure it out by myself, and I don't have any docs about SB PRO programming!) - Detect SBlaster's IRQ or DMA, you have to set them manually, the base port is detected. (Sorry about the in convenience) - Read mod files (You'll have to convert them into tbm format by using mod2tbm.exe, included in this zip (c source code also included)) - Have a cool name like DMP or STMIK or GoldPlay. 8-) - Like GUS with SBOS or MEGA-EM (it does work, but the sound quality isn't too good) - Like some mods. Don't ask me why.(but it plays about 90% of mods all right) This player does: - support the following effects / commands 00h none/arpeggio 01h portamento up 02h portamento down 03h portamento to tone 04h vibrato (I'm not sure if this works like it is supposed... At least it sounds pretty ok) 08h info byte is stored into geffect8 variable (so it can be used for synchronizing or something...) 09h sample offset 0ah volume slide 0bh position jump 0ch set volume 0dh break pattern 0eh e - commands 0e1- fine portamento up 0e2- fine portamento down 0e9- retrig note 0ea- fine volume slide up 0eb- fine volume slide down 0ec- cut note 0ed- delay note 0fh set tempo - supports also extented tempos - take about 31 Kbs of memory - provide an interface to make your demo or intro run at the same speed in all computers (see below) - eat up about 10% of CPU time (at least on my 40Mhz 386) - work ok with your source as long as you don't use dossegs and stuff (See the example, who needs dossegs, anyway) - compile with TASM 2.01 and link with Tlink 3.0 (or newer) - require 386sx or better, and naturally a SoundBlaster sound card -------------------------------------------------------------------------------- * People, remember! It is not lame not to have your own mod player! (Hardly * * any amiga groups have a player of their own, and they still are cool.) * * Not being able and not knowing how to do something are totally two different* * things. Most of coders, I think, would be able to write their own players if * * they could get documentation they needed. (SB & other hardware docs etc.) * * My opinion is that a person who says that groups that can't do their own * * player should get out of the scene is just being cocky, because 9 times out * * of 10 he doesn't know a diddly about coding a sound system! Because he hasn't* * coded a player (and most usually anything else either) in his life, he just * * happens to be in a group that has a player of its own! * -------------------------------------------------------------------------------- Everything you've always wanted to know about codind a mod player but never bothered to ask! Remark: This is just my way to code a player, you may have found a better, faster and a lot cooler way to do it. If so take a cocky look on your face and say: "Phew, another lamer who thinks he can do something, but he really can't because I'm so much better than him. (And everybody else, for that matter!)" (I'm gonna briefly go over some of the difficulties I had coding the player (Trust me, there were quite a few of them...) and how I solved them.) 1. How to make SoundBlaster to play stuff? First step in developing a player is to learn something about programming the SB. I was lucky to have a good doc about programming SB, SBDEVKIT.DOC. (included in zip) It shows with examples (in section 8) how to init SB etc. It also tells a great deal about programming the SB. Unfortunately, it covers only programming regular SB, it doesn't tell anything about SB PRO. 2. DMA - Intel's gift to coders. (NOT!) To get your SB card to understand that it should play something isn't hard (at first it is...) but to make DMA controller to agree to cooperate is a major pain in the but! SBDEVKIT.DOC briefly describes, how to program dma controller, but the examples weren't too clear... Later (after learning dma stuff the hard way) I've founds several docs about programming dma... (You know that's the way it usually is!) One should be included in zip, too. 3. Amiga mods The stucture of an Amiga ProTracker mod is quite simple, the only thing that gives you hard time with them is the way the Motorola processors handle words in memory. When a 80X86 stores a (16-bit) word into memory the low-order byte is stored before the high-order word (this system is called low-endian method, I think...), but Amigas store the high-order byte before the low-order byte. Heres's an example: When a 80X86 executes a followin instuction mov word ptr ds:[0100h],1234h it stores a word 1234h in offset 100h relative to ds. The low-order byte (34h) is stored in loacation 100h, and the high-order word in 101h Amigas do this differently. Because of this you have to switch the high-order and low-order bytes when reading them fron an Amiga mod. See, file modforma.txt to learn more about ProTracker file format. You must also convert Amiga samples from signed 8-bit format to unsigned 8-bit format. This is either done by adding 128 (80h) to all sample bytes, or by simply ORring sample byte with 128 (80h). 4. How to play samples at different pitches? First a few basic things about sounds. Sounds are (like most of us have learned at school) waves that propagate (isn't that just a cool word) in air or in some other medium. The higher the sound is the shorter its wave lenght will be and vice versa. How loud the voice is depends on its amplitude. This 'picture' may make this easier to understand: 128| **** **** \ (Nice graphics, huh?) 72 | ** ** ** ** | This is 48 | * * * * | amplitude 0 |*----------*----------*----------*--/ -48| * * * -72| ** ** ** -128| **** **** <-This is wavelenght--> After sampling this 'wave' would look something like this: 0,48,96,96,128,128,128,128,96,96,48,0,-48,-96,-128,-128,-128,-128 etc. If you play every byte of the sample it'll sound as high (or low) as the original sound. But if you output only every other byte the sample will be played exactly one octave above the original sound. And if you play all bytes twice (0,0,48,48,96,96,96,96,etc.) the sound will be one octave below the original sound. Conclution: The pitch of the sample can be changed by changing the speed at which the sample is being played! It wasn't that hard, was it? Volume can be altered by multiplying the byte that is being played by a certain value. (Or you can use a look-up table to improve the speed of the player... Only problem with the table is that it's going to be 16 Kb long!(256 different sample values * 64 different volume values). For example if you want to play a sample byte with a value 128 at volume of 32 (half of the full volume (=64)) you'd have to multiply it by 0.5. (Actually, it'd be wiser to multiply it by 32 and then divide by 64 since floating point math is rather complicated (=slow) on 80X86 CPUs. If this sounds confusing, don't worry. Look at the source instead, it'll be easier to understand (I think...) 5. How do you play samples on 4 channels at the same time? Mixing of channels is rather simple, you just add all 4 sample bytes together and divide them by 4 (= shr by 2). (Assuming that you're playing 4 channel mod.) Example: Samples byte on channel 1 2 3 4 128 96 230 18 To mix them, just add them together: 128+96+230+18=472 and divide the result by 4: 472/4=118. 118 is the value to be outputted on SB. Mixing 8 or any other number of channels is done in the same way, you just add up the sample bytes and then divide the result by number of channels. The problem with this method is that is decreases samples' quality, but I haven't managed to find any other method to mix samples. There may be better way to do the mixing, though. (If you've figured out a better way, let me know!) 6. How to make the player faster? This is the biggest problem I've had. The player isn't ever fast enough! About 3 months ago I thought that I couldn't optimize the player any more. After that I've managed to get almost 40% off of the CPU time it uses. And I'm not saying that this is the final and fastest version, either. You know, there are always faster and better ways to code! First of all, if you want to code a high performance SB player, you are going to have to use DMA! Direct DAC is slow because, every time you want to play a byte you have to send play raw command and wait for the SB to be ready to accept the data byte. (See sbdevkit.doc to learn more about different ways to output data) Use a look-up table for volume data. MULs are very time consuming commands. One (word*word) MUL can take up to about 30 clock cycles, while accessing a look-up table takes only 4 clocks. Besides using a table is a lot simplier in this case. (Mul (word*word) destroys dx register!) Hold as much data as possible in the registers. I'm sure everybody has read a lot of articles about register optimizations in various disk mags, so we really don't need to discus about it here. Use your variables as immedeate values. (Say what!?!) Here's an example: my_variable dw 0 mov ax,my_variable ; this will take 4 clks db 0b8h ;performs the same action, but takes my_variable dw 0 ;only 2 clks 0b8h is an opcode for mov ax,immedeate word This will speed up your program considerably. (To get opcodes (like 0b8h) use /l option in tasm, For example tasm /l myprog.asm and view the .lst file) A line : mov ax,1234h in asm file will look like this in list file: (line #)(address)(command) (immedeate) 2682 59F2 B8 1234 mov ax,1234h (line # and addres may vary) You can use this same method to change jump addresses. The only problem with using this technique with jumps is that you'll have to change the addresses every time you change the code between a jump command and its destination. I found that loading samples backward into memory speeds up the player a lot. Instead of comparing sample's offset with its lenght every time the offset has been increased, the end of the sample can simply be determined by the state of carry flag (the offset has to be decresed instead of increasing!). Example: Let's assume that a sample's lenght is 1200h Normally a sample would be loaded in the memory so that the first byte would be first (at offset 0) and the last byte last (offset 11ffh) Sample's current sample first byte byte | | <==========*========================> | | | offset 0 sample pointer-> offset 11ffh To play a sample you'll have to increase sample pointer and then compare it with sample's lenght.(To see if the sample has ended or the loop began) (a cmp command takes time 2 clocks * 4 channel = 8 clks for every mixed byte. If your sampling rate is 23000 you'll save 184000 clk cycles every second. [That's about 0.5% if 40 Mhz CPU's processing time!]) When the sample is loaded backwards (last byte first) (actually, mod2tbm inverts the sample while converting it) you'll only have to decrease the pointer and see wheter carry flag is clear (if not the sample just ended!). Sample's current sample last byte byte | | <==========*========================> | | | offset 0 <- sample pointer offset 11ffh (The loops work ok, the mod converter discards sample that is after the end of loop (loop_start+loop_end)) See the source code for more tricks I've used. I haven't explained the whole listing word by word, since I didn't have neither time nor desire for it, but I've added comments where I thought they were needed. If something seems unclear to you, write me, and I'll explain it to you. ************************ *About using the player* ************************ I'll just briefly go over the functions and variables that are available for your program. LOAD_TBM Allocates memory for tbr module and loads it. usage: DS:DX = pointer to file name, terminated with zero. INIT_PLAY Calculates notes frequencies, initalizes SB and performs several other things. SBIRQ, SBDMA and RATE must be set before calling init_play. Init_play must be called before calling start_tbm START_TBM Hooks interupts needed by player and starts playing module. You can use dos interrupts and stuff (do disk reads/writes), it shouldn't bother the player too much. (As long as your HD and SB don't use same DMA channel...) STOP_TBM Stops playing module, unhooks interrupts and frees memory used by module. SET_MAINVOL Sets mainvolume. bx=mainvolume (0-63) GBAR1, GBAR2, GBAR3, GBAR4 (word) Popbar values for channels 1, 2, 3 and 4. These are self-decrementing. SBIRQ Blasters irq (2,5,7,10) (word) SBDMA Blasters dma (0,1,2,3) (word) RATE Mixing rate: RATE mixing speed (Hz) 0 7576 1 11364 2 15152 3 18940 4 22728 GEFFECT8 (byte) Player stores info byte of effect command 8 into this variable. For example when player finds following note info C-1018AB, 0abh is stored into geffect8. SYNC (word) If this variable is set to 1 player will call a far proc thats location is sbrutseg:sbrutoff about 50 times a second. (When bpm=125) Syncronizing routine must preserve ds, fs and gs, other regs are preserved by player. If sync = 0 player won't call it. SBRUTOFF (word) synchorinizing routine's offset SBRUTSEG (word) synchorinizing routine's segment POS (word) songposition If you change pos, player 'jumps' to this position next time it's done playing a pattern. ERROR (byte) Flags different errors If error = 3 after calling load_tbm no module is found If error = 1 after calling init_tbm no SoundBlaster is found! See example.asm for more information! If you have questions about the player or coding in general, write to me. My address is: Niko Haatainen (Erwin / TBR) Tuomikuja 1 A 6 70420 KUOPIO FINLAND Voice : +358-71-3643983 (Don't call before noon! I need to get some sleep, you know...) If you want to swap stuff contact Linel Z. His address is: Marko Ik„heimo (Linel Z / TBR) Keskikaari 42 B 5 70420 KUOPIO FINLAND Voice : +358-71-3643978 E-mail: mikaheim@dakota.pspt.fi OR if you just wanna have fun call: Nether World BBS (WHQ) The Lycaeum BBS (Dist. Site) Open : 24H Open : 24H Number: +358-71-2619957 Number: +41-41-762989 SysOp : LifeGuard / TBR SysOp : Chicken / Pentagon | (Nicest peaces of ANSI art - | collect the whole series!) \ /-\ / \ / | \ / \ / | \ / \ / | \ / | | | __ | A | -- | | | | __ | | | | -- | | | | | | | | | Y | / | | | \ / \ | / \ \ | / / \ | / \ / \-/ \ | |