Making Noise with MMPM/2 - Part 1
Part 1 | Part 2 |
---|
Contents
Introduction
Beginning with version 2.1, IBM has delivered two new disks with OS/2 containing the previously separately sold Multimedia Presentation Manager/2 1.1 (MMPM/2). Having once visited a colleague who used a Mac and being enamoured with the characteristic emminating from the system speaker after he dropped some superfluous files on a toilet icon, I installed it to try out the system sounds on my SoundBlaster-equipped machine. Although it is quite satisfying in its own right that our beloved operating system is now able to cope with the Mac in this particular area, one should take the little extra time to have a closer look on the MMPM/2 for it has more to offer.
This is the first of two articles which do so. It gives an overview of MMPM/2 and introduces programming for multimedia, using the REXX language which is sufficient for many cases. The second article will deal with MMPM/2 programming in C and C++. Both articles focus on the audio capabilities of MMPM/2.
MMPM/2 Overview
Like its name suggests, the Multimedia Presentation Manager adds multimedia capabilities to IBM's OS/2 2.1 operating system, but what does this actually mean? For me, the term multimedia has acquired a slightly negative connotation in recent months because it is used so extensively by computer hardware vendors to hawk their wares. They take a PC, put a soundboard and a CD-ROM drive into it, drape it with two tiny speakers and voila(!) it's perfect multimedia! The hardware is sold but the purpose is still rather vague.
In the publication "OS/2 2.1 Technical Update" (GG24-3948-00) IBM writes:
The key benefit of multimedia is an enhanced quality of information and communication. When audio, image, and video are combined with text and graphics, customers can access richer forms of information, and communication becomes more effective.
This is not a bad definition. The variety of the types of data computers are asked to crunch has become richer over the years, starting with numbers and text, followed by graphics, and now also images (be it photos or live motion video) and audio data. The need for the capability to combine these different kinds of data is also crucial.
IBM writes further:
MMPM/2 enables this increase in productivity by providing device control, streaming, synchronization, and multimedia object I/O support.
That is a brief summary of MMPM/2. The package consists of:
- Physical Device Drivers (PDD)
- A collection of drivers for multimedia hardware like soundcards, CD-ROM drives, video digitizers, video disc-players, MIDI devices, etc
- Media Control Interface (MCI)
- The MCI is a programming interface which allows the easy use of the media devices, handling them all in a common way via command strings like open, close, play, record, stop, seek, etc.
- Synchronization & Streaming Programming Interface (SPI)
- The SPI multimedia subsystem is responsible for the smooth execution of multimedia applications under OS/2. It ensures that those tasks get the needed processor time and synchronizes different streams of multimedia data.
- Multimedia I/O Services
- The MMIO subsystem extends the OS/2 file services. It provides a standard mechanism for accessing and manipulating multimedia data files (media elements). Supported are buffered I/O, RIFF file I/O, memory file I/O, compound file I/O; it is also possible to add additional I/O procedures to the MMIO subsystem
- PM Extensions
- MMPM/2 provides some new Presentation Manager window classes tailored for the control of multimedia applications (graphical button, circular slider, etc.)
Also several multimedia utilities are delivered with MMPM/2, like a CD audio player, a waveaudio player and editor, software video players and more.
This first article only deals with the MCI because it is programmable from REXX in a very easy way and gives a nice impression of the MMPM/2 architecture. The other features will be treated in the second article.
The Media Control Interface
If you have installed MMPM/2 you'll find on your harddisk an online manual about using the MCI from a REXX programmer's point of view
Note: this manual has many gaps! A more complete description can be found in the MMPM/2 Programming Reference of the OS/2 Technical Library (available in online form on the bookware CD-ROM).
To allow interaction with the multimedia hardware MMPM/2 defines a set of media devices on top of the PDDs, which can be controlled via the textual string commands of the Media Control Interface (MCI), defined by IBM and Microsoft in 1991.
The standard media devices are:
Device name | Description |
---|---|
CDaudio | A CD-ROM device which supports standard audio compact disc playback |
Digitalvideo | A device which supports audio/video files, either hardware-assisted or software motion video-only (e.g. it plays Intel Indeo and IBM Ultimotion *.AVI files) |
Sequencer | A device which supports MIDI files (*.MID) |
Videodisc | A videodisc player |
Videotape | A videotape player or recorder |
Waveaudio | A device which supports digital audio files (*.WAV) |
The MMPM/2 headers also define these devices: | |
Ampmix | An amplifier/mixer device |
Animation | DataA device for playing Autodesk Animator (*.FLC, *.FLI) and MacroMind Director files (*.MMM) |
Audiotape | An analog audio tape deck |
CDXA | A CD-ROM device which supports the extended architecture (XA) audio compact disc standard (up to 8h stereo, 15h music, 30h voice quality playback) |
DAT | A digital audio tape deck |
Headphone | HeadphoneS |
Microphone | A microphone |
Monitor | A monitor output device |
Other | An undefined MCI device |
Overlay | A video overlay device - video output in a window |
Scanner | An image scanner |
Speaker | A speaker |
These media devices are modelled closely after their real-world pendants and their programming via the MCI command strings mirrors the actions one usually performs on a common audio or video component. Also the controls of the utilities delivered with the MMPM/2 have the same typical look and feel of their real-world counterparts.
All devices are either of simple or compound type. Compound devices use data files (in MCI-speak: can load a device element in the device context), simple devices don't. CDaudio is a simple device, Waveaudio a compound one.
Let's start with a simple example which should play a certain waveform audio file. The MCI command strings one needs for this are:
open Waveaudio alias wave shareable wait load wave c:\mmos2\sounds\boing.wav wait play wave wait close wave wait
First the Waveaudio device is opened in shareable mode (in MCI-speak: a device context is created) and it gets the alias wave assigned to it. Then using the alias to address the media device, the BOING.WAV file is loaded into memory. A play command is issued on wave and finally the device is closed. This also frees the memory assigned to the media device when loading the *.WAV file. The wait keyword tells MMPM/2 to wait until the command has finished before continuing.
Now let's play the end from "U got the look" (track 1, 3.40) to the beginning of "if I was your girlfriend" (track 2, 0.20) from disc 2 of Prince's "Sign o' the Times" album:
open CDaudio alias cd wait set cd time format tmsf wait play cd from 1:3:40 to 2:0:20 wait close cd wait
The CDaudio device is opened and the alias cd is assigned to it. Next the media device is told to count in tracks, minutes, seconds, and frames. Finally, both tracks are played from the given positions and the device is closed.
Note that the CDaudio device is directly manipulated; it is not necessary to load a data file.
The third examples plays a software video slowed down to 25% speed. Note that in this mode audio is switched off.
open digitalvideo alias video wait info video product wait load video some.avi wait set video speed format percentage wait play video speed 25 wait close video wait
The digitalvideo device gets opened and video is the assigned alias. Then the device is queried for the information product; this will create a return string with some describing text. The file SOME.AVI gets loaded and the format for speed settings is set to percentages. The video is then played with 25% speed and the device is closed.
I hope the basic recipe of a MCI script was conveyed from these examples. They illustrate that the different media devices are handled in similar manners. Armed with a MCI manual you should be able to write your own scripts.
To send those command strings to the interpreting Media Device Manager (MDM) one can use either REXX, C/C++ or the String Test tool provided with the Multimedia Toolkit.
Using the MCI from REXX
Now let's look how to realize those examples in REXX.
Example 1:
/************************************************************ boing.cmd REXX procedure to play boing.wav via MMPM Marc E.E. van Woerkom, 1/94 ************************************************************/ rc = call RxFuncAdd('mciRxInit', 'MCIAPI',, /*register MCI REXX API*/ 'mcciRxInit') rc = call mciRxInit() /*and initialize it*/ rc = mciRxSendString('open waveaudio', /*open digital audio*/ 'alias wave shareable wait',, 'RetStr', '0', '0') boing = 'c:\mmos2\sounds\boing.wav' rc = mciRxSendString('load wave' boing,, /*load wav*/ 'RetStr', '0', '0') rc = mciRxSendString('play wave wait',, /*play file*/ 'RetStr', '0', '0') rc = mciRxSendString('close wave wait',, /*close device "wave"*/ 'RetStr', '0', '0') call mciRxExit exit rc /*exit with rc code*/
The first step in BOING.CMD is to register the MCI REXX application interface using the call to RxFuncAdd() and then by initializing it via a call to mciRxInit().
Statements of the form mciRxSendString('some_MCI_command',RetStr,'0','0') send the MCI command string to the MDM and may receive a return string in the RetStr variable.
Business finishes with a call to mciRxExit().
Example 2:
/************************************************************ prince.cmd REXX procedure to play some audio CD tracks via MMPM Marc E.E. van Woerkom, 1/94 ************************************************************/ rc = call RxFuncAdd('mciRxInit', 'MCIAPI',, /*register MCI REXX API*/ 'mciRxInit') rc = call mciRxInit() /*and initialize it*/ rc = mciRxSendString('open CDaudio', /*open CDaudio*/ 'alias cd wait',, 'RetStr', '0', '0') rc = mciRxSendString('set cd time format', /*count in tracks*/ 'tmsf wait ',, /*(tt:mm:ss:ff)*/ 'RetStr', '0', '0') say rc rc = mciRxSendString('play cd from 1:3:40 to 2:0:20 wait',, /*play file*/ 'RetStr', '0', '0') if rc <> 0 then do rc2 = mciRxGetErrorString(rc, 'ErrStVar') say 'ERROR! rc =' rc ', ErrStVar =' ErrStVar exit rc2 end rc = mciRxSendString('close cd wait',, /*close device "cd"*/ 'RetStr', '0', '0') say rc call mciRxExit exit rc /*exit with rc code*/
This example also uses the function mciRxGetErrorString() to receive the error message belonging to the return value RC.
Example 3:
/************************************************************ playvid.cmd REXX procedure to play a software video at 25% speed usage: pmrexx playvid name.avi Marc E.E. van Woerkom, 1/94 ************************************************************/ parse arg avi /*fetch first argument*/ if avi = then do say 'usage: pmrexx playvid name.avi' exit 0 end say '(Playing a Software Video at 25% speed ...' rc = call RxFuncAdd('mciRxInit', 'MCIAPI',, /*register MCI REXX API*/ 'mciRxInit') rc = call mciRxInit() /*and initialize it*/ rc = mciRxSendString('open digitalvideo alias', /*open player*/ 'video wait',, 'RetStr', '0', '0') rc = mciRxSendString('info video', /*ask for product info */ 'product wait',, 'videoinfo', '0', '0') say ' Digital Video Device is' videoinfo rc2 = mciRxSendString('load video' avi,, /*play *.avi */ 'RetStr', '0', '0') rc = mciRxSendString('set video speed', /*set speed in % */ 'format percentage wait',, 'RetStr', '0', '0') rc = mciRxSendString('play video speed 25 wait',, /* play file */ 'RetStr', '0', '0') rc = mciRxSendString('close video wait',, /*close video device*/ 'RetStr', '0', '0') if rc2 <> 0 then do rc = mciRxGetErrorString(rc2, 'ErrStVar') say ' ERROR! rc =' rc2 ', ErrStVar =' ErrStVar say ' ... failed!)' exit rc end else say ' ... done.)' call mciRxExit exit rc /*exit with rc code*/
This one starts with a parsing of the command line arguments. Later the example queries the digitalvideo device for a product description string which is delivered into the videoinfo variable.
Note that the digitalvideo device needs the Presentation Manager, so if this REXX procedure is to be used from the command line, one has to call it through PMREXX.
Example 4:
/************************************************************ cdlock.cmd REXX procedure to lock the CD device usage: cdlock Marc E.E. van Woerkom, 11/93 ************************************************************/ say '(Locking the CD drives door ...' rc = call RxFuncAdd('mciRxInit', 'MCIAPI',, /*register MCI REXX*/ 'mciRxInit') rc = call mciRxInit() /*and initialize it*/ rc = mciRxSendString('open CDaudio alias cd', /*open CDaudio*/ 'shareable wait',, 'RetStr', '0', '0') rc2 = mciRxSendString('set cd door locked wait',, /*lock CD door*/ 'RetStr', '0', '0') rc = mciRxSendString('close cd wait',, /*close device "cd"*/ 'RetStr', '0', '0') if rc2 <> 0 then say ' ... failed!)' else say ' ... done.)' call mciRxExit exit rc2 /*exit with rc2 code*/
Did you notice already the menu item lock disk in the context menu of the CD-ROM drive icon? As does that menu item, this procedure locks the door of the CD-ROM drive so that it can't be opened with a push on the external open/close button.
Example 5:
/************************************************************ cdunlock.cmd REXX procedure to unlock the CD device usage: cdunlock Marc E.E. van Woerkom, 11/93 ************************************************************/ say '(Unlocking the CD drives door ...' rc = call RxFuncAdd('mciRxInit', 'MCIAPI',, /*register MCI API*/ 'mciRxInit') rc = call mciRxInit() /*and initialize it*/ rc = mciRxSendString('open CDaudio alias cd', /*open CDaudio*/ 'shareable wait',, 'RetStr', '0', '0') rc2 = mciRxSendString('set cd door unlocked wait',, /*unlock CD door*/ 'RetStr', '0', '0') rc = mciRxSendString('close cd wait',, /*close device "cd"*/ 'RetStr', '0', '0') if rc2 <> 0 then say ' ... failed!)' else say ' ... done.)' call mciRxExit exit rc2 /* exit with rc2 code*/
This procedure unlocks the CD-ROM drive, reenabling the external open/close button.
Example 6:
/************************************************************ cdopen.cmd REXX procedure to open the CD door usage: cdopen Marc E.E. van Woerkom, 11/93 ************************************************************/ say '(Opening the CD drives door ...' rc = call RxFuncAdd('mciRxInit', 'MCIAPI',, /*register MCI REXX API*/ 'mciRxInit') rc = call mciRxInit() /*and initialize it*/ rc = mciRxSendString('open CDaudio alias cd', /*open CDaudio*/ 'shareable wait',, 'RetStr', '0', '0') rc2 = mciRxSendString('set cd door open wait',, /*open CD door*/ 'RetStr', '0', '0') rc = mciRxSendString('close cd wait',, /*close device "cd"*/ 'RetStr', '0', '0') if rc2 <> 0 then say ' ... failed!)' else say ' ... done.)' call mciRxExit exit rc2 /*exit with rc2 code*/
This procedure opens the door of your CD-ROM drive and ejects the tray. I admit, it is a bit drole (somehow it reminds me of little R2-D2 from Star Wars) but I'd be grateful if someone points out a serious application of this to me.
Example 7:
/************************************************************ playall.cmd REXX procedure to play all *.wav *.mid usage: playall directory [wav] [midi] [rec] Marc E.E. van Woerkom, 1/94 ************************************************************/ parse arg dir arg1 arg2 arg3 /*copy argv[1] and remove*/ say if dir = then do say 'PlayAll seeks and plays all soundfiles' say say 'usage: playall directory [wav] [midi] [rec]' exit 0 end if dir = '.' then dir = directory() /*current dir*/ dowav = 0 domidi = 0 dorec = 0 parse upper arg dummy arg1 arg2 arg3 /*copy argv[1..3]*/ if arg1 = 'WAV' then dowav = 1 if arg1 = 'MIDI' then domidi = 1 if arg2 = 'MIDI' then domidi = 1 if arg1 = 'REC' then dorec = 1 if arg2 = 'REC' then dorec = 1 if arg3 = 'REC' then dorec = 1 if (\dowav) & (\domidi) then do dowav = 1 domidi = 1 end files = /*message*/ if dowav then files = ' *.wav' if domidi then files = files' *.mid' opt = 'O' /*dir tree options*/ if dorec then do /*F: files only*/ opt = opt'S' /*O: only qualified name*/ recstr = 'and beneath ' /*S: recurse*/ end else recstr = say '(Playing all'files 'files from' dir recstr'...' say rc = call RxFuncAdd('SysLoadFuncs', 'RexxUtil',, /*register REXX Utils*/ 'SysLoadFuncs') rc = call SysLoadFuncs() /*and initialize them*/ rc = call RxFuncAdd('mciRxInit', 'MCIAPI',, /*register MCI REXX API*/ 'mciRxInit') rc = call mciRxInit() /*and initialize it*/ if dowav then do rc = mciRxSendString('open waveaudio', /*open digital audio*/ 'alias wave shareable wait',, 'RetStr', '0', '0') if rc <> 0 then do rc2 = mciRxGetErrorString(rc, 'ErrStVar') say 'ERROR! rc =' rc ', ErrStVar =' ErrStVar exit rc2 end rc = mciRxSendString('info wave', /*ask for product info*/ 'product wait',, 'waveinfo', '0', '0') rc = mciRxSendString('close wave wait',, /*close device "wave"*/ 'RetStr', '0', '0') say ' Waveaudio device is' waveinfo end if domidi then do rc = mciRxSendString('open sequencer', /*open MIDI device*/ 'alias midi2 shareable wait',, 'RetStr', '0', '0') if rc <> 0 then do rc2 = mciRxGetErrorString(rc, 'ErrStVar') say 'ERROR! rc =' rc ', ErrStVar =' ErrStVar exit rc2 end rc = mciRxSendString('info midi2', /*ask for product info*/ 'product wait',, 'midi2info', '0', '0') rc = mciRxSendString('close midi2 wait',, /*close device "midi"*/ 'RetStr', '0', '0') say ' MIDI device is' midi2info end if Right(dir, 1) = '\' then mask = dir'*.*' else mask = dir'\*.*' rc = call SysFileTree(mask, 'file', opt) /*read dir tree*/ if file.0 = 0 then do say ' No files found!' rc = 0 end do i=1 to file.0 ext = Translate(Right(file.i, 4)) if dowav & (ext = '.WAV') then do say say ' File' file.i':' rc = mciRxSendString('open waveaudio', /*open digital audio*/ 'alias wave shareable wait',, 'RetStr', '0', '0') rc = mciRxSendString('set wave time', /*set time format to ms*/ 'format ms wait',, 'RetStr', '0', '0') rc = mciRxSendString('load wave' file.i 'wait',, /*play *.wav*/ 'RetStr', '0', '0') rc = mciRxSendString('status wave length wait',, /*query length*/ 'RetStr', '0', '0') if rc = 0 then do time = RetStr / 1000.0 say ' Playing time is' time 's' end rc = mciRxSendString('play wave wait',, /*play file*/ 'RetStr', '0', '0') rc = mciRxSendString('close wave wait',, /*close device "wave"*/ 'RetStr', '0', '0') end if domidi & (ext = '.MID') then do say say ' File' file.i':' rc = mciRxSendString('open sequencer', /*open MIDI device*/ 'alias midi2 shareable wait',, 'RetStr', '0', '0') rc = mciRxSendString('set midi2 time', /*set time format to ms*/ 'format ms wait',, 'RetStr', '0', '0') rc = mciRxSendString('load midi2' file.i 'wait',, /*play *.mid*/ 'RetStr', '0', '0') rc = mciRxSendString('status midi2 length wait',, /*query length*/ 'RetStr', '0', '0') if rc = 0 then do time = RetStr / 1000.0 say ' Playing time is' time 's' end rc = mciRxSendString('play midi2 wait',, /*play file*/ 'RetStr', '0', '0') rc = mciRxSendString('close midi2 wait',, /*close device "midi"*/ 'RetStr', '0', '0') end end say if rc = 0 then say '... done.)' else say '... playing aborted!)' call mciRxExit exit rc /*exit with rc code*/
I wrote this procedure to try out a whole directory of audio files at once. To play all *.WAV files from the current directory and all directories under it, type playall . wav rec on the command line.
Literature on MMPM/2
Dick Conklin (Ed.):
OS/2 2.x Notebook - The Best of OS/2 Developer Magazine.
Van Nostrand Reinhold, New York, 1993.
IBM Doc. S41G-2919:
MMPM/2 Programming Guide.
"Describes application and subsystem programming interfaces to help you select and implement functions for OS/2 multimedia applications"
IBM Doc. S41G-2920:
MMPM/2 Programming Reference.
"Provides detailed information on multimedia functions, messages, and data structures to enable you to write code for multimedia applications"
IBM Doc. S41G-2921:
MMPM/2 Sample Programs Workbook.
"Gives code examples from MMPM/2 sample application programms and templates for subsystem components"
IBM Doc. S41G-2922:
CUA Guide to Multimedia User Interface Design.
"Describes design concepts to consider when designing a CUA multimedia interface"
IBM Doc. S41G-2923:
OS/2 Multimedia Advantages.
"Describes advantages offered by OS/2 and MMPM/2 multimedia platform"
IBM Doc. S41G-3321:
Complete MMPM/2 Technical Library.
John Musser:
A Multimedia Class Library for Windows.
Dr. Dobb's Journal, 7:84-90, 1993.