home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / PASCAL / BKPLAY10.ZIP / BACKPLAY.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1988-09-24  |  10.1 KB  |  260 lines

  1. {$B-,F-,I+,O-,R-}
  2.  
  3. unit BackPlay;
  4.  
  5. { Unit for playing music in the background.
  6.  
  7.   Copyright 1988 Scott Bussinger
  8.   All rights reserved.
  9.   Permission is hereby granted by the author for you to use this unit in your programs.
  10.  
  11.   Scott Bussinger
  12.   Professional Practice Systems
  13.   110 South 131st Street
  14.   Tacoma, WA  98444
  15.   (206)531-8944
  16.   Compuserve [72247,2671]
  17.  
  18.   Version 1.00 --  9/24/1988 -- First version }
  19.  
  20. interface
  21.  
  22. type Song = procedure;
  23.      SongAction = (EndRepeatSong,RepeatSong,ResumeSong,StopSong,SuspendSong);
  24.  
  25. function PlayingInBackground: boolean;
  26.   { Is there a song currently playing? }
  27.  
  28. procedure PlayingMode(Action: SongAction);
  29.   { Change the play mode }
  30.  
  31. function PlayMuz(Filename: string): boolean;
  32.   { Play a song in the background loaded from a file -- returns true if file found }
  33.  
  34. procedure PlaySong(S: Song);
  35.   { Play a song in the background already loaded in memory }
  36.  
  37. implementation
  38.  
  39. uses Dos;
  40.  
  41. const BackgroundBufferSize = 256;                { Maximum number of note changes in song }
  42.       FreqConstant = 1193180.0;                  { Master timer chip clock rate }
  43.       TickConstant = FreqConstant / 65536.0 / 1000.0; { Constant for tick speed }
  44.  
  45. const CurrentNote: 0..BackgroundBufferSize+1 = 0; { Pointer to current note in BackgroundBuffer }
  46.       LastNote: 0..BackgroundBufferSize = 0;     { Pointer to last note in BackgroundBuffer }
  47.       Playing: boolean = false;                  { Are we currently playing? }
  48.       Repeating: boolean = false;                { Repeat song at end (automatically turned off when song started) }
  49.       Suspended: boolean = false;                { Suspend playing temporarily (automatically turned off when song started) }
  50.       TicksLeftInNote: word = 0;                 { Number of ticks left in current note }
  51.  
  52. var BackgroundBuffer: array[0..BackgroundBufferSize] of record
  53.       TimerCount: word;                          { Timer constant for the frequency }
  54.       Duration: word                             { Number of ticks left for this note }
  55.       end;
  56.     ExitSave: pointer;                           { Previous exit procedure }
  57.     SaveInt1C: pointer;                          { Previous timer interrupt handler }
  58.  
  59. procedure DisableInterrupts;
  60.   { Turn off interrupts }
  61.   inline($FA);                                   { CLI }
  62.  
  63. procedure EnableInterrupts;
  64.   { Turn on interrupts }
  65.   inline($FB);                                   { STI }
  66.  
  67. procedure JumpToOldISR(OldIsr: pointer);
  68.   { Chain on to previous ISR (doesn't return) }
  69.   inline($5B/$58/$87/$5E/$0E/$87/$46/$10/$89/
  70.          $EC/$5D/$07/$1F/$5F/$5E/$5A/$59/$CB);
  71.  
  72. {$F+,S-}
  73. procedure Int1CHandler; interrupt;
  74.   { Process timer interrupt }
  75.   begin
  76.   if Playing and not Suspended then              { Quit fast if we're not playing right now }
  77.     begin
  78.     if TicksLeftInNote = 0                       { Time to change notes }
  79.      then
  80.       begin
  81.       inc(CurrentNote);
  82.       if CurrentNote > LastNote
  83.        then
  84.         begin
  85.         Port[$61] := Port[$61] and $FC;          { Turn sound off }
  86.         CurrentNote := 0;                        { Reset buffer to beginning }
  87.         if not Repeating then
  88.           begin
  89.           Playing := false;                      { We're done playing }
  90.           LastNote := 0                          { Start filling from beginning }
  91.           end
  92.         end
  93.        else
  94.         with BackgroundBuffer[CurrentNote] do    { Change to new frequency }
  95.           begin
  96.           TicksLeftInNote := Duration;           { How long to hold note }
  97.           if TimerCount = 0
  98.            then
  99.             Port[$61] := Port[$61] and $FC       { Turn sound off }
  100.            else
  101.             begin
  102.             Port[$43] := $B6;                    { Change to new frequency }
  103.             Port[$42] := lo(TimerCount);
  104.             Port[$42] := hi(TimerCount);
  105.             Port[$61] := Port[$61] or $03        { Turn sound on }
  106.             end
  107.           end
  108.       end
  109.      else
  110.       dec(TicksLeftInNote)                       { Wait for note to finish }
  111.     end;
  112.   JumpToOldISR(SaveInt1C)                        { Call other interrupt handlers }
  113.   end;
  114. {$F-,S+}
  115.  
  116. function PlayingInBackground: boolean;
  117.   { Is there a song currently playing? }
  118.   begin
  119.   PlayingInBackground := Playing
  120.   end;
  121.  
  122. procedure PlayingMode(Action: SongAction);
  123.   { Change the play mode }
  124.   begin
  125.   case Action of
  126.     EndRepeatSong: Repeating := false;
  127.     RepeatSong: if Playing then
  128.                   Repeating := true;
  129.     ResumeSong: if Suspended then
  130.                   Suspended := false;
  131.     StopSong: if Playing then
  132.                 begin
  133.                 Port[$61] := Port[$61] and $FC;  { Turn sound off }
  134.                 Playing := false;
  135.                 Repeating := false;
  136.                 Suspended := false;
  137.                 CurrentNote := 0;                { Reset buffer to beginning }
  138.                 LastNote := 0;
  139.                 TicksLeftInNote := 0             { So first tick starts the song }
  140.                 end;
  141.     SuspendSong: if Playing and not Suspended then
  142.                    begin
  143.                    Port[$61] := Port[$61] and $FC; { Turn sound off }
  144.                    Suspended := true;
  145.                    TicksLeftInNote := 0          { Chop off current note }
  146.                    end
  147.     end
  148.   end;
  149.  
  150. procedure PlaySong(S: Song);
  151.   { Play a song in the background already loaded in memory }
  152.   { Add this song to currently playing tune if a tune is still in progress }
  153.   { Turns off automatic song repeat mode }
  154.   type NoteArray = array[1..16383] of record     { Arbitrary sized collection of notes }
  155.          O,NS: Byte;
  156.          D: Word
  157.          end;
  158.   var SongPtr: ^NoteArray;                       { Pointer to a song }
  159.       I: integer;
  160.  
  161.   procedure PlayANote(Octave,NoteStaccato: byte;Duration: integer);
  162.     { Play a single note from MUZ file }
  163.     const Factor: array[0..10] of real = (0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0);
  164.           FreqVal: array[1..12] of real = (1.0000000000, 1.0594630944, 1.1224620484, 1.1892071151,
  165.                                            1.2599210501, 1.3348398544, 1.4142135627, 1.4983070773,
  166.                                            1.5874010524, 1.6817928311, 1.7817974369, 1.8877486261);
  167.           OctVal: array[0..7] of real = (  65.406391320,  130.81278264,  261.62556528,  523.25113056,
  168.                                          1046.5022611,   2093.0045222,  4186.0090445,  8372.0180890);
  169.     var Note: byte;
  170.         Staccato: byte;
  171.  
  172.     procedure StuffNote(Freq,Dur: word);
  173.              { Put note information into BackgroundBuffer }
  174.       begin
  175.       DisableInterrupts;                         { Don't let a note change happen during this stretch }
  176.       if (LastNote<BackgroundBufferSize) and (Dur>0) then { Don't enter 0 length durations }
  177.         begin
  178.         inc(LastNote);                           { Bump note counter }
  179.         with BackgroundBuffer[LastNote] do       { Fill in the note information }
  180.           begin
  181.           TimerCount := Freq;
  182.           Duration := Dur - 1                    { Pre-decrement the duration }
  183.           end;
  184.         Playing := true                          { There's at least one note in buffer, so start playing }
  185.         end;
  186.       EnableInterrupts                           { Turn interrupts back on again }
  187.       end;
  188.  
  189.     begin
  190.     Note := NoteStaccato shr 4;
  191.     Staccato := (NoteStaccato and $0F) mod 11;
  192.     CASE Note OF
  193.       1..12: begin                               { Stuff on and off portion of notes }
  194.               StuffNote(round(FreqConstant / (OctVal[(Octave-1) mod 8] * FreqVal[Note])),
  195.                        round(Duration * Factor[10-Staccato] * TickConstant));
  196.              StuffNote(0,round(Duration * Factor[Staccato] * TickConstant))
  197.              end;
  198.       13: StuffNote(0,round(Duration * TickConstant)) { Stuff a rest into buffer }
  199.       else
  200.       end
  201.     end;
  202.  
  203.   begin
  204.   SongPtr := @S;                                 { Get address of the song in memory }
  205.   for I := 10 to (longint(SongPtr^[6]) and $FFFF) + 9 do { Play each of the notes in the song }
  206.     with SongPtr^[I] do
  207.       PlayANote(O,NS,D);
  208.   Repeating := false                             { Turn off automatic repeat anytime you add to the music buffer }
  209.   end;
  210.  
  211. function PlayMuz(Filename: string): boolean;
  212.   { Play a song in the background loaded from a file -- returns true if file found }
  213.   { Add this song to currently playing tune if a tune is still in progress }
  214.   { Turns off automatic song repeat mode }
  215.   var SaveFileMode: word;
  216.       Size: longint;
  217.       SongFile: file;
  218.       SongPtr: pointer;
  219.   begin
  220.   PlayMuz := false;                              { Default to file not loaded }
  221.   FileName := FSearch(FileName+'.MUZ',GetEnv('PATH')); { Search PATH for the song file }
  222.   if Filename <> '' then
  223.     begin
  224.     SaveFileMode := FileMode;
  225.     FileMode := $20 * ord(lo(DosVersion)>=3);    { Allow access to read only files }
  226.     assign(SongFile,Filename);                   { Open the song file }
  227.     {$I-}
  228.     reset(SongFile,1);
  229.     {$I+}
  230.     if ioresult = 0 then                         { Since we've already found it, this really should always work }
  231.       begin
  232.       Size := FileSize(SongFile);
  233.       getmem(SongPtr,Size);                      { Load file onto heap temporarily }
  234.       blockread(SongFile,SongPtr^,Size);
  235.       close(SongFile);
  236.       FileMode := SaveFileMode;
  237.       PlaySong(Song(SongPtr));
  238.       freemem(SongPtr,Size);                     { Free up the heap again }
  239.       PlayMuz := true
  240.       end
  241.     end
  242.   end;
  243.  
  244. {$F+}
  245. procedure ExitHandler;
  246. {$F-}
  247.   { Restore the timer interrupt and make sure sound is off }
  248.   begin
  249.   ExitProc := ExitSave;                          { Chain to other exit procedures }
  250.   SetIntVec($1C,SaveInt1C);                      { Remove interrupt handler }
  251.   Port[$61] := Port[$61] and $FC                 { Make sure sound is off }
  252.   end;
  253.  
  254. begin
  255. ExitSave := ExitProc;
  256. ExitProc := @ExitHandler;                        { Install our exit procedure }
  257. GetIntVec($1C,SaveInt1C);
  258. SetIntVec($1C,@Int1CHandler)                     { Install our timer interrupt handler }
  259. end.
  260.