{ SEE XX34 modules at end of document !!!} {$R-,F+} { ****************************************************************** BGSND.PAS Background Sound for Turbo Pascal Adapted from BGSND.INC for Turbo Pascal 3.0 by Michael Quinlan 9/17/85 This version for Turbo Pascal 6.0 by Larry Hadley 3/20/93 The routines are rather primitive, but could easily be extended. The sample routines included implement something similar to the BASIC PLAY statement. ****************************************************************** } Unit BGSND; INTERFACE Uses DOS; CONST BGSVer = '2.0'; { Unit version number } BGSPlaying :boolean = FALSE; { TRUE while music is playing } VAR _BGSNumItems :integer; procedure BGSPlay(n :integer; VAR items); procedure _BGSStopPlay; procedure PlayMusic(s :string); IMPLEMENTATION TYPE BGSItem = RECORD cnt :word; { count to load into the 8253-5 timer; count = 1,193,180 / frequency } tics:integer; { timer tics to maintain the sound; 18.2 tics per second } end; _BGSItemP = ^BGSItem; VAR _BGSNextItem :_BGSItemP; _BGSOldInt1C :pointer; _BGSDuration :integer; ExitSave :pointer; procedure _BGSsaveDS; external; { saves ds as a CS:CONSTANT for use within the int 1C vector } procedure _BGSPlayNextItem; external; { used by int 1C vector - selects next note to play } procedure _BGSStopPlay; external; procedure _BGSInt1C; external; { int1C vector - hooks timer } {$L BGS.OBJ} procedure BGSPlay(n :integer; VAR items); { *************************************************************************** You call this procedure to play music in the background. You pass the number of sound segments, and an array with an element for each sound segment. The array elements are two words each; the first word has the count to be loaded into the timer (1,193,180 / frequency). The second word has the duration of the sound segment, in timer tics (18.2 tics per second). *************************************************************************** } VAR item_list : array[0..1000] of BGSItem ABSOLUTE items; BEGIN while BGSPlaying do { wait for previous sounds to finish } ; if n > 0 then BEGIN _BGSNumItems := n; _BGSNextItem := Addr(item_list[0]); BGSPlaying := TRUE; _BGSPlayNextItem; _BGSsaveDS; SetIntVec($1C, @_BGSInt1C); END; END; procedure BGSErrorExit; { ************************************************************************** In case there's an "oopsie" ... make sure that Int $1C is clean, and music isn't playing. ************************************************************************** } BEGIN ExitProc := ExitSave; if BGSPLaying then BEGIN _BGSStopPlay; SetIntVec($1C, _BGSOldInt1C); END; END; { ************************************************************************** BASIC PLAY Routines ************************************************************************** } {$R+} VAR MusicArea : array[1..255] of BGSItem; { contains sound segments } { frequency table from: Peter Norton's Programmer's Guide to the IBM PC, p. 147 } CONST Frequency : array[0..83] of real = { C C# D D# E F F# G G# A A# B } (32.70, 34.65, 36.71, 38.89, 41.20, 43.65, 46.25, 49.00, 51.91, 55.00, 58.27, 61.74, 65.41, 69.30, 73.42, 77.78, 82.41, 87.31, 92.50, 98.00, 103.83, 110.00, 116.54, 123.47, 130.81, 138.59, 146.83, 155.56, 164.81, 174.61, 185.00, 196.00, 207.65, 220.00, 233.08, 246.94, 261.63, 277.18, 293.66, 311.13, 329.63, 349.23, 369.99, 392.00, 415.30, 440.00, 466.16, 493.88, 523.25, 554.37, 587.33, 622.25, 659.26, 698.46, 739.99, 783.99, 830.61, 880.00, 932.33, 987.77, 1046.50, 1108.73, 1174.66, 1244.51, 1378.51, 1396.91, 1479.98, 1567.98, 1661.22, 1760.00, 1864.66, 1975.53, 2093.00, 2217.46, 2349.32, 2489.02, 2637.02, 2793.83, 2959.96, 3135.96, 3322.44, 3520.00, 3729.31, 3951.07 ); procedure PlayMusic(s :string); { *************************************************************************** Accept a string similar to the BASIC PLAY statement. The following are allowed: A to G with optional # Plays the indicated note in the current octave. A # following the letter indicates sharp. A number following the letter indicates the length of the note (4 = quarter note, 16 = sixteenth note, 1 = whole note, etc.). On Sets the octave to "n". There are 7 octaves, numbered 0 to 6. Each octave goes from C to B. Octave 3 starts with middle C. Ln Sets the default length of following notes. L1 = whole notes, L2 = half notes, etc. The length can be overridden for a specific note by follow- ing the note letter with a number. Pn Pause. n specifies the length of the pause, just like a note. Tn Tempo. Number of quarter notes per minute. Default is 120. Period (.) terminates processing. Spaces are allowed between items, but not within items. *************************************************************************** } VAR i, n, { i is the offset in the parameter string; n is the element number in MusicArea } NoteLength, Tempo, CurrentOctave :integer; cchar :char; function GetNumber:integer; { ************************************************************************** get a number from the parameter string increments i past the end of the number ************************************************************************** } VAR n :integer; BEGIN n := 0; WHILE (i <= length(s)) and (s[i] in ['0'..'9']) do BEGIN n := n*10+(Ord(s[i])-Ord('0')); i := i+1; end; GetNumber := n; END; procedure GetNote; { ************************************************************************** Input is a note letter. convert it to two sound segments -- one for the sound then a pause following the sound. increments i past the current item ************************************************************************** } VAR note, len :integer; l :real; function CheckSharp(n :integer):integer; { ************************************************************************ check for a sharp following the letter. increments i if one found ************************************************************************ } BEGIN if (i < length(s)) and (s[i] = '#') then BEGIN i := i + 1; CheckSharp := n + 1 END ELSE CheckSharp := n; END; { CheckSharp } function FreqToCount(f : real) : integer; { *********************************************************************** convert a frequency to a timer count *********************************************************************** } BEGIN FreqToCount := Round(1193180.0/f); END; { FreqToCount } BEGIN { GetNote } case cchar of 'A' : note := CheckSharp(9); 'B' : note := 11; 'C' : note := CheckSharp(0); 'D' : note := CheckSharp(2); 'E' : note := 4; 'F' : note := CheckSharp(5); 'G' : note := CheckSharp(7) end; { case } MusicArea[n].cnt := FreqToCount(Frequency[(CurrentOctave*12)+note]); if (s[i] in ['0'..'9']) and (i <= length(s)) then len := GetNumber else len := NoteLength; l := 18.2*60.0*4.0/(Tempo*len); MusicArea[n].tics := Round(7.0*l/8.0); if MusicArea[n].tics = 0 then MusicArea[n].tics := 1; n := n + 1; MusicArea[n].cnt := 0; MusicArea[n].tics := Round(l/8.0); if MusicArea[n].tics = 0 then MusicArea[n].tics := 1; n := n + 1; END; { GetNote } procedure GetPause; { ************************************************************************ input is a pause. convert it to a silent sound segment. increments i past the current item ************************************************************************ } VAR len :integer; l :real; BEGIN { GetPause } MusicArea[n].cnt := 0; if (s[i] in ['0'..'9']) and (i <= length(s)) then len := GetNumber else len := NoteLength; l := 18.2*60.0*4.0/(Tempo*len); MusicArea[n].tics := Round(l); if MusicArea[n].tics = 0 then MusicArea[n].tics := 1; n := n + 1; END; { GetPause } BEGIN { PlayMusic } NoteLength := 4; Tempo := 120; CurrentOctave := 3; n := 1; i := 1; while (i <= length(s)) and (s[i]<>'.') do BEGIN cchar := s[i]; i := i + 1; case cchar of 'A'..'G' : GetNote; 'O' : CurrentOctave := GetNumber; 'L' : NoteLength := GetNumber; 'P' : GetPause; 'T' : Tempo := Getnumber end; { case } END; BGSPlay(n-1, MusicArea) END; { PlayMusic } BEGIN { Unit init code } ExitSave := ExitProc; ExitProc := @BGSErrorExit; GetIntVec($1C, _BGSOldInt1C); Writeln('BGS v'+BGSVer); END. (* DEMO PROGRAM FOR BACKGROUND SOUND *) {$M 1024, 0, 0} Program PlayBG; Uses DOS, CRT, BGSND; VAR F1 :text; play_str, buf, fname, progname :string; Procedure Usage; BEGIN Writeln('PLAYBG '); Writeln(#10+#13+'Where:'); Writeln(' is the file containing the music you want played in'); Writeln(' the background'); Writeln(#10+#13+'The playfile contains a series of notes in ascii format'); Writeln; Halt(1); END; {$I-} Function Exists(name:string):boolean; VAR F :file; BEGIN Assign(f, name); Reset(f); if IOresult<>0 then Exists := FALSE ELSE BEGIN Exists := TRUE; Close(f); END; END; {$I+} Function AskYN:boolean; VAR ch :char; BEGIN repeat ch := ReadKey; if ch = #0 then BEGIN ch := ReadKey; ch := #0; END; until ch in ['y','Y','n','N']; Write(ch); case ch of 'Y','y' : AskYN := TRUE; 'N','n' : AskYN := FALSE; END; END; BEGIN Writeln('Background Play 1.0'); if ParamCount<1 then Usage; fname := ParamStr(1); Assign(F1, fname); if (fname='') or not(Exists(fname)) then BEGIN Writeln('Invalid playfile.'); Halt(2); END; play_str := ''; Reset(F1); repeat ReadLn(F1, buf); play_str := play_str+buf; until Eof(F1) or (Length(play_str)>=200); Close(F1); Writeln(play_str); {debug} PlayMusic(play_str); Exec(GetEnv('COMSPEC'), ''); if BGSPlaying then BEGIN Writeln('Music still playing - wait for it to finish?'); if Not(AskYN) then _BGSStopPlay; while BGSPLaying do; END; END. (* XX34 Of OBJ CODE FILES. Extract to separte files and use XX3401 to create BGS.OBJ and PLAYFIL.ASC. Here is how to use : 1. Copy first block to BGS.XX. 2. run XX3401 : XX3401 D BGS.XX. This will create BGS.OBJ. 3. Copy second block to PLAYFIL.XX. 4. run XX3401 : XX3401 D PLAYFIL.XX 5. Write unit code to BGSND.PAS. Compile. 6. Write demo code to PLAYSND.PAS Compile and run. *XX3401-000674-210393--68--85-48874---------BGS.OBJ--1-OF--1 U-U+3aIuL4ZWPJlWNrBjRKtYL47bQmt-IooeW0++++-IRL7WPm--QrBZPK7gNL6U63NZ QbBdPqsUAWskAMS65U-+uF6-RFcKNHdQOK7hL47bQqxpPaFQMaRn9Y3HHJ46+k-+uImK +U++O6U1+20VZ7M9++F2EJF--2F-J22Xa+Q+G++++UA-2tM9++F1HoF3-2BDF2IVa+Q+ 8Bo+-+I-Icl3++lTEYRHHZJBGJF3HJA+13x0FpBCFJVIGJF3HE+ALo75IoxAF2ZCJ131 ++lTEYRHF3JGEJF7Hos+0Y75Ip-AEJZ7HYQ+ZN+L+++023x0FpBEH23NHYJMJ2ZIFIp3 +++XY-++++67Lo75IoZCJ131WE++Ad+F+++00Zx0FpBHFJF7HZE6+++tY-A+++6ALo75 IpBIHp-EH23N8E++Pt+F+++00Zx0FpBHEJN3F3A0++-EW+E+E86-YO1T++60+0uA5U++ mpK9v9U++6v+WoM8gEHqsMjsWoM4yei9FUWfysjZLQc4+9UQ+30V+U-EcE++I+vcnzzY MGHsta54-U+++AhJWymV++-6ck++g9PaEwEy+++aWULaEWO8FE5aEWO9FE8X+++aUno+ R+PYMEk1ta52DU++XII2XA8X++073U6+WyJRmpK9v3-HIJ7KJls4ymuC5U++cE++G8A+ +6Ay++++RGbYMGHsta41DU+++5IMi-k+I820+30V++-E1iV1zwM4++++ukKE1iVozkQT LptOKJhMWyJRnsmQLU12+pE0l0k4+ED2A+M-+wEz-U23l2Q4+E52GkM-+QFH-U20l4I4 +EH2REM-+gFx-U20l624+E92ZZE0l7Y4+EH2bEM--AGV-U22l8s4+E52i+M-+wGw-U21 lAI4+EJrWU6++5E+ ***** END OF XX-BLOCK ***** {------------------------- CUT HERE -------------------------------} *XX3401-000047-210393--68--85-51905-----PLAYFIL.ASC--1-OF--1 J1Uk62wo62ks62R4FIN5FoQUI1UUFYN4B0-5EY6o62R4FIN5FoQUFoN4FoN31Ec+ ***** END OF XX-BLOCK ***** *)