OS-9/OSK Answers! by Joel Mathew Hegberg
February 1994 issue


One of the great things about the OS-9 community is the cooperation between various programmers. When one needs a tip or trick, all he needs to do is post a brief message for others to see, and someone quickly answers the question. After college let out for the Christmas season, I had time to work on a few projects and ran into a couple difficulties. I would like to publicly share those problems and the answers my programming colleagues provided, in case anyone else is in a similar situation. I also am including a short program I wrote to play a digital bell-tone under K-Windows, due to popular request. Note that this month's article is exclusively for OS-9/68000 programmers.

WANTED: DEAD OR ALIVE

I recently released ModJukeBox for K-Windows, which manages multiple MOD music files and plays them using Russ Magee's 'Strack' program. Russ Magee did an amazing piece of work with Strack! ModJukeBox provides a graphical interface for playing MOD files, and allows the user to choose a sequence of several MOD files to be played automatically.

In order for my ModJukeBox program to know when it was time to move on to the next song, I needed to know when Strack was finished playing the current MOD music file. While I could have simply issued a WAIT(), I wanted ModJukeBox to stay "awake" and responsive to the user while Strack (a forked child process) played the music file. There should be some way to monitor and determine if a child has died yet, right?? (I mean, I saw those Psychic commercials where they depict a mother sensing her child in danger! OS-9 must have something similar, right?!)

Well, Ken Scales came to the rescue! It turns out he had a similar situation while programming KVed, his graphical K-Windows interface for use with Bob van der Poel's VED text editor. He solved the problem by using the _get_process_desc() C function, which provides information about a process (given the process id). When a child process dies, OS-9 holds the information regarding it's death until the parent process picks it up, which prevents another new process from being assigned the process id of the dead child process. So, all that's needed is to check the state of child process occasionally to make sure it's not dead yet. If it is, start up the next music file. Ken provided the following source code to illustrate his example. My thanks to him for allowing me to print this here.

LISTING #1:
-----------
#define P_CONDEMNED (0x200)    /* Process state: condemned */
#define P_DEAD      (0x100)    /* Process state: dead */

#include <procid.h>
void main(argc, argv)
int argc;
char *argv[];
{
    int pid;
    procid pbuf;
    unsigned short childstate;

    childstate = !(P_DEAD|P_CONDEMNED); /* Intitial assumption is not dead or condemned */
    
    ... program stuff ...

    if (_get_process_desc(pid, sizeof(pbuf), &pbuf)!= -1)
        childstate = pbuf._state;
    else
        childstate = P_DEAD;
    
    if (childstate & (P_DEAD|P_CONDEMNED))
    {
        /* child process has died... */
        /* Do your processing here, including */
        /* issuing a WAIT() call to release */
        /* the dead child's process id. */
    }
}

Ken also notes that there is a bit of information on this topic on pages 362 and 370 of "The OS-9 Guru," which is a fantastic book for any serious OS-9 programmer.

OS-9/68000 DATA MODULES

In another program I'm currently working on, I would like to make use of one of OS-9/68000's most useful features... a data module. Data modules allow several programs to share a common data-space, so more than one program can utilize the information stored within the data module. In my case, I have about 300k of data that subsequent programs I write may wish to use as well. Without the use of data modules, each program would have to load in the 300k of data separately, eating up lots of memory! This way, only one 300k hunk of memory is eaten up by the data module and shared by multiple programs. When the data module is no longer needed (i.e. all the programs using it quit running), it is removed from memory.

"Great!," I thought... "What could be simpler?" So, I threw open my Microware C manual and looked up "_mkdata_module()." It mentioned the header file "module.h" needed to be included (ok), the parameters for the call (ok), and that the function returned a pointer to a "mod_exec" structure (huh??). I took a look at the header file "module.h" and found the mod_exec structure. I was looking for a pointer to the place where I'm allowed to store my data, but found several defined pointers, including _mexec (offset to execution entry point), _mexcpt (offset to exception entry point), _midata (offset to initialized data), and _midref (offset to data reference lists). Once again, "huh??"

My best guess was to try using the _midata pointer, since I was dealing with data, right? Before trying it, I thought I'd post a message in Delphi forum... after all, I really didn't have a clue! Stephen Carville solved the problem for me, and I was glad I asked. It turns out the _mexec (execution offset) is where you want to store data, NOT the _midata (data offset)! Here's Stephen's sample source code (my thanks to him for permission to print it here):

