home *** CD-ROM | disk | FTP | other *** search
/ CD Actual 14 / CDACTUAL.iso / cdactual / demobin / share / program / Basic / QBTWEAKR.ZIP / TWEAKR.ASM < prev    next >
Encoding:
Assembly Source File  |  1992-07-10  |  14.5 KB  |  357 lines

  1. ;   QBTWEAKR.ASM By Rich Geldreich 1992
  2. ;   For QuickBASIC 4.5 or PDS 7.1
  3. ;
  4. ;   Plays  simple,   8-bit digitized sounds over the PC-Speaker.   No
  5. ;   bells.   No whistles.   Have  fun.    This  code  is in the public
  6. ;   domain.  It's yours now!  All I ask is that you give credit where
  7. ;   credit is due.   Assembled with TASM v2.00 - should also assemble
  8. ;   with  MASM 4.00 too(that's all I had to test it out with, sorry)...
  9. ;
  10. ;   Notes: My play interrupt does not call the original interrupt 08h
  11. ;   routine every 18.206 times a second,  therefore,  the BIOS  clock
  12. ;   will  stop  dead while you're playing sounds.   It doesn't matter
  13. ;   what you set the main interrupt rate to; samples will always play
  14. ;   at the same  frequency.    The  main  difference  is quality;  an
  15. ;   interrupt  rate  of  18,000hz  sounds  much,   much  better  than
  16. ;   10,000hz,  of course.   Many functions could  be  added  to  this
  17. ;   program,  such as volume,  echo,  2 or more channels,  etc.   The
  18. ;   program  as-is  only  plays signed samples,  if you play a sample
  19. ;   which seems  distorted,   try  unsigning  it(or reassembling this
  20. ;   program, see the interrupt procedure).   That's all for now.   If
  21. ;   you find any bugs,  tell me about them and I'll do my best to fix
  22. ;   'em.
  23. ;
  24. ;   I've also included the small QB program that made the  pc-speaker
  25. ;   translation  table.    (This  program  can be easily modified for
  26. ;   other devices,  such as an 8-bit  DAC on a printer port,  or even
  27. ;   the Adlib.)
  28. ;
  29. ;Functions provided:
  30. ;
  31. ;TweakOn freq%
  32. ; Captures  interrupt  08h,   the  18.206hz  timer  click  interrupt.
  33. ; Reprograms the 8253 timer chip  to  whatever  frequency  passed  by
  34. ; freq%  (in  hertz).    You'll  should hear a little click from your
  35. ; speaker when you call  this  function.    (Valid range for freq% is
  36. ; 1-40000.)
  37. ;
  38. ;TweakOff
  39. ;
  40. ; Frees interrupt 08h,  and reprograms the timer back to what it used
  41. ; to be.   You must do this before your program ends!  (Actually, you
  42. ; don't because QB and PDS restore the vector to interrupt 08h.  It's
  43. ; best to be safe, though. You should always call this function before
  44. ; you start editing your program in the environment, just in case...)
  45. ;
  46. ;PlaySound Offset%, Segment%, Length%, Frequency%
  47. ; Plays a sample  at  a  "virtual"  frequency  over  the  pc-speaker.
  48. ; Offset%,   Segment%,  & Length% specify the location of a sample in
  49. ; memory.   Frequency%  specifies  the  frequency(in  hertz) that the
  50. ; sample will be played at.   (Note:  if you  set  interrupt  08h  to
  51. ; 10,000hz,   and  you play a sample at 20,000hz,  exactly 1/2 of the
  52. ; samples will be skipped,  so it will sound like 20khz.   All of the
  53. ; frequencies above the interrupt  rate  will  be adjusted similar to
  54. ; this...)
  55. ;
  56. ; TweakStatus () Function
  57. ;  Returns -1 if sample is playing. Returns 0 if a sample is not.
  58. ;
  59. ; Stuff you can't do while using this driver:
  60. ;  
  61. ;  I've tested this program in the environment,  and it seems to work
  62. ;  fine,  although I still don't recommend that you let the interrupt
  63. ;  go while editing your program!!    For instance,  if you edit your
  64. ;  program and make it smaller,  while my interrupt is going,  QB may
  65. ;  try to move memory around and my  play  interrupt  may  crash  and
  66. ;  burn.    One  thing  though:   QB and PDS disable interrupts while
  67. ;  you're in the editor.  This causes distortion in the sound because
  68. ;  my interrupt doesn't get all of  the time it deserves.   You can't
  69. ;  use the SOUND, PLAY, or SLEEP commands, because they use interrupt
  70. ;  08h for timing.   You can't SHELL to  dos,   because  QB  restores
  71. ;  interrupt 08h before going under.   In other words:  Don't use any
  72. ;  commands that leach off interrupt 08h!  I don't recommend that you
  73. ;  access any mass-storage devices  while  playing samples, doing  so 
  74. ;  may cause distortion.
  75. ;
  76. ; All of these routines were derived from my QB .MOD player,  which I
  77. ; will (hopefully) be releasing soon.
  78. ; (Another last minute  note: Be  carefull using this  program in the 
  79. ; environment,  if you  press  ctrl+break and  the interrupt is still 
  80. ; going,  go to the immediate window and type "TweakOff".  Also  note  
  81. ; that the interrupt routine can still be going even though  it isn't
  82. ; producing any sound! Use caution!)
  83. ;
  84. ; And finally, here's what I need:  
  85. ;  If anybody has got an  assembly  routine that emulates QB's "PLAY"
  86. ;  command,  please let me know if you're willing to share it.    I'm
  87. ;  making  an ANSI emulator in assembly,  and it would be great if it
  88. ;  supported ANSI music.   I'd make it myself,  but I just don't have
  89. ;  the time these days...  Thanks!
  90.   
  91.  
  92. public TweakOn, TweakOff, PlaySound, TweakStatus
  93.  
  94. cseg segment para public 'CODE'
  95.         assume cs:cseg, es:nothing, ds:nothing
  96.  
  97. ;Notice this is the first byte of the driver. When you press SHIFT+F5 to
  98. ;to run in the environment, I believe QB copies the original .QLB file
  99. ;overtop of the old one(or something similar to that). If this does actually
  100. ;happen, and my play interrupt is in progress, all hell could break loose.
  101. ;Putting this flag first should protect against that (?)...
  102. EndFlag         db 0
  103. even
  104. ;Enables the driver.
  105. TweakOn proc far
  106.         Push    bp
  107.         Mov     bp, sp
  108.         Push    ds
  109.         Push    si                      ; si & di probably don't need to be
  110.         Push    di                      ; preserved for QB, but what the hell!
  111.  
  112.         Mov     ax, cs
  113.         Mov     ds, ax
  114.  
  115.         Mov     ax, 3508h               ; Get interrupt 08h vector in es:bx
  116.         Int     021h
  117.  
  118.         Mov     dx, offset NewInterrupt
  119.  
  120.         Cmp     bx, dx                  ; Has it already been changed?
  121.         Jne     NotInstalled            ; Nope
  122.         Mov     ax, cs                  ; Check the segment
  123.         Mov     cx, es
  124.         Cmp     ax, cx                  ; Compare the segments
  125.         Je      ExitTweakOn             ; If same then exit
  126.         Even
  127. NotInstalled:
  128.         Mov     cx, [ss:bp+06]          ; Get interrupt rate
  129.         Cmp     cx, 40000               ; < 1 or > 40000 ?
  130.         Ja      ExitTweakOn
  131.         And     cx, cx
  132.         Jz      ExitTweakOn
  133.  
  134.         Mov     [ds:EndFlag], 0         ; Make sure interrupt doesn't play
  135.         Mov     [ds:IFreq], cx          ; Save interrupt frequency for later
  136.         Mov     [ds:OldOfs], bx         ; Save original interrupt 08h vector
  137.         Mov     [ds:OldSeg], es
  138.  
  139.         cli                             ; Disable interrupts
  140.  
  141.         Mov     ax, 02508h              ; Change interrupt 08h vector to my
  142.         Int     021h                    ; routine
  143.  
  144.         Mov     al, 00110110b           ; Reprogram the 8253 timer chip
  145.         Out     043h, al
  146.  
  147.         ;Masm 4.00 didn't like this:
  148.         ;Mov     dx, 1193180 shr 16      
  149.         ;Mov     ax, 1193180 and 0FFFFh  
  150.  
  151.         Mov     dx, 012h                 ; Find the correct delay value:
  152.         Mov     ax, 034DCh               ; Delay = 1193180/Frequency
  153.         Div     cx
  154.  
  155.         Out     040h, al                ; Send it
  156.         Mov     al, ah
  157.         Out     040h, al
  158.  
  159.         In      al, 061h                ; Turn on PC-Speaker
  160.         Or      al, 3
  161.         Out     061h, al
  162.         Mov     al, 128+32+16           ; Reprogram high & low bytes to 0
  163.         Out     043h, al
  164.         Xor     al, al
  165.         Out     042h, al
  166.         Out     042h, al
  167.         Mov     al, 128+16              ; Tell 8253 we only want to change the
  168.         Out     043h,al                 ; low byte for now on
  169.  
  170.         sti                             ; Let it lose!
  171.  
  172. ExitTweakOn:
  173.         Pop     di                      ; Pop regs and exit to QB
  174.         Pop     si
  175.         Pop     ds
  176.         Pop     bp
  177.  
  178.         ret     2
  179. TweakOn endp
  180. ; This is the PC-Speaker translation table. A simple XLAT statement
  181. ; will translate an 8-bit signed sample into the correct delay value
  182. ; for the 8253 timer. (See MAKESIGN.BAS to see how this table was made.)
  183. TweakTable:
  184. db 32,31,30,29,28,27,26,25,24,24,23,23,22,22,21,21,21,20,20,20,19,19,19
  185. db 18,18,18,18,17,17,17,17,16,16,16,16,15,15,15,15,14,14,14,14,14,13,13
  186. db 13,13,13,12,12,12,12,12,11,11,11,11,11,11,10,10,10,10,10,10,9,9,9,9
  187. db 9,9,8,8,8,8,8,8,7,7,7,7,7,7,7,6,6,6,6,6,6,6,5,5,5,5,5,5,5,4,4,4,4,4
  188. db 4,4,3,3,3,3,3,3,3,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,65,65,65,65,65,65,65
  189. db 64,64,64,64,64,64,64,63,63,63,63,63,63,63,62,62,62,62,62,62,62,61,61
  190. db 61,61,61,61,61,60,60,60,60,60,60,60,59,59,59,59,59,59,59,58,58,58,58
  191. db 58,58,57,57,57,57,57,57,56,56,56,56,56,56,55,55,55,55,55,55,54,54,54
  192. db 54,54,53,53,53,53,53,52,52,52,52,52,51,51,51,51,50,50,50,50,49,49,49
  193. db 49,48,48,48,48,47,47,47,46,46,46,45,45,45,44,44,43,43,42,42,41,40,39
  194. db 38,37,36,35,34,33
  195. Even
  196.  
  197. IFreq           dw 65535                ; Original interrupt rate
  198. OldOfs          dw 0                    ; Old interrupt vector
  199. OldSeg          dw 0
  200.  
  201. SampleOffset    dw 0                    ; Current offset
  202. SampleSegment   dw 0                    ; Current segment
  203. SampleEnd       dw 0                    ; End of sample
  204. StepRate        dw 0                    ; Step rate of sample, actually Step/256
  205. OffsetRemainder db 0                    ; Current offset remainder
  206.  
  207. Even
  208. NewInterrupt proc far
  209.         Push    ax                      ; Save ax
  210.         Cmp     [cs:endflag], 0ffh      ; Any samples to play?
  211.         Je      SamplesLeft             ; Yup.
  212.         Mov     al, 020h                ; Send End of Interrupt command
  213.         Out     020h, al
  214.         Pop     ax                      ; Restore ax
  215.         Iret                            ; Return to original program
  216.         Even
  217. SamplesLeft:                            ; Samples are left
  218.         Push    ds                      ; Preserve what we use
  219.         Push    es
  220.         Push    si
  221.         Push    bx
  222.  
  223.         Mov     ax, cs                              ; Set ds to cs
  224.         Mov     ds, ax
  225.         Les     si, dword ptr [ds:SampleOffset]     ; Get current offset:segment
  226.         Mov     bx, offset TweakTable               ; Prepare to translate
  227.         Mov     al, [es:si]                         ; Get sample
  228.  
  229. ; Add this statement to play unsigned samples!!
  230.  
  231.         ;Add     al, 128
  232.  
  233.         Xlat                                        ; Translate sample
  234.         Out     042h, al                            ; Send it to the 8253
  235.  
  236.         Xor     ax, ax                              ; Add the step rate to the
  237.         Mov     bx, [ds:StepRate]                   ; offset
  238.         Add     [ds:OffsetRemainder], bl            ; Adjust the remainder
  239.         Mov     al, bh                              ; Add whole portion+carry
  240.         Adc     si, ax
  241.         Mov     [ds:SampleOffset], si               ; Save it
  242.  
  243.         Cmp     si, [ds:SampleEnd]                  ; Are we too far?
  244.         Jae     EndOfSample                         ; Yup.
  245. ExitInterrupt:
  246.         Pop     bx                                  ; Pop all regs and return
  247.         Pop     si
  248.         Pop     es
  249.         Pop     ds
  250.  
  251.         Mov     al, 020h
  252.         Out     020h, al
  253.         Pop     ax
  254.         Iret
  255.         Even
  256. EndOfSample:                                        ; No more samples left
  257.         Mov     [ds:EndFlag], ah                    ; Fix flag
  258.         Jmp     short ExitInterrupt                 ; Jump back
  259. NewInterrupt endp
  260. Even
  261. PlaySound proc far                                  ; Lets the interrupt
  262.                                                     ; play a sample
  263. ;Stack frame...
  264. ;PlaySound Offset%, Segment%, Length%, Frequency%
  265. ;             12         10       8      6
  266.  
  267.         Push    bp
  268.         Mov     bp, sp
  269.         Push    ds
  270.         Push    si
  271.         Push    di
  272.  
  273.         Mov     ax, cs
  274.         Mov     ds, ax
  275.  
  276.         Mov     ax, [ss:bp+06]          ; Frequency in range?
  277.         Cmp     ax, 40000
  278.         Ja      ExitPlaySound           ; Nope
  279.         And     ax, ax
  280.         Jz      ExitPlaySound           ; Nope
  281.  
  282.         Xor     dx, dx                  ; Multiply frequency by 256
  283.         Mov     dl, ah                  ; (shift it left 8 places)
  284.         Mov     ah, al
  285.         Mov     al, dh
  286.  
  287.         Div     [ds:Ifreq]              ; Divide it by the interrupt rate
  288.                                         ; to find the step rate(which will
  289.                                         ; actually be multiplied by 256)
  290.         Cli                             ; Turn off interrupts
  291.         Mov     [ds:StepRate], ax       ; Save the step rate
  292.  
  293.         Mov     bx, [ss:bp+12]          ; Save the offset and segment of sample
  294.         Mov     [ds:SampleOffset], bx
  295.         Mov     ax, [ss:bp+10]
  296.         Mov     [ds:SampleSegment], ax
  297.         Add     bx, [ss:bp+8]
  298.         Mov     [ds:SampleEnd], bx      ; Save the end of the sample
  299.  
  300.         Mov     [ds:OffsetRemainder], 0 ; Clear the remainder
  301.         Mov     [ds:EndFlag], 0ffh      ; Let it rip!
  302.         Sti
  303.  
  304. ExitPlaySound:
  305.         Pop     di
  306.         Pop     si
  307.         Pop     ds
  308.         Pop     bp
  309.         Ret     8
  310. PlaySound endp
  311. Even
  312. TweakOff proc far                       ; Disables the driver.
  313.         Push    ds
  314.  
  315.         Mov     ax, 03508h              ; Check to make sure the interrupt
  316.         Int     021h                    ; 08h vector is pointing to my routine
  317.  
  318.         Cmp     bx, offset NewInterrupt
  319.         Jne     AlreadyChanged          ; Nope
  320.         Mov     ax, cs
  321.         Mov     ds, ax                  ; ds = cs
  322.         Mov     bx, es
  323.         Cmp     ax, bx
  324.         Jne     AlreadyChanged          ; Nope
  325.  
  326.         Cli                             ; Disable interrupts
  327.  
  328.         Mov     al, 00110110b           ; Reprogram the timer to 18.206hz
  329.         Out     043h, al
  330.         Xor     al, al
  331.         Out     040h, al
  332.         Out     040h, al
  333.  
  334.         Lds     dx, dword ptr [ds:OldOfs]   ; restore the old vector
  335.         Mov     ax, 02508h
  336.         Int     021h
  337.  
  338.         Sti                             ; Let the interrupts go
  339.  
  340.         In      al, 061h                ; Turn the speaker off
  341.         And     al, NOT 03h             ; (should this be "And  al, 254" ?)
  342.         Out     061h, al
  343.  
  344. AlreadyChanged:
  345.         Pop     ds                      ; All done or interrupt 08h vector
  346.         Ret     0                       ; isn't pointing to our routine
  347. TweakOff endp
  348. even
  349. TweakStatus proc far                    ; Checks the state of the sample
  350.         Mov     al, [cs:EndFlag]        ; Get flag
  351.         Cbw                             ; Make it a word
  352.         Ret     0                       ; Exit
  353. TweakStatus endp
  354. cseg ends
  355. end
  356.