

                    USING THE PLAY STATEMENT IN QUICKBASIC                    

INTRODUCTION

  I need to point out this information is stuff I gathered from the QuickBASIC
  online help and some testing. I have no musical background and had to look
  up terms and things online, for which I cannot vouch for their accuracy (I
  also wouldn't bet that I understood everything properly.) Either way, this
  is what I learned from making PlayMATE, so it should explain why the program
  works the way it does.


PLAY STATEMENT

  PLAY is "a device I/O statement that plays music" following instructions
  provided in a command string. In other words, QuickBASIC uses the PC speaker
  to play some tune. Due to the way PC speaker works only one note can be
  played at any given time (no chords are allowed.)

  There are a few more keywords associated with PLAY:

    PLAY(0)   returns the number of notes currently in the background-music
              queue. It's only useful if you use the "MB" command and if the
              buffer is not so large the program execution is put on the back
              burner.
    PLAY ON   Contrary to what it may seem, PLAY ON/OFF/STOP doesn't actually
    PLAY OFF  do anything with the background-music playback. These are event
    PLAY STOP trapping statements used to execute other portions of your code
              when a certain number of notes remain in the background-music
              queue. In short, instead of having to check PLAY(0) yourself and
              compare the returned value, you may try using ON PLAY(x) to do
              it for you. Like all even-trapping statements in QuickBASIC, it
              is slow, awkward and probably not a good idea to use. The bottom
              line: you can't stop playback music with these statements.

  The PLAY instruction is a state machine. It means that commands used to
  modify the state of PLAY are preserved until they are changed and they don't
  have to be restated with every new execution.

  Before playing a new song, you should always define the tempo, octave and
  length of a note to ensure the music won't be played with the settings of
  the previously played score. PLAY commands strings may contain spaces,
  uppercase or lowercase letters.


TEMPO AND NOTE LENGTH

  Tn - [n] is a value between 32 and 255. Sets the number of quarter notes per
       minute. If this command is omitted, 120 is assumed.

  Ln - [n] is a value between 1 and 64. This command sets the default length
       of a note (L1 is a whole note, L4 is a quarter note, etc.) It applies
       to all notes that do not have a specified length. I.e. "L4 CDEF" and
       "C4D4E4F4" play the exact same way. Using this command can be used to
       reduce the length of the command string when many consecutive notes are
       the same length.

  ML - Music Legato; each note plays full length.
  MN - Music Normal; each note plays 7/8 of length.
  MS - Music Staccato; each note plays 3/4 of length.
       In theory, regardless of the played length (3/4, 7/8 or 1/1,) the time
       elapsed between two consecutive notes remain identical as a silence is
       added to complete the beat:
       "ML CDEFGAB": CCCCCCCCDDDDDDDDEEEEEEEEFFFFFFFFGGGGGGGGAAAAAAAABBBBBBBB
       "MN CDEFGAB": CCCCCCC DDDDDDD EEEEEEE FFFFFFF GGGGGGG AAAAAAA BBBBBBB 
       "MS CDEFGAB": CCCCCC  DDDDDD  EEEEEE  FFFFFF  GGGGGG  AAAAAA  BBBBBB  
       In practice, ML seems to produce slightly longer notes... that, or MN
       and MS (which are synchronized) are producing slightly shorter notes.


OCTAVES, NOTES AND PAUSES

  On - [n] is a value between 0 and 6. Defines the current octave. If omitted,
       the octave is assumed to be 2.
  <  - Down one octave (relative to current octave.)
  >  - Up one octave (relative to current octave.)

  Nn - [n] is a value between 0 and 84. Plays the note at the specified tone.
       If [n] is 0, the note is silent (pause.) Notes played with this command
       ignore the current octave as it is explicitly defined by [n], but they
       will modify the default octave to the next highest octave from that
       note (for instance, N47 is A# on octave 3, thus the default octave is
       set to 4.) Each octave contains 12 tones: C=1, C#=2, D=3, D#=4, E=5,
       F=6, F#=7, G=8, G#=9, A=10, A#=11, B=12. Compute a tone as:
       octave * 12 + note tone. For instance, D# (value: 4) on octave 3 would
       be 3 * 12 + 4 = 40. The length of a note specified with "N" depends on
       the last "L" value.

  Pn - [n] is a value between 1 and 64. Leaves a silence, its duration works
       exactly like notes.

  C  - Plays the given note on the current octave. The note may be followed by
  D    a suffix: "-" for flat (one tone lower,) or "+" or "#" for sharp (one
  E    tone higher.) Actual black keys are: C#, D#, F#, G#, and A#.
  F    After the suffix may appear a number (between 1 and 64) defining the
  G    length of that note. 1 is a whole note, 2 is a half note, 4 is a
  A    quarter, etc. If no length is present, the duration of the note is the
  B    last "L" value. An additional "." may be added to play the previous
       note 3/2 of its length. ";" is also valid but its effect is unknown.


MISCELLANEOUS COMMANDS

  MF - Plays music in foreground.
  MB - Plays music in background.
       When playing music in foreground, your program cannot take inputs and
       won't respond until the music is done playing. When paying music in
       background, your program should be able to take user inputs or do
       whatever background process is currently running, unless the playback
       buffer is too large, in which case notes are played until the buffer is
       small enough to allow your program to resume. The number of maximum
       notes playable in the background seems to be around 8, their length
       doesn't matter and pauses do not count.

  Xn - [n] is the string representation of a variable's address obtained with
       VARPTR$(string-expression). When executed, the PLAY instruction resumes
       playing the music commands found in the string located at that address.


NOTES

  SYNTAX

  PLAY strings are case-insensitive. Spaces are allowed ANYWHERE: "D - 3   2"
  will be read as "D-32" and won't cause any problem. Sharp (+ or #) sigil can
  only appear after C, D, F, G, or A; Flat (-) sigil can only appear after D,
  E, G, A, and B. Neither sigil can be "stacked" (repeated multiple times.) If
  the "." sigil is used, it must appear after a note or a pause; if that note
  or pause has an explicit length, "." must appear after the length.


  TIMING ISSUE

  I encountered a timing issue when coding PlayMATE because I wanted to use
  beats to define the length and offset of notes (and pauses) rather than a
  length divider (fraction of a black -or quarter- note.) This system seems
  obvious to me because the program would be able to properly handle pauses on
  its own by computing the space between two consecutive notes.

  However, because the fraction system uses integers, it was not always
  possible to retrieve the correct length (in beats) of a fraction and
  vice-versa: the longer the note, the more likely a distortion would occur.
  After 13 beats in length, the error would be such that it would always
  return a length of 1/2, which is actually 16 beats.

  To overcome the issue, the code first tests if it can get away by simply
  dividing 32 by the number of beats, and then dividing 32 by that result
  again to see if it would yield the correct amount of beats (if it does, it
  uses that value.) If numBars is not (32 \ (32 \ numBars)), then it would
  truncate the note by whole fractions until the entire length is covered.
  Those "sub notes" are stitched together with the "ML" command (plays full
  length of the note,) and the last sub note is preceded by the "MN" command
  (plays 7/8 of the note.)

  The same routine handles notes and pauses alike and allows lengths to be
  over 32 beats. The obvious downside is the increased length of the PLAY
  command string. While the result is "good enough," desynchronization still
  happens due to the mixing ofr ML, MN and MS notes.


  STRING COMPRESSION

  PLAY command strings can be compressed by 30% on average with a little
  creativity. If possible, replace absolute octave change (two characters) by
  relative octave change (one character.) If at least two notes have the same
  length, it's better to add a default note length with "L" and leave the
  length of those notes unspecified; it's a good deal because this technique
  guarantees a gain that is either null or positive: "A8C8" could be replaced
  by "L8AC" (same number of characters,) and "G16A16" by "L16GA" (and we save
  one character.) Note that pauses ("P") need to have their length specified,
  unless it is replaced by "N0" (silent tone,) in which cases its duration is
  the last length defined by "L". Here's a comparission chart for tracks
  converted to PLAY commands strings with PlayMATE, before and after the new
  compression scheme:

                  SIZE IN BYTES    SAVED BYTES
                  Before  After    Diff. Prct. 
   SKATERS.SPK       566    338      158   40%
     ELIZE.SPK       322    164      228   49%
    CANCAN.SPK     2,027  1,602      425   21%
     MERRY.SPK       283    220       63   22%
