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.
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. 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" 40 PRINT "Buffer no longer filled"
Since Microsoft BASIC halts when the PLAY buffer is filled, you must either use very short tunes, or slowly stream commands into the buffer as your program runs. Streaming the buffer is made possible with the PLAY(n) or ON PLAY statements.
The BEEP statement simply plays an 800 Hz sound for one-quarter of a second. It's syntax does not accept arguments:
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
- BEEP can be duplicated with the statement SOUND 800, 4.55 or PRINT CHR$(7).
- BEEP is not affected by background audio mode.
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.
- 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:
10 DIM MUSIC$(4) 20 MUSIC$(1) = "E8 E8 F8 G8 G8 F8 E8 D8 C8 C8 E8 E8 E8 D12 D4" 30 MUSIC$(2) = "E8 E8 F8 C8 G8 F8 E8 D8 C8 C8 D8 E8 D8 C12 C4" 40 MUSIC$(3) = "D8 D8 E8 C8 D8 E12 F12 E8 C8 D8 E12 F12 E8 D8 C8 D8 P8" 50 MUSIC$(4) = "E8 E8 F8 G8 G8 F8 E8 D8 C8 C8 D8 E8 D8 C12 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
- 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.
The MML of Microsoft BASIC is primarily implemented with the PLAY statement. Here is the syntax:
- string is a character string containing commands of Microsoft BASIC MML.
10 PLAY "O2 T120 E8 E8 F8 G8 G8 F8 E8 D8 C8 C8 E8 E8 E8 D12 D4" 20 PLAY "E8 E8 F8 C8 G8 F8 E8 D8 C8 C8 D8 E8 D8 C12 C4" 30 PLAY "D8 D8 E8 C8 D8 E12 F12 E8 C8 D8 E12 F12 E8 D8 C8 D8 P8" 40 PLAY "E8 E8 F8 G8 G8 F8 E8 D8 C8 C8 D8 E8 D8 C12 C4"
Here is a complete list of the MML commands accepted by the PLAY statement.
|A-G[n]||A, B, C, D, E, F, and G|| Plays the corresponding note.|
The octave is set by O, default 2. The length of the note is set by L, default 1 (a whole note). The tempo is set by T, default 120. 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||Music 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||Music 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. PLAY(n) will return the number of commands in the buffer, and ON PLAY will fire events in this mode.|
|MN||Music Normal||Plays music in normal style. Each note plays for seven-eights of a time set using L. This is the default.|
|ML||Music Legato||Plays music in legato style. Each note plays for the full period set by L.|
|MS||Music 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 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.|
|-, #, +||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.|
|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$;"
- 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. These note/tempo combinations must be determined through experimentation.
- 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$
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.
- n is a dummy variable and should be set to 0.
This function is useful because it allows you to move 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 E8 E8 E8 D12 D4" 30 MUSIC$(2) = "E8 E8 F8 C8 G8 F8 E8 D8 C8 C8 D8 E8 D8 C12 C4" 40 MUSIC$(3) = "D8 D8 E8 C8 D8 E12 F12 E8 C8 D8 E12 F12 E8 D8 C8 D8 P8" 50 MUSIC$(4) = "E8 E8 F8 G8 G8 F8 E8 D8 C8 C8 D8 E8 D8 C12 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
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.
- duration must be a number from 0 to 65535 and is the length of time the tone will play in clock ticks, which occur 18.2 times per second.
Both frequency and duration allow decimal values. 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
- A duration below 0.022 will play continually until the next SOUND or PLAY statement is processed.
- Setting the duration to 0 will silence any previous SOUND statement.
Games That Use Microsoft BASIC MML
- divonasperi.it/divona/tam/tecnologia/dow-all/GW%20Basic%20(inglese).pdf - GW-BASIC manual PDF.