Making Noise with MMPM/2 - Part 2

by Marc van Woerkom

Introduction
This is the second article on MMPM/2 programming. It was originally planned to be the final one, but two reasons forced me to extend this series:
 * 1) Semir Patel's fine article in volume 2, issue 1 of EDM/2 already touched most topics I intended to present here, blasting my concept. (Watch out for my upcoming DOOM-style game, featuring a certain editor)
 * 2) There is so much to cover about MMPM/2 that two parts are simply not enough.

Limitations of Using REXX
When I delved further into MMPM/2 programming it became clear to me that the features accessible from the REXX Media Control Interface (MCI) are very powerful but that there is still more to see, much like the part of an iceberg lying under water. After all, let's face the music (pun intended); MMPM/2 is written in C meaning all of the APIs will be accessible from C. The only APIs provided for REXX are: Compare this with the near 200 MMPM/2 API calls accessible through C/C++.
 * mciSendString (mciRxSendString)
 * mciGetErrorString (mciRxGetErrorString)
 * mciGetDeviceID (mciRxGetDeviceID)

To be fair, it should be possible to close the gaps since VX-REXX 2.0 from Watcom seems to have multimedia support. However, one must stick to C/C++ if one wants... This doesn't even include esoteric business like driver development.
 * to get complex feedback (be it a whole structure of information, or synchronization messages)
 * to use the basic Multimedia I/O subsystem (MMIO API)
 * to control streaming and synchronization through the SPI subsystem
 * to use the PM extensions provided by MMPM/2 (i.e. new graphical controls and secondary window support)

After all of this bashing on poor REXX, let's close this section with the biggest advantage of REXX:

It's pretty easy to use and provides a great introduction!

Please read the first part of this series for more information on this topic.

Preliminary Thoughts
Before you start MMPM/2 programming, you need a C or C++ compiler and MMPM/2 bindings. I recommend using either: Although this should suffice to recompile the examples given, and you'll get a lot of information from here, you still should acquire some of the literature mentioned at the end of the first part of this series and at the end of this part.
 * one of the IBM compilers (C Set++, Firststep) together with the Developer's Toolkit and the MMPM/2 Toolkit (which has been included in the Developer's Toolkit since the 2.1 release)
 * or, the EMX port of the GNU GCC C/C++/Objective C compiler (revisions 0.8h and 2.5.8 or later) together with the MM4EMX package (revision 1.0).

Another great source of information are the computer networks. Often you can contact someone on the Internet news groups or CompuServe fora who can help you or share thoughts with you.

For those with Usenet access I recommend comp.os.os2.multimedia, or (if you somehow feel an urge to polish your German a little bit :-) maus.os2.prog.

So much for the basic preparations. (Hmm...did I already tell you to install MMPM/2?)

Multimedia Programming Using EMX
To state it shortly - it's no problem to do any MMPM/2 programming with EMX.

As an EMX user you have the choice between using the OS/2 and MMPM/2 headers and libraries from the IBM Toolkits or using those of the EMX and MM4EMX packages. Both have their strengths and weaknesses. However, this article series should feature nothing which is specific to one of the above combinations, or even the EMX system.

The MM4EMX Package
The Multimedia for EMX package (MM4EMX) is a freeware package for the EMX development environment. It contains all necessary 32-bit header files and import libraries in the BSD .A and Intel OMF .LIB formats together with source code.

The samples presented and explained in this part of the article series are the ones from MM4EMX (release 1.0). As a side note, please contact the author if you should note incompatibilities.

The Textual Media Control Interface
The easiest way to use the MCI is via its textual interface, using the mciSendString API call. Below is an example of this. It opens the Sequencer (MIDI) multimedia device, giving it the alias "midi2". The product info is queried and mmtime is chosen as time format. Then a MIDI file from Bach is loaded into the context of the device, its length is queried and it gets played. Finally the device is closed.

The following example will take a file of MCI command strings and sends them line by line for execution to the MDM via mciSendString, thus interpreting the file. One has to define INCL_OS2MM to include the proper definitions and declarations, i.e. the 32-bit version of the MMPM/2 API with naming conventions conforming to OS/2.

