home *** CD-ROM | disk | FTP | other *** search
/ The CDPD Public Domain Collection for CDTV 4 / CDPD_IV.bin / amfm / amfm11 / techcorner / 8chan.txt.pp / 8chan.txt
Text File  |  1994-06-20  |  10KB  |  315 lines

  1. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  2.  
  3.                  AM/FM TECHCORNER
  4.  
  5.             The magic of "Octa"-sound
  6.  
  7.             Written by Teijo Kinnunen.
  8.  
  9. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  10.  
  11. To all TechCorner readers:
  12.  
  13. Up to now, I have received not a single letter from you. Therefore, I would
  14. really like to hear your comments about TechCorner. I would also be glad to
  15. hear your suggestions about what I should cover in the future TechCorners
  16. (I'll soon run out of ideas!). You're also welcome to send any questions
  17. concerning audio/music programming, which I'll attempt to answer in the
  18. following TechCorner.
  19.  
  20. My address is:
  21.  
  22.     Teijo Kinnunen
  23.     Oksantie 19
  24.     SF-86300  OULAINEN
  25.     FINLAND
  26.  
  27. (I'm sorry, but I don't probably have time to reply individually.)
  28.  
  29. or FidoNet: 2:228/402
  30.  
  31. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  32. And back to the point...
  33.  
  34. As you most likely know, there are music programs that can "split" the audio
  35. channels, resulting max. eight independent channels. Such programs are e.g.
  36. Oktalyzer, StarTrekker and OctaMED, OctaMED being the best (advertisement!!).
  37.  
  38. All these programs use a similar method to produce the sound. They also have
  39. similar restrictions, for example:
  40.  
  41.     * heavy CPU load
  42.     * volume control not possible on channel-by-channel basis
  43.     * rough looping resolution (a workaround could be possible, but quite
  44.       complex)
  45.     * decreased sound quality
  46.  
  47. Other means to produce eight-channel sound could be possible, but I will
  48. describe the method all of the above programs use.
  49.  
  50. The "magic" method is simply to mix two samples into one which is then played
  51. out. It has to happen in real time, though. The critical point is that the
  52. samples don't usually have the same playback rate. The mixing routine has to
  53. remove or double bytes in order to achieve the correct playback frequency,
  54. this leads to degradation in sound quality.
  55.  
  56. OctaMED uses two buffers per channel. The samples are mixed into the buffer
  57. which is then played at constant frequency (period). When the other buffer
  58. is being played, the other one is being filled. When the first buffer has
  59. been played, the other buffer will start playing. This technique is called
  60. double-buffering.
  61.  
  62. As already mentioned, the output rate is fixed. Naturally it has to be slow
  63. enough, so that the other buffer can be filled before the first has been
  64. played. On 7 MHz 68000 machines a good output period is approx. H-2/C-3
  65. (OctaMED uses period 227 (non-HQ)). If not all four channels are splitted,
  66. or a fast processor is being used, there's more time for filling the buffers,
  67. and a higher output period can be used (in HQ-mode, OctaMED uses the highest
  68. possible frequency, 124). The higher the output period, the better the sound
  69. quality.
  70.  
  71. The sample buffers are played out using normal DMA output. However, the sample
  72. pointers (AUDxDAT) have to be constantly swapped. The only correct method to
  73. do this is via audio interrupts. In 5 - 8 -channel modes, OctaMED also uses
  74. this interrupt for timing the music, this is very handy. E.g. StarTrekker,
  75. as far as I know, uses VBlank timing for music and "assumes" that a certain
  76. number of samples are played during one frame, which is very bad.
  77.  
  78. It's wise to keep all channels in exactly the same phase with each other.
  79. When the DMA is started (done only once), you have to set all audio DMA bits
  80. with the same MOVE-instruction. As a result, we can assume that the audio
  81. interrupts will occur _exactly_ at the same time. So, you only need to have
  82. one audio interrupt that handles all channels at the same time.
  83.  
  84. To clarify everything, let's have a look at a real 8-channel routine. It's
  85. a very stripped-down version of the OctaMED routine. To simplify things, it
  86. only handles one splitted channel, and doesn't handle repeat.
  87.  
  88. Below is the macro that fetches the samples, does the actual mixing, and
  89. pushes the result into the playback buffer:
  90.  
  91. ; This code does the magic 8 channel thing (mixing).
  92. MAGIC_8TRK    MACRO
  93.         swap    d6
  94.         swap    d7
  95.         move.b    0(a3,d6.w),d0
  96.         add.b    0(a4,d7.w),d0
  97.         move.b    d0,(a1)+
  98.         swap    d6
  99.         swap    d7
  100.         add.l    d1,d6
  101.         add.l    d2,d7
  102.         ENDM
  103.  
  104. This is the shortest way to do it (if someone can find a shorter/faster way,
  105. *PLEASE* let me know ;-). This macro is repeated many times, once for each
  106. byte of the playback buffer (on OctaMED max. 1600 times/interrupt), so it
  107. had better be fast.
  108.  
  109. Let's examine this macro more closely. First we'll have a look at the register
  110. usage.
  111.  
  112. A3 and A4 are pointers to the _beginning_ of the samples to mix. They remain
  113. constant throughout the mixing. Index registers D6.w and D7.w will be used to
  114. get the actual offset. The samples will be mixed in D0, and the resulting
  115. sample will be pushed into the buffer (pointed by A1). Note that the sample
  116. data must be halved beforehand (converted into 7-bit dynamic range by shifting
  117. sample bytes right one bit position), this saves us from using an extra
  118. ASR-instruction.
  119.  
  120. As mentioned above, D6 and D7 are used to index the sample data, they are
  121. offsets from the beginning of the sample. However, a resolution of a byte is
  122. far too rough. Therefore we need to have a 16-bit fraction:
  123.  
  124.     SSSSSSSS SSSSSSSS FFFFFFFF FFFFFFFF
  125.  
  126. The upper word 'S' is the actual byte offset from the beginning of the sample,
  127. and 'F' is the fraction part. When the value is updated (to point to the next
  128. sample to mix), it's handled as a 32-bit value. (D1 and D2 contain the 32-bit
  129. numbers to add each time, they are constant values based on the current playback
  130. periods of the channels).
  131.  
  132. However, when the sample value must be fetched, the fractions must be forgotten.
  133. A single SWAP instruction will do fine. As a result
  134.  
  135.     FFFFFFFF FFFFFFFF SSSSSSSS SSSSSSSS
  136.  
  137. the lower word can be easily used for indexing. Another SWAP, and everything is
  138. back again for a new cycle.
  139.  
  140. This was the most critical part of 8-channel output, but let's also look at the
  141. interrupt code.
  142.  
  143. _IntHandler8:    movem.l    d2/d5-d7/a2-a5,-(sp)
  144.  
  145. DB is a pointer to the data area, we can use A6-relative data addressing.
  146.  
  147.         lea    DB,a6
  148. ; ================ 8 channel handling (buffer swap) ======
  149.         not.b    whichbuff-DB(a6)    ;swap buffer
  150.         bne.s    usebuff1
  151.  
  152. 'whichbuff' tells us which buffer is currently in use. not.b toggles it and
  153. we change the buffer pointers accordingly. A1 (int_Data) points to the buffers
  154. (each 200 bytes long). A0 points to $DFF000 (custom chips)
  155.  
  156.         move.l    a1,$a0(a0)        ;ac_data = buffer 1 (offs = 0)
  157.         move.w    #100,$a4(a0)        ;ac_len = 200 bytes
  158.         bra.s    buffset
  159. usebuff1    lea    200(a1),a1        ;ac_data = buffer 2 (offs = 200)
  160.         move.l    a1,$a0(a0)
  161.         move.w    #100,$a4(a0)
  162.  
  163. Audio interrupt request bit MUST be cleared (very important).
  164.  
  165. buffset        move.w    #1<<7,$9c(a0)
  166.  
  167. Set the volume to maximum.
  168.  
  169.         move.w    #64,$a8(a0)
  170. ; ============== fill buffers ============
  171.  
  172. To make things easier, I've set up some pseudo-audio-hardware registers
  173. (track0hw, track4hw). Instead of using ac_len, however, ac_end points to the
  174. end of the sample.
  175.  
  176. startfillb    lea    track0hw-DB(a6),a2
  177. ;calculate channel A period
  178.  
  179. Some wizard-stuff again... It will calculate the fraction value to add each
  180. cycle. The actual formula is:
  181.  
  182.                   227 * 65536    14876672
  183.     fracval = ----------- = ----------
  184.                     period        period
  185.  
  186. But as the result could be > 65535 and DIVU doesn't handle that big quotients,
  187. it will be calculated as
  188.  
  189.                    3719168
  190.     fracval = --------- * 4
  191.                     period
  192.  
  193. ac_per of 0 is considered silence...
  194. D1 will contain fracval and D2 will contain fracval / 4.
  195.  
  196.         move.l    #3719168,d7    ;227 * 16384
  197.         move.w    ac_per(a2),d6
  198.         beq.s    setpzero0
  199.         move.l    d7,d2
  200.         divu     d6,d2
  201.         moveq    #0,d1
  202.         move.w    d2,d1
  203.         add.l    d1,d1
  204.         add.l    d1,d1
  205.  
  206. Then we fetch the required addresses. A5 is the sample end pointer, and A3
  207. (after checking) is the sample start pointer. Note: A3 is the _current_
  208. start pointer, it will change after each fill.
  209.  
  210. ;get channel A addresses
  211.         move.l    ac_end(a2),a5
  212.         move.l    (a2),d0
  213.         beq.s    setpzero0
  214. chA_dfnd    move.l    d0,a3    ;a3 = start address, a5 = end address
  215.  
  216. The following operation will check, if the sample would run past the end
  217. address during this fill. If so, turn it off.
  218.  
  219. ;calc bytes before end
  220.         mulu    #200<<3,d2
  221.         clr.w    d2
  222.         swap    d2
  223. ; d2 = # of bytes/fill
  224.         add.l    a3,d2    ;d2 = end position after this fill
  225.         sub.l    a5,d2    ;subtract sample end
  226.         bmi.s    norestart0
  227.         clr.l    (a2)
  228. setpzero0    lea    zerodata-DB(a6),a3
  229.         moveq    #0,d1
  230. norestart0
  231.  
  232. Now repeat everything for the other channel....
  233.  
  234. ;channel B period
  235.         move.w    SIZE4TRKHW+ac_per(a2),d6
  236.         beq.s    setpzero0b
  237.         divu    d6,d7
  238.         moveq    #0,d2
  239.         move.w    d7,d2
  240.         add.l    d2,d2
  241.         add.l    d2,d2
  242. ;channel B addresses
  243.         move.l    SIZE4TRKHW+ac_end(a2),a5
  244.         move.l    SIZE4TRKHW(a2),d0
  245.         beq.s    setpzero0b
  246.         move.l    d0,a4
  247.         mulu    #200<<3,d7
  248.         clr.w    d7
  249.         swap    d7
  250.         add.l    a4,d7
  251.         sub.l    a5,d7
  252.         bmi.s    norestart0b
  253.         clr.l    SIZE4TRKHW(a2)
  254. setpzero0b    lea    zerodata-DB(a6),a4
  255.         moveq    #0,d2
  256. norestart0b
  257.  
  258. Finally, it's time to mix. It'll be done 200 times. To save time, DBF will occur
  259. only after every 20th mix.
  260.  
  261.         moveq    #0,d6    ;clear index regs
  262.         moveq    #0,d7
  263.         moveq    #9,d5    ;DBF counter
  264. do8trkmagic
  265.         MAGIC_8TRK    ;20 times..
  266.         MAGIC_8TRK
  267.         MAGIC_8TRK
  268.         MAGIC_8TRK
  269.         MAGIC_8TRK
  270.         MAGIC_8TRK
  271.         MAGIC_8TRK
  272.         MAGIC_8TRK
  273.         MAGIC_8TRK
  274.         MAGIC_8TRK
  275.         MAGIC_8TRK
  276.         MAGIC_8TRK
  277.         MAGIC_8TRK
  278.         MAGIC_8TRK
  279.         MAGIC_8TRK
  280.         MAGIC_8TRK
  281.         MAGIC_8TRK
  282.         MAGIC_8TRK
  283.         MAGIC_8TRK
  284.         MAGIC_8TRK
  285.  
  286.         dbf    d5,do8trkmagic    ;do until cnt zero
  287.  
  288. Then add the advanced index sample offsets to the sample pointers (the fraction
  289. part cleared first).
  290.  
  291. end8trkmagic    clr.w    d6
  292.         clr.w    d7
  293.         swap    d6
  294.         swap    d7
  295.         add.l    d6,(a2)
  296.         add.l    d7,SIZE4TRKHW(a2)
  297.  
  298. And exit the interrupt...
  299.  
  300.         movem.l    (sp)+,d2/d5-d7/a2-a5
  301.         rts
  302.  
  303.  
  304. I have provided you with an example program that plays two samples through a
  305. single channel (for two seconds). Its arguments are:
  306.  
  307.     example <sample1> <period1> <sample2> <period2>
  308.  
  309. (where periods are usually between 200 - 900)
  310.  
  311. Have a look at the sources as well. The program consist of an interface & loader
  312. part (written in C), and the player & audio part (in assembler).
  313.  
  314. As usual, feel free to use the code in your own programs!
  315.