Listing #2:
-----------
#include <module.h>
main()
{
    char *dataptr, *mod;    /* Pointer to whatever data-type to be stored */
    mod_exec mp;

    mp = _mkdata_module(modname, datasize, attrevs, perms);
    mod = (char *)mp;
    dataptr = (char *)(mod + mp->_mexec);    /* data pointer */
}

Notice that what Stephen is doing is taking mp (which is a pointer to the module header) and adding to it the execution offset, since an offset is not an absolute memory address, but rather is a relative pointer. Stephen also mentions to make sure you don't mess with the value returned by _mkdata_module() ('mp' in the source code above), as it will be needed to unlink the module when the program is finished using it. OS-9 will not automatically deallocate a data module when the creating process terminates.

STEREO BELL FOR K-WINDOWS

A few programmers have mentioned how much they like the stereo "bell" sound I use in Write-Right! (my commercial word processor for K-Windows, available through Sub-Etha Software). It's actually a very simple routine to set-up. Since it's such a simple sound, the frequency for playback doesn't need to be very high, so the memory the sample takes up can be very low (specifically, 1k)!

The idea behind the sample is I want a tone that evenly decreases in volume. The tone's frequency is not of concern, since it can be varied by the K-Windows _ss_play() setstat call. The volume of a tone is determined by the value difference from one byte to the next being sent to the computer's speaker. So, if I first send the byte value $80 to the speaker, and then send a byte value of $00 to the speaker, there is a large difference and the sound-wave has greater amplitude (i.e. volume). If I slowly make the difference between subsequent bytes shrink, the tone will taper off and sound like a chime or bell. The data bytes could look like this:

$00 $80 $00 $7f $00 $7e $00 $7d ... $00 $01 $00 $00
  LOUD                                QUIET

Because the K-Windows _ss_play() function is a stereo call, in actuality each byte needs to be duplicated, since the first byte goes to one speaker, the second byte to the other speaker. So, the data would actually be something like:

$00 $00 $80 $80 $00 $00 $7f $7f ... etc.

Ok, enough theory... here's the source code: (Please note that the source requires the 'cgfx.l' library for the _osk() function call.)

Listing #3: "playbell.c"
------------------------
/* Playbell routine by Joel Mathew Hegberg */
#include <stdio.h>
#include <MACHINE/regs.h>

#define PATH 1
#define SNDMEM 1024

extern char *malloc();

REGISTERS reg;

main(argc, argv)
int argc;
char *argv[];
{
    int length, t;
    char *sound_data, *tp, e;
    
    sound_data = malloc(SNDMEM);
    if (sound_data == NULL)
    {
        fprintf(stderr, "\nPlayBell: Cannot allocate %d bytes of memory.\n\n", SNDMEM);
        exit(0);
    }
    e = 0x80;
    for(t = 0, tp = sound_data; t < SNDMEM; t += 8, e--)
    {
        *(tp++) = 0;
        *(tp++) = 0;
        *(tp++) = e;
        *(tp++) = e;
        *(tp++) = 0;
        *(tp++) = 0;
        *(tp++) = e;
        *(tp++) = e;
    }
    reg.d[0] = PATH;
    reg.d[1] = 0x0097; /* $97 is K-Windows _ss_play setstat code */
    reg.d[2] = SNDMEM; /* Number of bytes of sound data to play */
    reg.d[3] = 1000; /* Playback at 1000 Hz */
    reg.d[4] = 0; /* Must be zero */
    reg.d[5] = 0; /* No signal to send when done */
    reg.d[6] = 0; /* Must be zero */
    reg.a[0] = sound_data; /* Address of sound data */
    _osk(0x8e, &reg);
    free(sound_data); /* Release the memory back to the system */
}

Of course, if this is used in an application program, such as Write-Right!, you don't want to be reprogramming the sound data every time you want to ring the bell. Rather, you allocate sound data memory, initialize the sound data, and then whenever you need to ring the bell, simply call the _ss_play() K-Windows function. Before exiting the program, you should release the memory back to the system, using the free() function.

See you next issue!

* THE END *