A defined INCL_MCIOS2 will include the MCI support. Now we finally got a non-comment line into the variable buff.

The next thing to do is to provide a return buffer for mciSendString, which has to be empty. The return code wasn't MCIERR_SUCCESS, so something strange happened. Because an error code is not too enlightening, we use mciGetErrorString to get a nice string. In case the rc is out of range we give up and display the number. All of this is pretty straight forward. Now we have a tool to try out MCI command strings quick and easy. Below are some other MCI scripts: This example shows how the audio CD player applet recognizes CDs, it checks for the 8 byte long ID:

The Procedural Media Control Interface
While the ease of the textual MCI is unbeatable, it has some weaknesses in comparison to the procedural MCI. The following example uses the procedural interface to query an audio CD for it's table of contents. This function prints out a time given in MMTIME format. It employs the helper macros ULONG_LWLB, ULONG_LWHB and ULONG_HWLB to get the information out of the ULONG. Each MCI command has its specific parameter structure. All unused fields should be set to 0.
 * It has to be translated (parsed) internally which needs time.
 * Anything that goes beyond a text string can't be returned as a result.

The device to work with (CDaudio) is identified via its name. The first parameter of mciSendCommand is the ID of the device. In this special case it's not necessary - the ID is returned to a field of the parameter structure.

Next is a message specifying the command, MCI_OPEN in this case.

The third parameter contains some so-called message flags. Here they tell the MDM to wait until the open action is completed and to open it in shared mode.

Then a pointer to the parameter structure is given.

