home *** CD-ROM | disk | FTP | other *** search
/ Kids Cube / 2_Music.iso / mel / music.pas < prev    next >
Pascal/Delphi Source File  |  1992-07-27  |  11KB  |  304 lines

  1. unit Music;
  2.  
  3. {
  4.    MUSIC.PAS allows you play music on IBM PC or compatible using the same
  5.    set of commands that you would use with BASICA's "PLAY" command.
  6.  
  7.    The original module was written and uploaded by Gregory Arakelian
  8.    (74017,223)  703-435-7137.   The code was unitized for Turbo Pascal
  9.    4.0 by Ted Lassagne (70325,206).  Code was added to handle dotted
  10.    notes.  Some error checking was added, and minor corrections and
  11.    optimizations were made.
  12.  
  13.    Support for "<" and ">" was added by Alexei A. Efros, Jr. Octave
  14.    numberation was also fixed.
  15. }
  16.  
  17. {=======================================================================}
  18.  
  19. interface
  20.  
  21. uses CRT;
  22.  
  23. Procedure Play (TuneString:string);
  24.  
  25.   {Play interprets a string very similar to that used with the PLAY
  26.    verb in BASICA.  The two major exceptions are that the "N" order
  27.    is not interpreted and that variables cannot appear in the string.
  28.  
  29.    The string characters are interpreted as follows:
  30.  
  31.       A .. G    The musical notes A thru G.  A note may be followed
  32.                 by an accidental ('#' or '+' for sharp and '-' for
  33.                 flat.)  Additionally, a note (With optional sharp or
  34.                 flat) may also be followed by a number denoting the
  35.                 note length (1 for a whole note thru 64 for a 64th
  36.                 note.)   The note, with optional accidental and
  37.                 length, may also be followed by one or more dots
  38.                 ("."), each of which extends the note by one half
  39.                 of its existing value.  For example, two dots produce
  40.                 a length of 9/4 the original value, and three dots
  41.                 a length of 27/8 the original value.
  42.  
  43.       Ln        Specifies the default length of the notes following
  44.                 ("n" must be 1 for a whole note thru 64 for a 64th
  45.                 note.)  The initial default value is 4 (quarter note.)
  46.  
  47.       Mz        Specifies the fraction of the note length that the
  48.                 note is actually sounding.  "z" is one of the letters
  49.                 "S", "N", or "L", which have these meanings:
  50.  
  51.                    MS   Music staccato   (3/4 of note length)
  52.                    MN   Music normal     (7/8 of note length)
  53.                    ML   Music legato     (all of note length)
  54.  
  55.       On        Specifies the octave in which the notes following
  56.                 are to be played (0 thru 7).  The initial default
  57.                 octave is 3, which is the octave which begins at
  58.                 middle C.
  59.  
  60.       > and <   Changes the current octave up 1 or down 1.
  61.  
  62.       Pn        Specifies that no sound is to be made for an
  63.                 interval.  "n" (optional) is the note length (1
  64.                 for a whole note thru 64 for a 64th note.)  If "n"
  65.                 is omitted, the current default note length is used.
  66.                 One or more dots may follow, each of which extends
  67.                 the rest by one half of its existing value.
  68.  
  69.       Tn        Specifies the tempo in beats per minute (32 thru
  70.                 255.)  The initial default value is 120.
  71.  
  72.       Note: The playing may be interrupted at any time by pressing
  73.       Control-Break or Control-C.  This terminates the program and
  74.       returns control to the operating system.  If you want to
  75.       change this, the keyboard checking code immediately follows
  76.       the note playing code.
  77.  
  78. }
  79.  
  80. {=======================================================================}
  81.  
  82. implementation
  83.  
  84.  
  85. Const
  86.     SharpOffset = 60;
  87.  
  88. Var
  89.     PitchArray : Array[1..120] Of Integer;
  90.       {The first 56 entries in PitchArray are frequencies for
  91.        the notes A..G in seven octaves.  Entries 60 thru 115
  92.        are frequencies for the sharps of the notes in the
  93.        first 56 entries.}
  94.     BaseOctave : Integer;
  95.     Octave     : Integer;
  96.     GenNoteType: Integer;
  97.     Tempo      : Integer;
  98.     PlayFrac   : Byte;
  99.  
  100.  
  101. {PlayInit sets default values for octave, note length, tempo, and
  102.  note length modifier.  It sets up the array of frequencies for the
  103.  notes.}
  104.  
  105. Procedure PlayInit;
  106.   Const
  107.       NextFreq    = 1.05946309436;
  108.   Var
  109.       RealFreq : Array[1..7] Of Real;
  110.       BaseFreq : Real;
  111.       J,K      : Integer;
  112.   Begin
  113.  
  114.    {Set up default values}
  115.  
  116.     BaseOctave := 1;
  117.     Octave := 3;         {Third octave - starts with middle C}
  118.     GenNoteType := 4;    {Quarter note}
  119.     Tempo := 120;        {120 beats per minute}
  120.     PlayFrac := 7;       {Normal - note plays for 7/8 of time}
  121.  
  122.     {Set up frequency array}
  123.  
  124.     BaseFreq := 27.5;    {"A" four octaves below A-440}
  125.     For J := 0 To 7 Do
  126.       Begin
  127.         RealFreq[1] := BaseFreq;
  128.         RealFreq[2] := RealFreq[1]*NextFreq*NextFreq;
  129.         RealFreq[3] := RealFreq[2]*NextFreq;
  130.         RealFreq[4] := RealFreq[3]*NextFreq*NextFreq;
  131.         RealFreq[5] := RealFreq[4]*NextFreq*NextFreq;
  132.         RealFreq[6] := RealFreq[5]*NextFreq;
  133.         RealFreq[7] := RealFreq[6]*NextFreq*NextFreq;
  134.         BaseFreq := BaseFreq * 2;   {next octave}
  135.         For K := 1 to 7 Do
  136.           Begin
  137.             PitchArray[J*7+K] := Round(RealFreq[K]);
  138.             PitchArray[J*7+K+SharpOffset] := Round(RealFreq[K]*NextFreq);
  139.           End;
  140.       End;
  141.   End;
  142.  
  143.  
  144. {Play interprets the passed string and plays the specified notes for
  145.  the specified time periods.   The orders in the string are interpreted
  146.  as outlined in the interface section above.}
  147.  
  148. Procedure Play (TuneString:string);
  149.   Var PlayTime,IdleTime,DotTime,NoteTime  : Integer;
  150.       NoteType,PitchIndex,Position,Number : Integer;
  151.       Code,TuneStrLen                     : Integer;
  152.       Character                           : Char;
  153.  
  154.   Procedure NVal(Pos:integer; var v, code: integer);
  155.   {Extracts a numeric value "v" from the tune string starting at
  156.    the index Pos.  The returned value in "code" is the number of
  157.    digits scanned plus one.}
  158.      var  posn:integer;
  159.      begin
  160.         v := 0;
  161.         posn := Pos;
  162.         while (posn <= TuneStrLen) and
  163.         (TuneString[posn] in ['0'..'9']) do begin
  164.            v := v*10 + ord(TuneString[posn]) - ord ('0');
  165.            posn := posn + 1;
  166.         end;
  167.         code := posn - Pos + 1;
  168.      end {NVal};
  169.  
  170.   Procedure CheckDots;
  171.   {Checks for dots after note or pause.  Each dot increases note
  172.    or rest length by half.}
  173.     begin
  174.        while (Position <= TuneStrLen) and
  175.        (TuneString[Position] = '.') do begin
  176.           DotTime := DotTime + DotTime div 2;
  177.           inc(Position)
  178.        end;
  179.     end {CheckDots};
  180.  
  181.   Begin {Play subroutine}
  182.     CheckBreak := false;
  183.     TuneStrLen := length(TuneString);
  184.     Position := 1;
  185.  
  186.     Repeat
  187.       NoteType := GenNoteType;
  188.       DotTime := 1000;
  189.  
  190.       Character := upcase(TuneString[Position]);
  191.       Case Character Of
  192.         'A'..'G' : Begin
  193.                      PitchIndex := (ord(Character)-64)+Octave*7;
  194.                      If (Character='A') or (Character='B') Then
  195.                        PitchIndex := PitchIndex + 7;  {next octave}
  196.                      inc(Position);
  197.  
  198.                      {Check for sharp or flat}
  199.                      if Position <= TuneStrLen then
  200.                         case TuneString[Position] of
  201.                           '#','+': begin
  202.                             PitchIndex := PitchIndex+SharpOffset;
  203.                             inc(Position);
  204.                            end;
  205.                           '-': begin
  206.                             PitchIndex := PitchIndex+SharpOffset - 1;
  207.                             inc(Position);
  208.                            end;
  209.                         End;
  210.  
  211.                      {Check for length following note}
  212.                      if (Position <= TuneStrLen) and
  213.                      (TuneString[Position] in ['0'..'9']) then begin
  214.                         NVal(Position,NoteType,Code);
  215.                         inc(Position, Code - 1)
  216.                      end;
  217.  
  218.                      {Check for dots after note}
  219.                      CheckDots;
  220.  
  221.                      {Play the note}
  222.                      NoteTime := Round(DotTime/Tempo/NoteType*240);
  223.                      PlayTime := Round(NoteTime*PlayFrac/8);
  224.                      IdleTime := NoteTime-PlayTime;
  225.                      Sound(PitchArray[PitchIndex]);
  226.                      Delay(PlayTime);
  227.                      if IdleTime <> 0 then begin
  228.                         NoSound;
  229.                         Delay(IdleTime)
  230.                      end;
  231.  
  232.                      {Check for Ctl-Break pressed}
  233.                      if keypressed and (ReadKey = ^C) then begin
  234.                         NoSound;
  235.                         halt
  236.                      end;
  237.  
  238.                    End;
  239.              'L' :  {Note length (1 thru 64).  "1" signifies a
  240.                      whole note and "64" a 64th note.}
  241.                    Begin
  242.                      NVal (Position+1,GenNoteType,Code);
  243.                      if (GenNoteType < 1) or (GenNoteType > 64) then
  244.                         GenNoteType := 4;
  245.                      inc(Position, Code);
  246.                    End;
  247.              'M' :  {Note length modifier - "S" for staccato,
  248.                      "L" for legato, or "N" for normal.}
  249.                    Begin
  250.                      if Position < TuneStrLen then begin
  251.                         Case upcase(TuneString[Position+1]) Of
  252.                           'S' : PlayFrac := 6;
  253.                           'N' : PlayFrac := 7;
  254.                           'L' : PlayFrac := 8;
  255.                         End;
  256.                         inc(Position, 2);
  257.                      end;
  258.                    End;
  259.              '<' : begin
  260.                      Dec(octave);
  261.                      inc(Position);
  262.                    end;
  263.              '>' : begin
  264.                      Inc(octave);
  265.                      inc(position);
  266.                    end;
  267.              'O' :  {Octave specification (0 thru 7)}
  268.                    Begin
  269.                      NVal (Position+1,Octave,Code);
  270.                      Octave := Octave+BaseOctave;
  271.                      if Octave > 7 then Octave := 3;
  272.                      inc(Position, Code);
  273.                    End;
  274.              'P' :  {Pause (rest) followed by optional value of
  275.                      1 thru 64, with "1" signifying a whole rest
  276.                      and "64" a 64th rest.}
  277.                    Begin
  278.                      NoSound;
  279.                      NVal (Position+1,NoteType,Code);
  280.                      if (NoteType < 1) or (NoteType > 64) then
  281.                         NoteType := GenNoteType;
  282.                      inc(Position, Code);
  283.                      CheckDots;
  284.                      IdleTime := DotTime Div Tempo * (240 Div NoteType);
  285.                      Delay (IdleTime);
  286.                    End;
  287.              'T' :  {Tempo - number of beats per minute (32 - 255)}
  288.                    Begin
  289.                      NVal (Position+1,Tempo,Code);
  290.                      if (Tempo < 32) or (Tempo > 255) then
  291.                         Tempo := 120;
  292.                      inc(Position, Code);
  293.                    End;
  294.             Else inc(Position);   {Ignore spurious characters}
  295.       End;
  296.     Until Position > TuneStrLen;
  297.     NoSound;
  298.   End {Play};
  299.  
  300. Begin    {Initialization}
  301.  
  302.   PlayInit;
  303.  
  304. End.