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 Ikheimo (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    |
           /   |    |    |  \
          /     \   |   /    \
                 \  |  /
             /    \ | /   \
            /      \-/     \
                    |
                    |