The last parameter is usually 0. Now we issue a GETTOC command. It's not accessible from the textual MCI, because it returns a bunch of information that doesn't fit into a simple line of text. (Let's hope there is no CD with more than 99 tracks.) Note that this time (like in most cases) the device to work with is specified via the ID obtained from MCI_OPEN MCI_CLOSE doesn't have any special parameters, so the parameter structure is of the type MCI_GENERIC_PARMS: OK, MCI_GETTOC was successful, so print out the obtained toc entries: Now let's try CDTOC.EXE on a certain audio CD with "Gorgeous Gals", "Transylvanian Parties" etc. :-) Yup, this is the table of contents of the Rocky Horror Picture Show soundtrack! I chose this CD because track 11 has 3 subtracks (2.46, 3.34, 1.53). However they don't show up here, so I have to guess further what the Control field is good for ("track control field").

Querying MMPM/2 System Values
Remember PM's WinQuerySysValue API call? MMPM/2 has a counterpart named mciQuerySysValue. But in contrast to PM there are only less than a dozen MMPM/2 system values defined, which the following example displays. Running MMPMVALS.EXE on my system yields:

A First Rendezvous with the MMIO Subsystem
The MMIO subsystem is responsible for I/O on multimedia files. It comes with several I/O procedures installed, which can handle specific kinds of data. The example below will show the installed MMIO procedures. Wrap MMFORMATINFO into a C++ class. This constructor clears the MMFORMATINFO structure and initializes the fccIOProc field with the proper four character code specific to the MMIO procedure and the format it handles. Use 0 as default argument. The mmioStringToFOURCC API call translates a string into a four character code. mask = mmioStringToFOURCC(argv[1], MMIO_TOUPPER); A FOURCC is a 32-bit variable, containing 4 characters. mmioQueryFormatCount returns the number of installed MMIO procedures. Get all format information via mmioGetFormats. Extract the name of the format via mmioGetFormatName. So let's run mmiofmts.exe to display the currently installed MMIO procedures: Save this utility for the next part of this article series when we go to install our own MMIO procedure!

Memory Playlists
Using an MCI script, playing several soundfiles is not smooth, for the waveaudio device must load each soundfile (element) into its context, delaying the play. A possible work-around is to load the elements into memory before playing starts. This can be achieved using the MMIO waveaudio I/O procedure and the MMPM/2 waveaudio playlist processor.

Loading the waveaudio file via the MMIO subsystem into memory has the advantage that one can extract the necessary data without knowing much about the maybe complicated internal structure of such a file. The .WAV format is a special kind of the more general RIFF (Resource Interchange File Format) multimedia format. For example, the M1.WAV file is 26504 bytes long, but contains only 26460 bytes of pure sound data. Using MMIO we don't have to worry where it is located in the file.

The usual encoding scheme for sound data is pulse code modulation (PCM). This means at a fixed rate per second (the sampling frequency) the amplitude of the signal of each channel is converted by an audio to digital converter into a number. The M1.WAV file was sampled in mono with 11kHz and 8 bit resolution, so it contains 26460 bytes/(11000 bytes/s) = 2.4s worth of audio data.

Note: once loaded into memory, one can easily work with this data in ways such as applying a compression scheme to it, mixing in a second waveaudio file (take the mean of both amplitude values) if you want to play more than one sound at once, adding effects such as echoing (rescale the amplitudes and mix this data with a short delay over the original data), etc.

A playlist is an array of playlist instructions, which is processed by the playlist processor. Among those instructions is not only the command to play a waveaudio file residing in memory, but also flow control instructions which allow jumps, loops and subroutines.

Look at this example, which employs a playlist to play a certain rhythm: The MMAUDIOHEADER structure will get the header information of the waveaudio file from the MMIO. The constructor of the mem_wav class will load a waveaudio file (given by its filename) into memory via the MMIO subsystem. The MMIO subsystem looks at the .WAV extension and calls the proper I/O procedure to open it, delivering a handle as the result. Now get the header information of the waveaudio file. The header contains the length in bytes (needed to allocate the buffer memory), the sampling frequency and the sampling resolution (these settings are needed for a proper reproduction). The buffer is allocated, now read all information into it. Finally close the file. That's all! (And we didn't need to know anything about RIFF chunks, etc.) The destructor of the class gets rid of the allocated memory resources. Now comes the playlist. A playlist entry is composed of four ULONG variables. The first represents the instruction and the others are its possible arguments. This class represents a playlist. It should be stated that it is tailored for this special example. (For it reads exactly 7 different samples and the playlist is hardcoded into the setup member function. A general class of this kind should be able to deal with a variable amount of samples and should read the playlist from a data file or a resource block.) Allocate the playlist entries. This member function will fill a playlist entry with the proper values. Note the default arguments. And it returns the number of the current entry which will come in handy when employed in setup. A branch operation (jump to a specific entry). A call operation (call a playlist subroutine). A data operation (play a waveaudio file from a buffer). An exit operation (end the playlist processing). A return operation (return from a playlist subroutine). This is a hardwired playlist. Note that is ordered in a way that only one forward reference (70) is needed. void playlist::setup { Jump to the 70th playlist entry. add_branch(70); This is one of several subroutines. It plays the buffer containing the audio data of M1.WAV thrice. Well, I didn't count until here. I simply printed out the return code of the next call in a prior version of this source and noted it. This class represents the waveaudio device together with an associated playlist. The characteristics of the mem_wav given to the constructor are used for the processing of the whole playlist. Open the waveaudio device via an MCI command message in a way that it will use a playlist as data. If these values aren't set via MCI_SET, the waveaudio data will be played with improper speed or even be garbled. Close the waveaudio device. Set the waveaudio device on 'play'. Routines to print pretty error messages. Main function. Perhaps you should start reading this example from here. Try it!

Note that this example is not too far away from the .MOD file playing mechanism. The hardest thing for an extension in this direction is probably getting the proper .MOD file definition. Since there was an unconfirmed report on Usenet that IBM may deliver a .MOD MMIO procedure in the next MMPM/2 release, I personally won't put time into something like that.

What's Next?
At least two important topics are still on my list:
 * Expect to see more on the MMIO subsystem. Procedures for handling compressed waveaudiofiles are under development.
 * The PM extensions (graphical buttons, circular sliders and the secondary windows support) cry for some nice examples.

More Literature on MMPM/2
IBM Doc. S53G-2166: OS/2 Online Book Collection CD-ROM. This CD-ROM contains 144 different OS/2 manuals in *.boo format and readers for OS/2 and DOS. (Highly recommended!)