home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / PASCAL / BGSOUND.ZIP / BGSND.INC < prev   
Encoding:
Text File  |  1986-06-06  |  10.7 KB  |  288 lines

  1. { BGSND.INC
  2.  
  3.   Background Sound for Turbo Pascal
  4.   Michael Quinlan
  5.   9/17/85
  6.  
  7.   Modified 6/6/86 by R. P. Byrne
  8.     -*-  Changed procedure _BGSSetInt to use inline assembler code   -*-
  9.     -*-  The original code used DOS function 25h to change           -*-
  10.     -*-  interrupt vectors.  The new inline code accesses the        -*-
  11.     -*-  interrupt vector table directly to avoid conflicts with     -*-
  12.     -*-  other DOS function calls that may be issued simultaneously  -*-
  13.     -*-  from within the body of the main program.                   -*-
  14.     -*-  (ie. The background sound routine sets up an ISR for the    -*-
  15.     -*-  Timer Tick interrupt (1C).  This ISR calls _BGSSetInt       -*-
  16.     -*-  to reset the vector table back to its original state after  -*-
  17.     -*-  it has completed its task.  But calling DOS from an ISR is  -*-
  18.     -*-  a tricky business at best and was causing my system to      -*-
  19.     -*-  crash.  This patch corrected that.)                         -*-
  20.  
  21.   The routines are rather primitive, but could easily be extended.
  22.  
  23.   The sample routines at the end implement something similar to the
  24.   BASIC PLAY statement.
  25.  
  26. }
  27.  
  28. type BGSItem   = record
  29.                    cnt  : integer;  { count to load into the 8253-5 timer;
  30.                                       count = 1,193,180 / frequency }
  31.                    tics : integer   { timer tics to maintain the sound;
  32.                                       18.2 tics per second }
  33.                  end;
  34.  
  35.      _BGSItemP = ^BGSItem;
  36.  
  37.      _BGSCharP = ^Char;
  38.  
  39. const BGSPlaying : boolean = FALSE;  { TRUE while music is playing }
  40.       _BGSDSSave : integer = 0;
  41.  
  42. var _BGSNextItem : _BGSItemP;
  43.     _BGSNumItems : integer;
  44.     _BGSOldInt1C : _BGSCharP;
  45.     _BGSDuration : integer;
  46.  
  47. function _BGSGetInt(int : integer) : _BGSCharP;
  48. { call MsDos to get interrupt vector }
  49.   var R : record case integer of
  50.             1 : (ax, bx, cx, dx, bp, si, di, ds, es, flags : integer);
  51.             2 : (al, ah, bl, bh, cl, ch, dl, dh            : byte)
  52.           end;
  53.   begin
  54.     with R do begin
  55.       ah := $35;
  56.       al := int;
  57.       MsDos(R);
  58.       _BGSGetInt := Ptr(es, bx)
  59.     end
  60.   end;
  61.  
  62. procedure _BGSSetInt(int, s, o : integer);
  63. { call to set an interrupt vector }
  64.   begin
  65.     Inline($B8/$00/$00/    { mov    ax,0                                   }
  66.            $8E/$C0/        { mov    es,ax       ;es=seg of vector table    }
  67.            $8B/$86/int/    { mov    ax,[bp+int] ;load interrupt num. in al }
  68.            $B1/$04/        { mov    cl,4        ;each vector is 4 bytes    }
  69.            $F6/$E1/        { mul    cl          ;ax=ofs of vector in table }
  70.            $89/$C7/        { mov    di,ax       ;es:di = addr of vector    }
  71.            $8B/$86/o/      { mov    ax,[bp+o]   ;ax=new offset for vector  }
  72.            $FA/            { cli                ;no interruptions please   }
  73.            $AB/            { stosw              ;store offset into vector  }
  74.            $8B/$86/s/      { mov    ax,[bp+s]   ;ax=new segment for vector }
  75.            $AB/            { stosw              ;store segment into vector }
  76.            $FB);           { sti                ;interrupts back on        }
  77.   end;
  78.  
  79. procedure _BGSPlayNextItem;
  80. { used internally to begin playing the next sound segment }
  81.   begin
  82.     _BGSNumItems := _BGSNumItems - 1;
  83.     Port[$43] := $B6;
  84.     with _BGSNextItem^ do begin
  85.       Port[$42] := Lo(cnt);
  86.       Port[$42] := Hi(cnt);
  87.       _BGSDuration := tics;
  88.       if cnt <> 0 then Port[$61] := Port[$61] or $03
  89.     end;
  90.     _BGSNextItem := Ptr(Seg(_BGSNextItem^), Ofs(_BGSNextItem^) + SizeOf(BGSItem))
  91.   end;
  92.  
  93. procedure _BGSInt1C;
  94. { Interrupt procedure invoked 18.2 times a second. Decrements a count and
  95.   when the count equals zero, selects the next sound segment to play. }
  96.  
  97.   begin
  98.     Inline($50/$53/$51/$52/$56/$57/$1E/$06/$FB);
  99.     Inline($2E/$A1/_BGSDSSave/$8E/$D8);  { move _BGSDSSave to DS reg }
  100.     _BGSDuration := _BGSDuration - 1;
  101.     if _BGSDuration = 0 then begin
  102.       Port[$61] := Port[$61] and $F8;
  103.       if _BGSNumItems = 0 then begin
  104.         _BGSSetInt($1C, Seg(_BGSOldInt1C^), Ofs(_BGSOldInt1C^));
  105.         BGSPlaying := FALSE
  106.       end else begin
  107.         _BGSPlayNextItem
  108.       end
  109.     end;
  110.     Inline($07/$1F/$5F/$5E/$5A/$59/$5B/$58/$8B/$E5/$5D/$CF)
  111.   end;
  112.  
  113. procedure BGSPlay(n : integer; var items);
  114.  
  115. { You call this procedure to play music in the background. You pass the number
  116.   of sound segments, and an array with an element for each sound segment. The
  117.   array elements are two words each; the first word has the count to be loaded
  118.   into the timer (1,193,180 / frequency). The second word has the duration of
  119.   the sound segment, in timer tics (18.2 tics per second). }
  120.  
  121.   var item_list : array[0..1000] of BGSItem absolute items;
  122.   begin
  123.  
  124.     while BGSPlaying do  { wait for previous sounds to finish }
  125.       ;
  126.  
  127.     if n > 0 then begin
  128.       _BGSNumItems := n;
  129.       _BGSNextItem := Addr(item_list[0]);
  130.       BGSPlaying   := TRUE;
  131.       _BGSPlayNextItem;
  132.       _BGSOldInt1C := _BGSGetInt($1C);
  133.       _BGSDSSave := DSeg;
  134.       _BGSSetInt($1C, CSeg, Ofs(_BGSInt1C))
  135.     end
  136.   end;
  137.  
  138. {**************************************************************************}
  139. {                                                                          }
  140. {   Sample Routines                                                        }
  141. {                                                                          }
  142. {**************************************************************************}
  143.  
  144. type s255 = string[255];
  145.  
  146. var MusicArea : array[1..100] of BGSItem; { contains sound segments }
  147.  
  148. { frequency table from Peter Norton's Programmer's Guide to the IBM PC, p. 147 }
  149. const Frequency : array[0..83] of real =
  150.   {    C        C#       D        D#       E        F        F#       G        G#       A        A#       B }
  151.     (32.70,   34.65,   36.71,   38.89,   41.20,   43.65,   46.25,   49.00,   51.91,   55.00,   58.27,   61.74,
  152.      65.41,   69.30,   73.42,   77.78,   82.41,   87.31,   92.50,   98.00,  103.83,  110.00,  116.54,  123.47,
  153.     130.81,  138.59,  146.83,  155.56,  164.81,  174.61,  185.00,  196.00,  207.65,  220.00,  233.08,  246.94,
  154.     261.63,  277.18,  293.66,  311.13,  329.63,  349.23,  369.99,  392.00,  415.30,  440.00,  466.16,  493.88,
  155.     523.25,  554.37,  587.33,  622.25,  659.26,  698.46,  739.99,  783.99,  830.61,  880.00,  932.33,  987.77,
  156.    1046.50, 1108.73, 1174.66, 1244.51, 1378.51, 1396.91, 1479.98, 1567.98, 1661.22, 1760.00, 1864.66, 1975.53,
  157.    2093.00, 2217.46, 2349.32, 2489.02, 2637.02, 2793.83, 2959.96, 3135.96, 3322.44, 3520.00, 3729.31, 3951.07);
  158.  
  159. procedure PlayMusic(s : s255);
  160. { Accept a string similar to the BASIC PLAY statement. The following are
  161.   allowed:
  162.     A to G with optional #
  163.       Plays the indicated note in the current octave. A # following the letter
  164.       indicates sharp. A number following the letter indicates the length of
  165.       the note (4 = quarter note, 16 = sixteenth note, 1 = whole note, etc.).
  166.     On
  167.       Sets the octave to "n". There are 7 octaves, numbered 0 to 6. Each octave
  168.       goes from C to B. Octave 3 starts with middle C.
  169.     Ln
  170.       Sets the default length of following notes. L1 = whole notes, L2 = half
  171.       notes, etc. The length can be overridden for a specific note by follow-
  172.       ing the note letter with a number.
  173.     Pn
  174.       Pause. n specifies the length of the pause, just like a note.
  175.     Tn
  176.       Tempo. Number of quarter notes per minute. Default is 120.
  177.  
  178.   Spaces are allowed between items, but not within items. }
  179.  
  180.   var i, n : integer;  { i is the offset in the parameter string;
  181.                          n is the element number in MusicArea }
  182.       cchar : char;
  183.  
  184.   var NoteLength    : integer;
  185.       Tempo         : integer;
  186.       CurrentOctave : integer;
  187.  
  188.   function GetNumber : integer;
  189.   { get a number from the parameter string }
  190.   { increments i past the end of the number }
  191.     var n : integer;
  192.     begin
  193.       n := 0;
  194.       while (i <= length(s)) and (s[i] in ['0'..'9']) do begin
  195.         n := n * 10 + (Ord(s[i]) - Ord('0'));
  196.         i := i + 1
  197.       end;
  198.       GetNumber := n
  199.     end;
  200.  
  201.   procedure GetNote;
  202.   { input is a note letter. convert it to two sound segments --
  203.     one for the sound then a pause following the sound. }
  204.   { increments i past the current item }
  205.     var note : integer;
  206.         len  : integer;
  207.         l    : real;
  208.  
  209.     function CheckSharp(n : integer) : integer;
  210.     { check for a sharp following the letter. increments i if one found }
  211.       begin
  212.         if (i < length(s)) and (s[i] = '#') then begin
  213.           i := i + 1;
  214.           CheckSharp := n + 1
  215.         end else
  216.           CheckSharp := n
  217.       end;  { CheckSharp }
  218.  
  219.     function FreqToCount(f : real) : integer;
  220.     { convert a frequency to a timer count }
  221.       begin
  222.         FreqToCount := Round(1193180.0 / f)
  223.       end;  { FreqToCount }
  224.  
  225.     begin  { GetNote }
  226.       case cchar of
  227.         'A' : note := CheckSharp(9);
  228.         'B' : note := 11;
  229.         'C' : note := CheckSharp(0);
  230.         'D' : note := CheckSharp(2);
  231.         'E' : note := 4;
  232.         'F' : note := CheckSharp(5);
  233.         'G' : note := CheckSharp(7)
  234.       end;
  235.       MusicArea[n].cnt := FreqToCount(Frequency[(CurrentOctave * 12) + note]);
  236.       if (s[i] in ['0'..'9']) and (i <= length(s)) then
  237.         len := GetNumber
  238.       else
  239.         len := NoteLength;
  240.       l := 18.2 * 60.0 * 4.0 / (Tempo * len);
  241.       MusicArea[n].tics := Round(7.0 * l / 8.0);
  242.       if MusicArea[n].tics = 0 then MusicArea[n].tics := 1;
  243.       n := n + 1;
  244.       MusicArea[n].cnt := 0;
  245.       MusicArea[n].tics := Round(l / 8.0);
  246.       if MusicArea[n].tics = 0 then MusicArea[n].tics := 1;
  247.       n := n + 1
  248.     end;  { GetNote }
  249.  
  250.   procedure GetPause;
  251.   { input is a pause. convert it to a silent sound segment. }
  252.   { increments i past the current item }
  253.     var len  : integer;
  254.         l    : real;
  255.  
  256.     begin  { GetPause }
  257.       MusicArea[n].cnt := 0;
  258.       if (s[i] in ['0'..'9']) and (i <= length(s)) then
  259.         len := GetNumber
  260.       else
  261.         len := NoteLength;
  262.       l := 18.2 * 60.0 * 4.0 / (Tempo * len);
  263.       MusicArea[n].tics := Round(l);
  264.       if MusicArea[n].tics = 0 then MusicArea[n].tics := 1;
  265.       n := n + 1;
  266.     end;  { GetPause }
  267.  
  268.   begin
  269.     NoteLength := 4;
  270.     Tempo := 120;
  271.     CurrentOctave := 3;
  272.  
  273.     n := 1;
  274.     i := 1;
  275.     while i <= length(s) do begin
  276.       cchar := s[i];
  277.       i := i + 1;
  278.       case cchar of
  279.         'A'..'G' : GetNote;
  280.         'O'      : CurrentOctave := GetNumber;
  281.         'L'      : NoteLength    := GetNumber;
  282.         'P'      : GetPause;
  283.         'T'      : Tempo         := Getnumber
  284.       end
  285.     end;
  286.     BGSPlay(n-1, MusicArea)
  287.   end;
  288.