[comp.lang.pascal] Background Music

consp06@bingsuns.cc.binghamton.edu (Robert Konigsberg) (10/01/90)

I'm working on a program that I would like to include background music. 
I know there is code out there that to do this.  After all, Turbo Basic
has the "PLAY"
statement.  I started writing my own, but it just ain't workin out as neet as I
wanted it to be,  If anyone has any code at all whatsoever (it could be
in C, too) compiled or not, (preferable not compiled) please let me
know..  Or, feel free to discuss the difficulties here.  What I mean is
that ANY information would be enlightening

			-Rob Konigsberg

KRW1@Lehigh (10/03/90)

Here's a sample program for doing background music in Turbo
Pascal.  It could be turned into a general-purpose unit without
too much difficulty.  -- Kevin

-------------------------------------------------------------------
program backplay;

{Sample program demonstrating how background music may be played in
 a running Turbo Pascal program.  The routine getevents should be replaced
 with something that loads the actual song.  Events consist of a note
 frequency specification (look up value in period array; item 0 is no
 sound) and a duration in system ticks (.0549 sec).

 Background playing is driven by the 1C user timer interrupt (18.2 int/sec).
 Higher resolution can be accomplished, but is more difficult.  The base
 timer interrupt (08) can be changed to a different rate in a manner
 similar to programming the speaker timer, but this is dangerous, since
 other processes may be depending on the correct rate.  It is also possible
 to use the DOS background/idle interrupt, but that is unpredictable and
 must be used in conjunction with a timer interrupt for synchronization.
 It is also possible to use a temporary stack in the interrupt routine
 if more sophisticated processing is required.  Contact the author for
 additional information.

 Kevin Weiner   krw1@ns.cc.lehigh.edu }

{$r-,s-}

uses dos, crt;

const
  maxevents = 100;                     {Max size of event list}

  {Note frequencies in cycles per second corresponding to MIDI notes 1-127
   (C#-2 to G8).  Middle C is note 60.  Frequencies are converted to
   .84 microsecond counter ticks in array period in the initialization
   routine for actual timing use.}

  notefreq: array [0..127] of integer =
   (   0,     9,     9,    10,    10,    11,    12,    12,
      13,    14,    15,    15,    16,    17,    18,    19,
      21,    22,    23,    24,    26,    27,    29,    31,
      33,    35,    37,    39,    41,    44,    46,    49,
      52,    55,    58,    62,    65,    69,    73,    78,
      82,    87,    92,    98,   104,   110,   117,   123,
     131,   139,   147,   156,   165,   175,   185,   196,
     208,   220,   233,   247,   262,   277,   294,   311,
     330,   349,   370,   392,   415,   440,   466,   494,
     523,   554,   587,   622,   659,   698,   740,   784,
     831,   880,   932,   988,  1047,  1109,  1175,  1245,
    1319,  1397,  1480,  1568,  1661,  1760,  1865,  1976,
    2093,  2217,  2349,  2489,  2637,  2794,  2960,  3136,
    3322,  3520,  3729,  3951,  4186,  4435,  4699,  4978,
    5274,  5588,  5920,  6272,  6645,  7040,  7459,  7902,
    8372,  8870,  9397,  9956, 10548, 11175, 11840, 12544 );

var
  count,                               {Event duration counter in system
                                        timer ticks (18.2/sec)}
  eventnum,                            {Current event number}
  nevents:   integer;                  {Number of events defined}

  event: array [1..maxevents] of record
    ticks:     integer;                {Speaker timer period}
    duration:  integer;                {Duration in system ticks}
  end;

  period: array [0..127] of integer;   {Cycle length for note frequencies
                                        in spkr timer ticks (.84 microsec)}

  saveint: pointer;                    {Saved interrupt vector}


function inport(x: integer): byte;
  inline($5a/$eb/$00/$ec);             {Read port (w/short jump)}

procedure timerint; interrupt;

  { Procedure called at each system timer interrupt.

    Since there may not be enough available stack space at the time
    of the interrupt, no procedures should be called from this routine
    (and runtime checks should be disabled). }

  begin
    dec(count);                        {Count event duration}
    if eventnum < nevents then
      if count <= 0 then               {Time for next event?}
        begin
          inc(eventnum);               {Get next}
          with event[eventnum] do
            begin
              count := duration;       {Reset event timer}
              port[$42] := lo(ticks);  {Set speaker counter value}
              port[$42] := hi(ticks);
            end;
        end;
    inline($9c/$ff/$1e/saveint);       {Chain to old interrupt}
  end;

procedure getevents;

  { Define some events: 12-note chromatic scale from middle C, .5 sec
    per note with .2 sec rest (approx)}

  var
    i, j: integer;

  begin
    i := 1;
    j := 60;
    repeat
      event[i].ticks := period[j];     {Note}
      event[i].duration := 9;
      i := i + 1;
      event[i].ticks := period[0];     {Rest}
      event[i].duration := 4;
      i := i + 1;
      j := j + 1;
    until i > 24;
    nevents := 24;
    count := 0;
  end;

procedure init;

  var
    i: integer;

  begin
    count := 0;
    nevents := 0;
    eventnum := 0;

    for i := 1 to 127 do               {Convert freq to counter ticks}
      period[i] := 1193182 div notefreq[i];
    period[0] := $08;                  {No sound (very high frequency -
                                        quieter than turning speaker off)}

    getintvec($1c, saveint);           {Save present timer interrupt address}
    setintvec($1c, @timerint);         {Substitute our interrupt routine}
    port[$43] := $b6;                  {Counter 2, mode 3, LSB+MSB}
    port[$42] := $08;                  {Init timer value - no audible sound}
    port[$42] := $00;
    port[$61] := inport($61) or 3;     {Enable speaker, gate counter}
  end;

begin
  init;
  getevents;
  writeln('Press a key to exit');
  repeat
    {Idle while playing - this can be anything}
  until keypressed;
  {Clean up}
  port[$61] := inport($61) and $fc;    {Turn off speaker}
  setintvec($1c, saveint);             {Restore old interrupt - IMPORTANT!}
end.