Making Noise with MMPM/2 - Part 1

From EDM2
Revision as of 01:45, 21 March 2018 by Ak120 (Talk | contribs)

Jump to: navigation, search

by Marc van Woerkom

Making Noise with MMPM/2
Part 1 Part 2

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.