Microsoft BASIC MML
Microsoft BASIC MML is a Music Macro Language created by Microsoft for their BASIC programming languages. The MML supports the ability to play music and sound through the PC Speaker by entering single-character commands, usually followed by a number, to represent notes as well as various other musical functions. Microsoft included BASIC languages with most versions of their operating systems, MS-DOS and Windows 95 and 98. DOS versions before 5.0 came with GW-BASIC, and later versions came with QuickBASIC. Both GW-BASIC and QuickBASIC use the same MML.
Microsoft BASIC has three statements that can produce sound: BEEP, PLAY, and SOUND. PLAY is the most complex of these statements and exclusively uses the Microsoft BASIC MML. Games programmed in a Microsoft BASIC language most likely used these statements for their sound.
Contents
Modes
Microsoft BASIC supports two audio modes, foreground and background. When audio is set to foreground mode, each PLAY and SOUND statement is processed completely before the next BASIC statement is processed. When audio is set to background mode, PLAY and SOUND statements are put into the PLAY buffer and played in the background while additional BASIC statements are processed. You can see this at work in the following example:
10 INPUT "Press ENTER to see Foreground Mode:", A$ 20 PLAY "MF C D E F G A B" 30 PRINT "Done" 40 INPUT "Press ENTER to see Background Mode:", A$ 50 PLAY "MB C D E F G A B" 60 PRINT "Done"
However, the PLAY buffer only holds 32 commands (38 in QuickBASIC 4.x). If a PLAY statement exceeds this buffer, BASIC halts further execution until only 32 commands are left and those are then put in the PLAY buffer and processed in the background. You can see this result in the following example:
10 PLAY "MB L8 O0 C D E F G A B O1 C D E F G A B" 20 PLAY "O2 C D E F G A B O3 C D E F G A B" 30 PLAY "O4 C D E F G A B O5 C D E F G A B" 40 PLAY "O6 C D E F G A B" 50 PRINT "Buffer no longer filled"
Since Microsoft BASIC halts when the PLAY buffer is filled, you must use very short tunes. However, longer background music was possible by feeding music to the buffer through events by using the PLAY(n) or ON PLAY statements.
Statements
BEEP
The BEEP statement simply plays an 800 Hz sound for one-quarter of a second. Its syntax does not accept arguments:
BEEP
BEEP is usually used to give audio feedback to the user. Here is an example:
10 INPUT "Enter a number greater than 10: ", N 20 IF N > 10 THEN GOTO 60 30 PRINT "WRONG!" 40 BEEP 50 END 60 PRINT "CORRECT!" 70 END
Notes:
- In BASICA, BEEP is the same as
SOUND 800, 4
and affected by background mode. - In GW-BASIC, BEEP is the same as
SOUND 800, 4.55
, temporarily forced into foreground mode. - In QuickBASIC, BEEP waits till the buffer is processed and is then the same as
PRINT CHR$(7);
, itself not affected by background mode.
ON PLAY(n)
ON PLAY creates an event trap which sends the execution pointer to the specified subroutine when the number of commands in background buffer goes one below n. This is similar to PLAY(n), however PLAY(n) is preferred in most implementations because it is easier to use and debug and runs faster in most instances. ON PLAY, being event driven, is less likely to encounter interruptions in music when a program is running particularly slow, but such slowdowns shouldn't exist in properly optimized code. ON PLAY only works in background mode. Here is the syntax:
ON PLAY(n) GOSUB linenumber PLAY action
- n can be a number between 1 and 32 (even in QuickBASIC 4.x, despite the buffer size being 38).
- linenumber must be a line number in the program or 0.
Once an ON PLAY statement is processed, you can activate the event with PLAY action. action must be the word "ON," "OFF," or "STOP." When the action is set ON, BASIC will check the number of background PLAY commands after every BASIC statement is processed, and, if the number is one less than the number specified, process the GOSUB. When the action is set OFF, BASIC will stop checking. When the action is set to STOP, BASIC will continue to check the PLAY buffer after every statement, but it will not process the GOSUB when the number of PLAY commands goes one below n. However, BASIC will remember that it has gone below, and the next time you set PLAY to ON, it will process the GOSUB immediately. This example will play Ode to Joy in the background while still allowing user input (press ESC to quit):
10 DIM MUSIC$(4) 20 MUSIC$(1) = "E8 E8 F8 G8 G8 F8 E8 D8 C8 C8 D8 E8 E8. D16 D4" 30 MUSIC$(2) = "E8 E8 F8 G8 G8 F8 E8 D8 C8 C8 D8 E8 D8. C16 C4" 40 MUSIC$(3) = "D8 D8 E8 C8 D8 E16 F16 E8 C8 D8 E16 F16 E8 D8 C8 D8 O1 G4 O2" 50 MUSIC$(4) = "E8 E8 F8 G8 G8 F8 E8 D8 C8 C8 D8 E8 D8. C16 C4" 60 MUSICPART = 0 70 PLAY "MB O2 T120" 80 ON PLAY(1) GOSUB 1000 90 PLAY ON 100 GOSUB 1000 500 K$ = INKEY$ 510 IF LEN(K$) > 0 THEN PRINT K$; 520 IF K$ = CHR$(27) THEN END 530 GOTO 500 1000 MUSICPART = MUSICPART + 1 1010 IF MUSICPART > 4 THEN MUSICPART = 1 1020 PLAY MUSIC$(MUSICPART) 1030 RETURN 500
Notes:
- When ON PLAY processes its GOSUB, it also sets the PLAY to STOP to prevent recursive traps. When the next RETURN statement is processed, PLAY is automatically set back to ON unless you explicitly set it to OFF in the subroutine.
- If an ON ERROR event is processed, all other events, including ON PLAY, are set to OFF.
- Setting the GOSUB linenumber to 0 is the same as a PLAY OFF statement.
- Since ON PLAY is usually set before the main loop, it is common to end the subroutine with RETURN n, where n is a line number inside the main loop so as to not reset the program.
- In QuickBASIC, the linenumber argument may be a line label instead.
PLAY
The MML of Microsoft BASIC is primarily implemented with the PLAY statement. Here is the syntax:
PLAY string
- string is a character string containing commands of Microsoft BASIC MML.
For example, the following code will play Ode to Joy on the PC Speaker.
10 PLAY "O2 T120 E8 E8 F8 G8 G8 F8 E8 D8 C8 C8 D8 E8 E8. D16 D4" 20 PLAY "E8 E8 F8 G8 G8 F8 E8 D8 C8 C8 D8 E8 D8. C16 C4" 30 PLAY "D8 D8 E8 C8 D8 E16 F16 E8 C8 D8 E16 F16 E8 D8 C8 D8 O1 G4 O2" 40 PLAY "E8 E8 F8 G8 G8 F8 E8 D8 C8 C8 D8 E8 D8. C16 C4"
Here is a complete list of the MML commands accepted by the PLAY statement.
Command | Mnemonic For | Description |
---|---|---|
A–G[n] | A, B, C, D, E, F, and G | Plays the corresponding note. The octave is set by O, default 4. The length of the note is set by L, default 4 (a quarter note). The tempo is set by T, default 120 BPM. Each note may be followed by n, a number from 1–64, indicating the duration of the note; 1 is a whole note, 2 is a half, 4 is a quarter, 8 is an 8th, and so on. Irregular note lengths like 27 are allowed. When n is present, the length set by L is ignored. |
Ln | Length | Sets the length of each note equal to n for those notes without an optional duration. 1 is a whole note, 2 is a half, 4 is a quarter, etc., up to 64. The default length is 4. This is useful for simplifying a PLAY command. For example the two following lines will produce identical sounds, but the first is less code:
10 PLAY "L16 C D E F G A B" 20 PLAY "C16 D16 E16 F16 G16 A16 B16" |
MF | Meta Foreground | Sets audio into foreground mode. PLAY and SOUND statements must be processed completely before BASIC will process the next statement. This is the default mode. PLAY(n) will always return 0 in this mode, and ON PLAY will not fire events in this mode. |
MB | Meta Background | Sets audio into background mode. PLAY and SOUND commands will send their commands to the PLAY buffer which can hold up to 32 commands (38 in QuickBASIC 4.x). PLAY(n) will return the number of commands in the buffer, and ON PLAY will fire events in this mode. |
MN | Meta Normal | Plays music in normal style. Each note plays for seven-eights of a time set using L. This is the default. |
ML | Meta Legato | Plays music in legato style. Each note plays for the full period set by L. |
MS | Meta Staccato | Plays music in staccato style. Each note plays for three-quarters of the time set using L. To hear the difference between the three run this example: 10 PLAY "L8" 20 PRINT "Normal " : PLAY "MN C D E F G A B" 30 PRINT "Legato " : PLAY "ML C D E F G A B" 40 PRINT "Staccato" : PLAY "MS C D E F G A B" |
Nn | Note | Plays note n, Play note n where n is a number from 0 to 84. In the 7 possible octaves, there are 84 notes. If you use 0, it indicates a rest. Just using N, you can play all of the notes you could play using A–G and O, however, most musically inclined readers prefer to read the notes. |
On | Octave | Changes the octave to n where n is a number 0 to 6, to indicate the 7 octaves. The default is 4. Middle C is at the beginning of octave 3. This example will play C across each octave:
10 PLAY "O0 C O1 C O2 C O3 C O4 C O5 C O6 C" |
Pn | Pause | Pauses (rests) for the length of n. 1 is a whole note, 2 is a half note, 4 is a quarter note, etc., up to 64. |
Tn | Tempo | Sets the tempo to n beats per minute. n must be 32–255 and represents the number of quarter notes in a minute. The default is 120. However, in GW-BASIC and QuickBASIC, the result is 3% slower, so the range and default are effectively 31–248 and 117 BPM. |
-, #, + | Flat, Sharp | - (minus) plays the preceding note flat, # (pound) or + (plus) plays the preceding note sharp. These can only be added to notes that would be a black key on a piano. For example:
10 PLAY "A#8 D-" |
. | Dot | Placing a . (period) after a note increases its play time by 3/2 times the period set by L (length) times T (tempo). You can have multiple dots. A single dot will play the note at one and a half times its normal time, two dots will play at 9/4 times the note's usual time, three dots plays at 27/8 times, and so forth. For example:
10 PLAY "E. F.. G..." You can also append a period to P to increase the length of a rest. |
> | Greater Octave | > (greater-than) preceding a note will play the note at the next higher octave. For example:
10 PLAY "C< D E> F G< A B" |
< | Lower Octave | > (less-than) preceding a note will play the note at the next lower octave.
Both commands were added in GW-BASIC 2.0. In BASICA and GW-BASIC 1.0, they will abort the program. |
Xstring; | Execute | Executes a sub-string within the current string. The string is a variable assigned to a string of additional PLAY commands. For example:
10 MORE$ = "C16 D16 E16 F16" 20 PLAY "A8 B16 XMORE$; G4 A8 XMORE$; B4 A8 XMORE$;" The above does not work under QuickBASIC (for technical reasons) and QBASIC (likely for compatibility). However, on every language, you can write: 10 MORE$ = "C16 D16 E16 F16" 20 PLAY "A8 B16 X" + VARPTR$(MORE$) + " G4 A8 X" + VARPTR$(MORE$) + " B4 A8 X" + VARPTR$(MORE$) |
Notes:
- Because of the slow clock interrupt rate in early PCs, some notes do not play at higher tempos; for example PLAY "T255 A64" may not be heard. Each computer has a different upper limit, but, as long as you play music at typical tempos, you'll probably never encounter it, even on early PCs.
- A PLAY statement will interrupt a SOUND statement with a duration set below 0.022.
- Since PLAY accepts any string, you can set a variable to a list of commands and send the variable as the argument instead of a constant character string. For example:
10 A$ = "L32 C D E F G A B" 20 B$ = "L32 B A G F E D C" 30 PLAY A$ 40 PLAY B$ 50 PLAY A$ + B$
PLAY(n)
The PLAY(n) function will return the number of commands in the PLAY background music buffer when music is set to background mode due to a PLAY "MB" statement. The syntax is:
result = PLAY(n)
- result is the number of commands left in the PLAY buffer, it can be from 0 to 32 (or 38 in QuickBASIC 4.x).
- n is a dummy variable and should be set to 0.
This function is useful because it allows the program to move on to a new set of PLAY commands. For example, this program will play Ode to Joy on a loop in the background, while allowing the user to keep interacting with the program.
10 DIM MUSIC$(4) 20 MUSIC$(1) = "E8 E8 F8 G8 G8 F8 E8 D8 C8 C8 D8 E8 E8. D16 D4" 30 MUSIC$(2) = "E8 E8 F8 G8 G8 F8 E8 D8 C8 C8 D8 E8 D8. C16 C4" 40 MUSIC$(3) = "D8 D8 E8 C8 D8 E16 F16 E8 C8 D8 E16 F16 E8 D8 C8 D8 O1 G4 O2" 50 MUSIC$(4) = "E8 E8 F8 G8 G8 F8 E8 D8 C8 C8 D8 E8 D8. C16 C4" 60 PLAY "MB O2 T120" 70 MUSICPART = 0 500 K$ = INKEY$ 510 IF LEN(K$) > 0 THEN PRINT K$; 520 IF PLAY(0) < 4 THEN GOSUB 1000 530 IF K$ = CHR$(27) THEN END 540 GOTO 500 1000 MUSICPART = MUSICPART + 1 1010 IF MUSICPART > 4 THEN MUSICPART = 1 1020 PLAY MUSIC$(MUSICPART) 1030 RETURN
Notes:
- PLAY(n) gives essentially the same functionality as ON PLAY(n), but you don't have to bother with the problems that can arise using GOSUB and RETURN. Because of this, it is generally the preferred method of managing background music.
SOUND
SOUND plays a tone at the frequency and duration specified. The command's syntax is as follows:
SOUND frequency, duration
- frequency must be a number from 37 to 32767, and is the frequency of the generated tone in hertz. Decimal values are rounded to next integer. In GW-BASIC and QuickBASIC, 0 stands for silence.
- duration has several functions that vary by language:
Duration | BASICA | GW-BASIC | QuickBASIC |
---|---|---|---|
0 | Any running tone and PLAY statement stops. Frequency argument is ignored. | ||
> 0 | Tone plays continually until next SOUND, PLAY or BEEP. | Tone plays continually until next SOUND, PLAY or BEEP. | Statement is ignored. |
>= 0.016 | Tone plays for duration divided by 18.2 (dictated by hardware). | ||
>= 0.023 | Tone plays for duration divided by 17.7 (contrary to manual). | ||
>= 1986 | Tone plays for 1:52. | ||
>= 2048 | Tone plays for 1:52. | ||
> 65535 | Program aborts. |
For example, this code will play a tone at an increasing frequency:
10 FOR I = 40 to 3000 STEP 20 20 SOUND I, 1 30 NEXT I
Games That Use Microsoft BASIC MML
- Adventure Math (DOS)
- Adventure Math Deluxe (DOS)
- Castle Adventure (DOS)
- Donkey (DOS)
- Hurkle Hunt (DOS)
- Magic Maze (DOS)
- The Oregon Trail (DOS)
- QBasic Gorillas (DOS)
- QBasic Nibbles (DOS)
- Robots from Hell (DOS)
- SimCity (W16)
- Telemate (DOS)
- Tropfen (DOS)
Links
- github.com/microsoft/GW-BASIC/blob/master/GWSTS.ASM#L538 - Source code of GW-BASIC 1.0 (official but incomplete release).
- antonis.de/qbebooks/gwbasman/index.html - GW-BASIC manual.