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


The plumber is always the guy played in movies who is synonymous with charging an arm-and-a-leg for his services. Of course, the home-owner in the movie always tries to fix the leaky pipe himself, winds up causing much worse damage, and merely prolongs the inevitable call to the plumber. Wow! Just think of the lesson that is reverberated again and again in the countless movies and television shows which use this premise. They drill the moral, "Fixing pipes is for professionals only... don't try this yourself". Talk about great advertising on behalf of plumbers nationwide!!

So what does plumbing have to do with OS-9? Well, to start off this issue's column I thought I'd discuss OS-9's pipes, and what they can do for you. Instead of carrying water, OS-9 pipes carry data from one process to another (or even back to the same process). Best of all, we can tinker with these pipes without worrying about flooding the property or making any costly calls to your local plumber!

Under OS-9/6809, there is only one type of pipe -- "unnamed". Under OS-9/68000, we have both "named" and "unnamed" pipes. Unnamed pipes must be inherited by a child process from a parent process. This makes them a little less flexible than named pipes, which can be accessed by any process that knows the pipe's name. Because of this benefit, I'm going to concentrate on named pipes for this issue.

What are these named couriers of bits good for? Plenty, actually. First, they are great for sending messages from one process to another. In the OSK version of my CheckBook+ program, I used BASIC to code the main program (since that was all I knew at the time I wrote the original code). I wanted to get my hands on the mouse data, to make the program more user-friendly, but I only knew how to do so in C. What did I do to pass the mouse data from the C routine back to the main BASIC program? A named pipe, of course!

Secondly, named pipes are wonderful for broadcasting data to any number of processes. This is the idea I would like to illustrate in this issue. There is also a third use that comes to mind... a temporary storage area, like a ram disk. I'll elaborate more on that in the next issue, as it is an extremely useful concept for installation programs.

