Back to documentation

2000 December 1

YLib Programming Tutorial

Contents

  1. Introduction
  2. Hello World in YLib
  3. YLib Procedure
  4. Putting It All Togeather
  5. YEvent Handling

Introduction

Y Sound Systems is a low-level and multi-client system of access to a machine's Recorder (sound card). YLib is the programming language used to write applications for that system. YLib follows the X window Systems pattern of design and is licensed under GPL (GNU Public License).

You can develop open software, free software, or even commercial (non-free) software using YLib without having to spend anything for licenses or royalties.

The YLib is intended for (but not limited to) entertainment software, any application that wants simplified, multiple, network transparent access to the Recorder much like multiple GUI applications want to output to the display, should use YLib.

YLib attempts to follow as many existing standards as possible, many of its objects contain data that are formatted after popular formats such as the DSP Sound Object capable of having raw, voc, or wav formats.

Back to the top.

Hello World in YLib

First make sure that you have access to a Y Server (preferably on your own computer) and that YLib (which consists of /usr/lib/libY2.so.# and (/usr/include/Y2/*) is properly installed. If not, then you should obtain the complete package of the YIFF Sound System (both server and library) here and install it. Get the latest version and make sure that you build and install all the compoents to maximize your Y Sound System capabilities.

We begin our journey into YLib with a simple hello world(tm) program (note, the below source comes from the YIFF source package, from the file yiffutils/helloworld.c). Code colored red in the below example denotes code that is specific to the YLib language.


#include <stdio.h>

#include <Y2/Y.h>           /* Basic Y types and constants. */
#include <Y2/Ylib.h>        /* YLib functions and structs. */

/* Change this to the address and port of the Y server you want
 * to connect to. Note that 127.0.0.1 is a symbolic address
 * meaning `localhost'.
 */
#define CON_ARG             "127.0.0.1:9433"


int main(int argc, char *argv[])
{
        YConnection *con;
        char *filename;
        YEventSoundObjectAttributes sndobj_attrib;
        YID play_id;
        YEvent event;


        /* Need atleast one argument, being the file name.
         * This is so that we can play a sound object on file.
         */
        if(argc < 2)
                return(1);
        else
                filename = argv[1];


        /* Connect to the Y server. We pass NULL as the start
         * argument, this means the Y server will not be started if
         * it was detected to be not running. The connection
         * argument is CON_ARG which is defined at the beginning of
         * the source. The connection argument is a string of the
         * format ":".
         */
        con = YOpenConnection(
                NULL,
                CON_ARG
        );
        if(con == NULL)
        {
                /* Failed to connect to the Y server. */
                fprintf(
                        stderr,
                        "%s: Cannot connect to YIFF server.\n",
                        CON_ARG
                );
                return(1);
        }


        /* Check if the filename exists on the machine that
         * the Y server is running on and obtain its attributes.
         */

        if(YGetSoundObjectAttributes(
                con,
                filename,
                &sndobj_attrib
        ))
        {
                /* Can't get sound object attributes. */
                fprintf(
                        stderr,
                        "%s: Error: Missing or corrupt.\n",
                        filename
                );
        }
        else
        {
                /* Start playing the sound object. */
                play_id = YStartPlaySoundObjectSimple(
                        con,
                        filename
                );

                /* There is also a YStartPlaySoundObject()
                 * function which allows additional specified
                 * values for playing. More on that later.
                 */

                /* Print sound object attributes. */
                switch(sndobj_attrib.format)
                {
                        case SndObjTypeDSP:
                        printf(
 "ID: %i  Type: DSP  SmpRate: %i Hz  Bits: %i  Ch: %i\n",
                            play_id,
                            sndobj_attrib.sample_rate,
                            sndobj_attrib.sample_size,
                            sndobj_attrib.channels
                        );
                        break;

                        case SndObjTypeMIDI:
                        printf(
 "ID: %i  Type: MIDI\n",
                            play_id
                        );
                        break;

                        default:
                        printf(
 "ID: %i  Type: *Unknown*\n",
                            play_id
                        );
                }

                /* Manage events: Wait untill audio is done playing.
                 * Here we call YGetNextEvent() repeatedly to get
                 * events from the Y server.  Once we get an event
                 * we process it accordingly by its type.
                 */
                while(1)
                {
                        /* Get the next event (if any). */
                        if(YGetNextEvent(
                                con,
                                &event,
                                False   /* Nonblocking. */
                        ) > 0)
                        {
                                /* Sound object stopped playing? */
                                if((event.type == YSoundObjectKill) &&
                                   (event.kill.yid == play_id)
                                )
                                {
                                        /* Our play has stopped. */
                                        printf("Done playing.\n");
                                        break;
                                }
                                /* Server disconnected us? */
                                else if(event.type == YDisconnect)
                                {
                                        /* Got disconnected. */
                                        printf(
                         "Y server disconnected us, reason %i.\n",
                                            event.disconnect.reason
                                        );

                                        /* Need to close connection! */
                                        YCloseConnection(con, False);
					con = NULL;
                                        break;
                                }
                                /* Server shutdown? */
                                else if(event.type == YShutdown)
                                {
                                        /* Server shutdown. */
                                        printf(
                                   "Y server shutdown, reason %i.\n",
                                            event.shutdown.reason
                                        );

                                        /* Need to close connection! */
                                        YCloseConnection(con, False);
                                        con = NULL;
                                        break;
                                }
                                else
                                {
                                        /* Some other Y event, ignore. */
                                }
                        }

                        usleep(1000);   /* Don't hog the CPU. */
                }
        }


        /* Disconnect from the Y server. We need to pass the
         * original connection pointer con to close that
         * connection to the Y server. Note that con may be
         * NULL at this point if the Y server disconnected us
         * already, passing NULL is okay.
         *
         * The second argument asks us do we want to leave the
         * Y server up when we disconnect. If we were the
         * program that started the Y erver and the second
         * argument is set to False then the Y server will
         * be automatically shut down.  To ensure that the Y
         * server stays running, you can pass True instead.
         */
        YCloseConnection(con, False);
        con = NULL;


        return(0);
}

To compile the program, type:

The -lY2 instructs the compiling procedure to link to libY2.so.# (where # is the version number of YLib.

Now before you run helloworld, make sure that you have:

  1. A Y server running on (preferably) your computer (to run YIFF type /usr/sbin/yiff /usr/etc/yiffrc).
  2. The Y utility programs compiled and installed (on your computer).
  3. A readable raw, voc, or wav file to play on the computer the Y server is running on.
If all of the above requirements are met, then type the following commands:
  1. yrecinfo -m (to get a list of Audio modes).
  2. yset audio <audio_mode_name> (choose an Audio mode that best suits the sound file you are about to play).
  3. ./helloworld /home/me/helloworld.wav (play it).
You should be hearing the sound file being played. If the sound didn't come out right, then you may need to select another Audio mode that better matches the sound file you were trying to play (yrecinfo -m to get a list of Audio modes) or adjust the Y mixers (use ymixer).

Now is a good time to review the helloworld program. You may have noticed that its code is awefully long for a `simple example', this is because Ylib is a low-level language. Low-level languages are very lengthy, however the advantage is that they provide the most functionality to the actual device you want to control.

Now don't give up just yet, there's good news ahead! You now have been exposed to all the basic and some intermediate levels of code that's required to work with Ylib. It also means that since you made it this far, you're over the first and most difficult hurdle. If you keep at it, then you are gauranteed to understand the rest of YLib!

You'll find out that the `massive lines of required code' are a blessing rather than `make work'. It allows you to control as much of the aspect of sound programming while keeping the even more low level coding hidden (yup, you don't want a back stage tour just yet!) and keeping your code portable to any platform with YLib.

Remember that any platform with BSD style networking (ie, all UNIXes) can support YLib but not always the Y server. This means you can write your programs for almost any platform and not have to worry about portability, because YLib is almost gauranteed to be portable to it.

If a Y server is not available for that platform, YLib will simply return a NULL when you call YOpenConnection() and your program should be able to continue on normally without playing sound.

There is also a more advanced form of YStartPlaySoundObjectSimple() which allows you to specify additional values on how your Sound Object should be played (these values can also be adjusted while the Sound Object is being played).


        YEventSoundPlay value;
        YID yid;

        value.flags = YPlayValuesFlagPosition |
                      YPlayValuesFlagTotalRepeats |
                      YPlayValuesFlagVolume |
                      YPlayValuesFlagSampleRate;
        value.position = 0;
        value.total_repeats = total_repeats;    /* Can be -1. */
        value.left_volume = vol_left;           /* 0.0 to 1.0. */
        value.right_volume = vol_right;         /* 0.0 to 1.0. */
        value.sample_rate = sample_rate;        /* In Hz, can be 0. */

        yid = YStartPlaySoundObject(con, path, &value);

In the YEventSoundPlay value the member flags specify which values are to be changed. The position specifies which byte position to start playing at. If the Audio is in 16 bits, make sure the position is an even number. total_repeats specify how many times you want to repeat this play, normally it is set to 1 or -1. -1 means to repeat infinatly. The left_volume and right_volume specify the volume of the Sound Object to be mixed into the Sound, valid values are from 0.0 to 1.0 (inclusive). The sample_rate specifies the applied sample rate deviance of the Sound Object to be played at in Hz, specifying a value less than the actual sample rate of the current Audio has no affect (hence passing 0 is the default), specifying a value greater than will make the Sound Object play faster (good for engine sound simulations).

Try replacing the call to YStartPlaySoundObjectSimple() in the sample source with the above example of YStartPlaySoundObject(). Try tinkering with the values passed on to YStartPlaySoundObject() and see what happens!

Back to the top.

YLib Procedure

Good news, it's time to simplify things!

Now that you've seen an example program, all you need to do next is pick out the significant parts of it... that we'll be covering in this section.

YLib is a low level language because it requires the calling program to handle all the procedures (except timings to the sound driver, since YLib is async), while at the same time YLib is a high-level language because it has a transparent network layer and uses a server to do all the messy sound loading, mixing, and playing.

Let's simplify the low level part first, the following is the procedure (flow) of YLib. Note the sequence of YLib functions your program needs to call:

  1. YOpenConnection() this opens a connection to the Y server and (at your option) starts it too. You call this function once when your program wants to connect to the Y server (usually at startup or when your program wants to turn on sound).
  2. YGetNextEvent() this is considered the maintainance function as well as the event reporter. You need to call this function each time your program loops (note the helloworld example does not illustrate this well, we'll go into depth about this function later).
  3. YCloseConnection() this closes the connection to the Y server and (at your option) if your program started the Y server, then it will shut down the Y server.

Let's talk about YOpenConnection() first. This function when called, returns a pointer that you will use as refferance to the connection to the Y server when using most of the other YLib functions. Later on, before your program exits you will want to call YCloseConnection() and pass this pointer to it. Once you do that the pointer is no longer valid and your connection to the Y server should be considered closed. If you want to connect to the Y server again, just call YOpenConnection() again and you will get a pointer to the new connection.

Next is the more complicated function YGetNextEvent(). This function has two purposes, it serves as a maintainance function (something to allow YLib to do internal things you don't need/want to worry about) and fetches the latest event (if any) comming from the Y server. It's up to your program to handle these events properly, examples about handling events from YGetNextEvent() will be covered later. The important part is that your program call this function atleast once per loop (example on that is comming, when we put it all togeather).

Lastly is YCloseConnection() which closes the connection to the Y server. You need to pass it the pointer that you got from YOpenConnection(), or if you pass NULL then it will do nothing.

Back to the top.

Putting It All Togeather

Now it's time to get a contrasting overview of how and where your program should call YLib functions (note this example may differ with the helloworld example):


#include <Y2/Y.h>           /* Basic Y types and constants. */
#include <Y2/Ylib.h>        /* YLib functions and structs. */
#include "myprogram.h"

YConnection *con;

int main()
{
        YEvent event;


        MyProgramInitialize();
        con = YOpenConnection(NULL, somewhere);
        if(con == NULL)
                return(1);

        while(1)
        {
                MyProgramManage();
                if(YGetNextEvent(con, &event, False) > 0)
                        MyProgramHandleEvent(&event);
        }

        MyProgramShutdown();
        YCloseConnection(con);

        return(0);
}

The above sample code is not compilable because it is missing the functions you will have to write for your program. Instead, pay attention to the procedure and note the following:

Notice how YOpenConnection() is called once at the very beginning and YCloseConnection() is called at the end.

Also notice how YGetNextEvent() is called once per loop, and if there was a new event then it would be passed on to be handled by your program's function MyProgramHandleEvent().

Those are the key points you want to pay attention to, remember that YLib is async. It's not too important when you call the YLib function, it's important what order you call them (hence why we emphisize procedure).

Back to the top.

YEvent Handling

Here we show you how to handle YEvents obtained from YGetNextEvent() by example.

Below is what your MyProgramHandleEvent() function probably should look like (that function came from the previous section Putting It All Togeather).


void MyProgramHandleEvent(YEvent *event)
{
        if(event == NULL)
                return;


        switch(event->type)
        {
                /* Audio mode has changed, this happens sometimes.
                 * usually when your program or another program has
                 * asked the Y server to change Audio modes.
                 *
                 * When this happens all your Sound Objects that
                 * you have playing (if any) will recieve
                 * YSoundObjectKill events.
                 */
                case YAudioChange:
                if(event->audio.preset)
                        printf(
        "Audio mode changed to `%s'.\n",
                                event->audio.mode_name
                        );
                else
                        printf("Audio mode changed.\n");
                        printf("Sample Rate: %i Hz\n",
                                event->audio.sample_size
                        );
                        printf("Channels: %i  Bits: %i\n",
                                event->audio.channels,
                                event->audio.sample_size
                        );
                        printf("Fragment Size: %i Bytes\n",
                                event->audio.fragment_size_bytes
                        );
                break;

                /* The Audio cycle interval on the Y server has changed,
                 * either by your program or another program.
                 */
                case YCycleChange:
                printf("Audio cycle interval changed to: %ld ms\n",
                        event->cycle.cycle_us
                );
                break;

                /* Your program has been disconnected from the Y server.
                 * A variety of reasons may have caused this but
                 * regardless of that, you must set the YConnection
                 * pointer you got from YOpenConnection() to NULL.
                 */
                case YDisconnect:
                printf("You have been disconnected, reason code %i.\n",
                        event->disconnect.reason
                );
                /* Need to close connection afterwards. */
                YCloseConnection(con, False);
                con = NULL;
                break;

                /* A host has been added or removed, this isn't
                 * a very important event unless your program is serious
                 * about managing who's allowed to connect.
                 */
                case YSetHost:
                printf("Host %i.%i.%i.%i has been %s.\n",
                        event->host.ip.charaddr[0],
                        event->host.ip.charaddr[1],
                        event->host.ip.charaddr[2],
                        event->host.ip.charaddr[3],
                        ((event->host.op) ? "added" : "removed")
                );
                break;

                /* A sound object (that your program instructed to
                 * play) has stopped playing. This may be important
                 * if you want to keep track of when a sound object
                 * has stopped playing (if not then just ignore it).
                 */
                case YSoundObjectKill:
                printf("Sound object YID %ld has stopped playing.\n",
                        event->kill.yid
                );
                break;

                /* A mixer channel value has changed. This is usually
                 * not very important and you can ignore it.
                 */
                case YMixerChannel:
                printf("Mixer %i has changed value to %.4lf %.4lf\n",
                        event->mixer.code,
                        ((YMixerValues >= 1) ? event->mixer.value[0] : 0),
                        ((YMixerValues >= 2) ? event->mixer.value[1] : 0)
                );
                break;

                /* The Y server has shut down, treat this event
                 * as if it were a YDisconnect type event.
                 */
                case YShutdown:
                printf("Y server has shut down, reason code %i.\n",
                        event->shutdown.reason
                );
                /* Need to close connection afterwards. */
                YCloseConnection(con, False);
                con = NULL;
                break;
        }
}

Most of the events can be ignored for a simple Ylib program. There are only two very important events that you must watch for, they are YDisconnect and YShutdown. When you recieve either of those events you must call YCloseConnection() and then set the connection pointer to NULL. Those events indicate that the Y server has either disconnected you or it has shut down.

Another important responsibility on your part is to make sure that you call YGetNextEvent() to allow it to manage itself and flush the event queue. This does not need to be called on every loop but atleast reasonably often enough to ensure that the Ylib side of things are kept reasonably up to date.

This is the end of the current YLib tutorial, we hope that you've had a productive and educating experiance and wish you the best of luck with YLib!

If you feel that there is a topic that should be included, please write to the developers.

Back to the top.