The APL+Win RAD development system contains high level functions (such as WI) to
quickly and easily develop your application interface.But APL+Win also lets you use all of
the 16bit and 32bit Windows API.
Let's use the 32 bit Windows API to write small functions allowing us to play MIDI files from APL+Win.
The 32 bit Windows
API contains several functions which could let you play MIDI files. One of them is called mciSendString
and is found in WINMM.DLL in the Windows 95/98 System directory (look for WINMM16.DLL
under Windows 3.1).
The Windows SDK
documentation tells us that mciSendString accepts 4 arguments:
- the command string
to be sent to the MCI (Media Control Interface) device (called
a sequencer for MIDI files)
- a buffer for return
information
- the size of this
buffer
- a handle to a
window to call back if "notify" was specified in the command string
Since we will not
use the "notify" keyword in our command strings the fourth argument will
always be 0 for us.
We will choose the
second argument to always be a vector of 256 null characters (TCNUL). The
third will therefore always be integer scalar 256.
The first argument
is more interesting: it should contain the command strings we need to send to the MCI
sequencer device in order to play our MIDI file.
Let's assume our
MIDI file is called \APLWIN\RES\MID\BABYBUMB.MID.
The Windows SDK
documentation tells us that the set of mciSendString commands required to play the
file are:
- open
\APLWIN\RES\MID\BABYBUMB.MID type sequencer alias song
- play song wait
- close song
So we can use the APL+Win ŚWCALL
system function to
access the mciSendString Windows API function to play the MIDI file, in the
following way:
’ PlayMidiWait A;B;C
[1] B„(256˝ŚTCNUL)256 0
[2] C„ŚWCALL'mciSendString'('open ',A,' type sequencer alias song'),B
[3] C„ŚWCALL'mciSendString' 'play song wait',B
[4] C„ŚWCALL'mciSendString' 'close song',B
’
The PlayMidiWait
function argument is the MIDI file name to play. Example:
PlayMidiWait'\aplwin\res\mid\babybumb.mid'
Alternatively,
APL+Win offers a NA (Name Association) system function which also lets
you access the Windows API. Using NA, our PlayMidiWait function would read:
’ PlayMidiWait2 A;B;C;mciSendString
[1] B„(256˝Śtcnul) 256 0
[2] C„'DLL I4„WINMM.mciSendStringA(*C1,*C1„,I4,I4)'Śna'mciSendString'
[3] A„mciSendString(›'open ',A,' type sequencer alias song',Śtcnul),B
[4] A„mciSendString(›'play song wait',Śtcnul) , B
[5] A„mciSendString(›'close song',Śtcnul) , B
’
This function
works as well to play any MIDI file:
PlayMidiWait2'\aplwin\res\mid\fugue.mid'
Note that the mciSendString Windows API function returns
0 if it succeeded or an error code in case it failed. You
might want to check the meaning of this integer error code with
the mciGetErrorString Windows API function. This will be
explained in detail in the April 97 issue of the APL+Win
Training Program.
The above examples
work fine, but have one drawback. These PlayMidiWait programs wait until the MIDI file is
entirely played to terminate.
It would be nicer
to return immediately and not block the user.
So we would like
to remove the wait keyword in the play song wait command string.
However if we do
so, the program will start playing the MIDI file and will immediately execute the next
statement (close song) which closes the MCI device and therefore stops the MIDI
playback. That's not exactly what we want!
Therefore we
should remove the close song command string lines in our programs. But the Windows
SDK documentation indicates that we MUST be sure to close the device we have opened.
How do we solve
this problem then?
We can simply use
a high level APL+Win object called a Timer. A Timer is an object which allows you
to schedule regular callbacks to APL.
The mciSendString
function accepts other command strings than the 3 ones we have already seen. One of them
is quite interesting: the status ... mode command. This command returns "playing"
as long as the MCI (sequencer) device is still playing.
We will therefore
be able to tell the APL system to regularly check if the device is still playing and as
soon as it has finished playing, we will disable our timer and automatically run the close
song command.
Here is a more
general PlayMidi function which does not wait until the MIDI file is finished playing:
’ PlayMidi A;B
[1] B„(256˝Śtcnul) 256 0
[2] :select A
[3] :case 'StillPlaying?'
[4] A„†1‡Śwcall'mciSendString' 'status song mode',B
[5] :if ~'playing'(^\A¬Śtcnul)/A
[6] A„Śwcall'mciSendString' 'close song',B
[7] 'timer'Świ'enabled'0
[8] :end
[9] :else
[10] 'timer'Świ'Delete'
[11] A„Śwcall'mciSendString'('open ',A,' type sequencer alias song'),B
[12] A„Śwcall'mciSendString' 'play song',B
[13] A„'timer'Świ'New' 'Timer'('onTimer' 'PlayMidi''StillPlaying?''')
[14] :end
’
The function uses
a :select Control Structure to check the argument. If the argument is a MIDI file
name (i.e. if it is not character string 'StillPlaying?') lines 10 to 13 are
executed.
On line 10
an object called timer is destroyed in case it already existed.
Line 11
opens the MIDI file on the MCI sequencer and aliases it with the word 'song'.
Line 12
starts playing song (alias our MIDI file) and does not wait.
Immediately, on line
13 a Timer object called timer is created, using a default 1000 milliseconds
interval. Whenever the onTimer event happens (i.e. every 1000 milliseconds) the following
APL expression will be automatically executed by the APL+Win system:
Thus PlayMidi will
run again, this time executing lines 4 to 8. On line 4 we check if the MIDI
file is still playing by querying its status mode with the mciSendString 'status
song mode' command.
On line 5
we check if the status result is keyword 'playing'.
If it is not the
case, this means the sequencer has now finished playing the MIDI file and we can safely
close the MCI sequencer device (line 6) and disable our timer (line 7) |