Here's the concept behind the source code that I'm including here. Let's pretend we're working in an electronic newsroom, where news headlines come in over a live newsfeed. The newsfeed will be a named pipe (appropriately named "newsfeed"), and it will be filled by a background process called "headlines". Headlines (listing #1) simply makes up randomly generated headlines and feeds them into the newsfeed pipe.

Next, we need a newsfeed reader program. This could simply be the "list" utility ('list /pipe/newsfeed'), but to make this fantasy a little more realistic I'm including source to a program called "news_reader" (listing #2). This program reads in one headline from the newsfeed pipe, displays it on screen, pauses for 2 seconds, and then repeats the process forever. If you run news_reader when headlines isn't currently running, news_reader will report the newsfeed is down. Additionally, if the news feed pipe ever becomes empty (which happens a few seconds after you kill the "headlines" process), news_reader will report the newsfeed has gone down.

What's so neat about this concept? Remember I said "named pipes are wonderful for broadcasting data to any number of processes." So, if you have a multi-window windowing system or a spare terminal hooked up to your computer, simply start the "headlines" program running in the background by typing "headlines&" from the OS-9 shell. Then, run "news_reader" on a couple different windows or terminals and you will see that the headlines program can supply data for all copies of news_reader running. If you kill the headlines program, after a few seconds both news_reader programs will report the newsfeed has gone down. The reason for the delay is the pipe is buffered by 90 bytes, so the news_reader program(s) will continue to run until the pipe has been emptied.

A practical application for this may be a robot program that takes commands from a named pipe and executes them. A management program could be written to send commands to the robot programs via the named pipe. But each command takes time to execute, and in the meantime additional commands from the management program might be piling up in the named pipe. Simply run additional copies of the robot program in the background, and each one will take turns answering the management program's commands. And if one is busy taking care of a previous command, another robot can concurrently grab the next command and execute it.

Listing # 1: headlines.c
========================
#include <stdio.h>
#include <modes.h>
#include <errno.h>

/* Below are the words to make random headlines from... */
/* Feel free to add more if you like. */
char *noun[]=
{
    "The President ",
    "Microware ",
    "An OS-9 computer system ",
    "Stocks ",
    "Prisoners ",
    NULL
};

char *verb[]=
{
    "dropped ",
    "escaped ",
    "ran ",
    "left ",
    "announced ",
    NULL
};

char *object[]=
{
    "a new software product.",
    "for re-election.",
    "from the county jail.",
    "10 points.",
    "for vacation.",
    NULL
};

int  num_nouns, num_verbs, num_objects;
char sentence[80];

main()
{
    int pipepath;
    
    /* Create the pipe file. */
    pipepath = creat("/pipe/newsfeed", S_IWRITE);
    if (pipepath == -1)
    {
        fprintf(stderr, "Can't open named pipe.\n");
        exit(errno);
    }
    
    /* Count how many nouns, verbs, and objects. */
    for (num_nouns = 0; noun[num_nouns]; num_nouns++);
    for (num_verbs = 0; verb[num_verbs]; num_verbs++);
    for (num_objects = 0; object[num_objects]; num_objects++);
    
    /* This loop will run forever. */
    while(1)
    {
        strcpy(sentence, noun[rand(num_nouns) - 1]);
        strcat(sentence, verb[rand(num_verbs) - 1]);
        strcat(sentence, object[rand(num_objects) - 1]);
        strcat(sentence, "\n");
        writeln(pipepath, sentence, 80);
    }
}

static rndval = 4444;

/* rand( val ) : return a pseudo-random value between 1 and val.
**               IF val is negative, it is used as a seed.
*/
rand( val )
int val;
{
        int bit0, bit1, bit14, num, i;
        
    /* see if -1 */
    if ( val < 0 )
      rndval = -val;
      
    for ( i = 0; i < 10; i++ )
    {
        bit0 = rndval & 1;
        bit1 = (rndval & 2) >> 1;
        bit14 = bit0 ^ bit1;
        rndval >>=1;
        rndval |= (bit14 << 14);
    }
    num = rndval % val;
    return( num ? num : val );
}


Listing #2: news_reader.c
=========================
#include <stdio.h>
#include <modes.h>
#include <errno.h>

#define STDOUT 1

char sentence[80];

main()
{
    int pipepath, length;
    
    /* Attempt to open the pipe file */
    pipepath = open("/pipe/newsfeed", S_IREAD);
    if (pipepath == -1)
    {
        fprintf(stderr, "Sorry, the newsfeed is down!\n");
        exit(errno);
    }
    
    /* This loop runs forever... */
    while(1)
    {
        /* read the next headline */
        length = readln(pipepath, sentence, 80);
        if (length <= 0)
        {
            fprintf(stderr, "\n** Sorry, the newsfeed has gone down! **\n\n");
            exit(errno);
        }
        
        /* print the headline */
        writeln(STDOUT, sentence, 80);
        
        /* Pause for 2 seconds */
        sleep(2);
    }
}

One problem a few MM/1 programmers have asked me about is how to load and play sounds under K-Windows. At first I thought the problem may be playing the sounds, but as it turns out the confusion appears to lie in loading the sound and memory management.

First, let me say I will be assuming the sounds you want to play are in IFF format. Unfortunately, I have been unable to come by the exact file specifications of this format, but I do know the first 40 bytes are header information, while the rest of the file is the raw, digitized sample. Secondly, I will also be assuming you know the sample rate to play back the digitized sample (or can find out by trial-and-error). Somewhere within the first 40 bytes of IFF header data is this information for you, however I cannot seem to find it, and it appears some digitizing programs omit this information. (If anyone knows about the sample-rate information, please drop me a note!) Finally, I assume you are using K-Windows and have the cgfx.l library with necessary C header files.

C is a great language for this problem since it easily allows dynamic allocation and deallocation (and even reallocation) of memory. This means "memory as you need it" or "memory to order." What our sound-playing program needs to do is allocate memory for the sound, load the sound, play the sound, and finally deallocate the memory. How much memory do we need to allocate? We have to get the file-size and then subtract the number of header-info bytes. Piece of cake under OS-9! Listing #3 shows how to do this for a stereo sound file. Note that the K-Windows _ss_play() function assumes the data you are playing is in stereo.

Listing #3: playsnds.c
======================
#include <stdio.h>
#include <modes.h>
#include <sound.h>

#define STDOUT 1
extern unsigned char *malloc();

unsigned char *sound;
int   size;

main(argc, argv)
int   argc;
char *argv[];
{
    int dpath, sample_rate;
    
    if (argc != 2 && argc != 3)
    {
        fprintf(stderr, "PlaySnds <sndfile> {sample_rate}\n");
        fprintf(stderr, "  If sample_rate is omitted, 16000 Hz is used.\n");
        exit(0);
    }
    
    if (argc == 3)
    {
        sample_rate = atoi(argv[2]);
        if (sample_rate < 4000 || sample_rate > 32000)
        {
            fprintf(stderr, "Sample_rate invalid!\n");
            exit(0);
        }
    }
    else sample_rate = 16000;  /* default */

    dpath = open(argv[1], S_IREAD);
    size = _gs_size(dpath) - 40;

    /* allocate memory */
    sound = malloc(size);
    if (sound == (unsigned char *)NULL)
    {
        fprintf(stderr, "Can't allocate memory!\n");
        exit(0);
    }

    /* skip past the header bytes */
    lseek(dpath, 40, 0);

    /* read in the sound data */
    read(dpath, sound, size);
    close(dpath);
    
    /* play the sample */
    _ss_play(STDOUT, sound, size, sample_rate, SND_NOSIG, 0);

    /* release memory */
    free(sound);
}

What if you need to playback a sound not recorded in stereo (mono)? There are now two techniques to play your sound back in mono. The technique that has been used until recently has been to take the mono sound data and convert it to a stereo sample . This is easily accomplished by duplicating each byte of digital sound so each speaker (left & right) is playing the same data. There are some downsides to this technique. First, it takes time to convert the sample. Second, your requested memory buffer must be twice as large as the sample due to the mono-to-stereo conversion. This technique takes just a few changes in the program.

The first change is we need twice the amount of memory to store our sound sample. This can be done by changing the line

    size = _gs_size(dpath) - 40;

to

    size = (_gs_size(dpath) - 40) * 2;

However, although we need twice the amount of memory, we aren't loading twice the amount of sound-data in from the disk file! So, change the read statement from

    read(dpath, sound, size);

to

    read(dpath, sound, size / 2);

Next, we need a routine to duplicate each byte of sound data. Since the sound data has been loaded into the beginning of our memory buffer (leaving a gap at the end of our allocated memory), we must work backwards to duplicate the sound data or else our routine will overwrite sound data and become a mess. Add the following lines after the close(dpath) statement.

    for (ptr1 = sound + size / 2 - 1, ptr2 = sound + size - 2; ptr1 >= sound; ptr1--, ptr2 -= 2)
        *ptr2 = *(ptr2 + 1) = *ptr1;

To get the above lines to work, you will need to add ptr1 and ptr2 to your program's variable lists. They are unsigned character pointers, so define them as:

    unsigned char *ptr1, *ptr2;

Just so you can be sure about the changes, Listing #4 shows the modified source code (without comments).

Listing #4: playsndm.c
======================
#include <stdio.h>
#include <modes.h>
#include <sound.h>

#define STDOUT 1
extern unsigned char *malloc();

unsigned char *sound, *ptr1, *ptr2;
int   size;

main(argc, argv)
int   argc;
char *argv[];
{
    int dpath, sample_rate;
    
    if (argc != 2 && argc != 3)
    {
        fprintf(stderr, "PlaySndm <sndfile> {sample_rate}\n");
        fprintf(stderr, "  If sample_rate is omitted, 16000 Hz is used.\n");
        exit(0);
    }

    if (argc == 3)
    {
        sample_rate = atoi(argv[2]);
        if (sample_rate < 4000 || sample_rate > 32000)
        {
            fprintf(stderr, "Sample_rate invalid!\n");
            exit(0);
        }
    }
    else sample_rate = 16000;  /* default */

    dpath = open(argv[1], S_IREAD);
    size = (_gs_size(dpath) - 40) * 2;
    sound = malloc(size);
    if (sound == (unsigned char *)NULL)
    {
        fprintf(stderr, "Can't allocate memory!\n");
        exit(0);
    }
    lseek(dpath, 40, 0);
    read(dpath, sound, size / 2);
    close(dpath);
    for (ptr1 = sound + size / 2 - 1, ptr2 = sound + size - 2; ptr1 >= sound; ptr1--, ptr2 -= 2)
        *ptr2 = *(ptr2 + 1) = *ptr1;
    _ss_play(STDOUT, sound, size, sample_rate, SND_NOSIG, 0);
    free(sound);
}

So, what is the second method of playing mono sound samples? Very simple, actually. Andrzej Kotanski discovered that the MM/1's sound driver actually has an undocumented mono-play mode. Actually, it was partially documented but was rumored not to have been implemented... and the partial documentation was quite vague. But, it's extremely easy. You simply need to set bit #0 of the "option flag" in the _ss_play() call. So, you can take Listing #3 and make the following single change and it will play files in mono-play mode.

Change the following line in Listing #3:

    _ss_play(STDOUT, sound, size, sample_rate, SND_NOSIG,0);

to read:

    _ss_play(STDOUT, sound, size, sample_rate, SND_NOSIG | 0x0001, 0);

The advantage of using this technique is there's no mono-to-stereo conversion needed (saves CPU time), and your memory buffer does not have to be twice the size of the sound sample. The downside to this is mono sounds played with this technique seem to have more distortion than mono sounds which have been converted to stereo and then played.

* THE END *