next up previous contents index
Next: Configure and Compile Up: The Toolkit Developers Guide Previous: Introduction

 

A simple package

Over the next couple of chapters we present the basic steps needed for creating a custom Tcl package.

Following the normal conventions of CSLUsh a package has a name associated with it's functionality, a set of commands, and a set of data objects for communication between similar commands. The latter will be discussed in greater detail in chapter gif.

To invoke a command we will identify the package and then the set of verbs or associated commands. Throughout this guide we will be building a Tcl package for implementing various sorts of filters. In this example there is one package ``filter'' and a set of commands which implement various filtering techniques. The philosophy is that each package will identify a type of data (in this case a wave object), and then the actions or commans releated to the data. At the Tcl level, the data will appear as a ``noun'' in the description. The individual commands will operate on this data and will appear as ``verbs.''

In this chapter we will create a simple first-order low-pass filter.

Getting started

Using the directory tree structure mentioned in chapter 1, setup a directory for this project. Also create the necessary compilation tree.

The source code and appropriate configure and make files for the examples presented reside in the contrib/cslush/pkgs/filter subtree of Toolkit installation.

system prompt% pwd
/local/installation/1.x/contrib/cslush/pkgs/filter
system prompt% ls
configure.in*      cslush.make.ch4    Makefile.in*       cslush.configure   
fCmd.c.ch2         configure*         cslush.make.ch3    fCmd.c.ch4

CSLU-C and CSLUsh uses the GNU auto configuration utility for configuring and compiling the system. Chapter 3 describes the process involved in configuring and compiling in detail. In this chapter we will concentrate mostly on the basic steps involved in creating a CSLUsh package. Therefore the following sections concentrate on the contents of the main Tcl ``glue'' code ( fCmd.c).

To be able to work through this example copy the files fCmd.c.ch2 to fCmd.c and cslush.make.ch3 to cslush.make.

Revision control

In order to keep track of the changes over the lifetime of the project it is advisable to use a revision control system like RCS or CVS. The introductory lines are therefore designed for use by the revision control system. These also provide the necessary authorship and related copyright information.

/*
 * Copyright (C)1996 Johan Schalkwyk <johans@cse.ogi.edu>
 * Copyright (C)1996 Center for Spoken Language Understanding,
 *                   Oregon Graduate Institute of Science & Technology
 *
 * See the file "license.ogi" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * $Source: /usr/speech/CSLUrep/doc/devsh/dev_2.tex,v $
 * $Revision: 1.3 $
 * $Date: 1996/07/16 23:27:28 $
 * $State: Exp $
 * $Locker:  $
 *---------------------------------------------------------------------------
 */

Header files

The header files or include files indicate which set of libraries are needed. Each library has an associated include file which defines the necessary function prototypes. For the CSLUsh packages the include files are typically broken up into three parts:

The first set of include files indicate which set of functions will be drawn from the standard C libraries.

/* standard C library include file directives */
#include <config.h>          /* needed by auto configuration utility */

#include <stdio.h>
#include <cf/stdlib.h>
#include <cf/string.h>
#include <math.h>

It is important to remember that #include<config.h> is always first. config.h provides the necessary system specific configuration constants. In conjunction with the GNU auto configuration utility this allows for the software to be easily portable across multiple platforms.

Note also that some of the include file are prefixed with ``cf''. Due to variability of the standard C libraries across multiple platforms the Toolkit uses these configurable include files instead. For any of the following include files:

tabular89

use the ``cf'' version included with the Toolkit release. Similarly to the constants in config.h, these files are configured using the GNU auto configuration utility.

Tcl packages are mostly just ``glue'' code for C level implemenations of an algorithm. This is true for most of the CSLUsh packages provided. Almost all of the work is done by the CSLU-C counter part of the Toolkit. In our filter example we will be needing some of these libraries.

/* CSLU-C include file directives */
#include <dballoc.h>                /* memory management                   */
#include <wave.h>                   /* speech wave file interfacing        */
#define malloc dbmalloc
#define realloc dbrealloc
#define free dbfree

For a more detailed explanation of the CSLU-C libraries please refer to the CSLU-C users manual. The CSLU-C header files provide information regarding the type definitions and function prototypes needed for the package. In our example we will be using the memory management routines (dballoc.h) and and the type definition for wave objects (wave.h).

The next set of include files are related to Tcl part of the package.

/* Tcl include file directives */
#include <tcl.h>

/* CSLUsh include file directives */
#include <obj.h>                    /* tcl - object interface              */
#include <cmds.h>                   /* tcl - command line parser           */

The include file obj.h provides the necessary function prototypes for interfacing with CSLUsh objects. cmds.h provides the necessary function prototype for Tcl command parsing. These include creating new commands, creating new subcommands and command line parsing of arguments.

Glue code

The Tcl glue code allows us to link the dedicated C implementation of our algorithm with the related data at the Tcl scripting level. Generally the dedicated C implementation of the algorithm will be independent of our Tcl ``glue'' code. Using the application programmer interface (API), the ``glue'' code only has to setup the necessary data needed for the algorithm. This philosophy is true for most CSLUsh packages. However in this example we choose to implement the dedicated code as part of the Tcl ``glue'' code. For many simple algorithms this is often a more efficient implementation.

The process involved can be broken up into the following three steps:

Command line parsing

The first step of the Tcl glue code is to retrieve the data needed to execute the dedicated C implementation of the algorithm. In Tcl the data and the parameters governing the specific algorithm are passed along via the Tcl command line.

 

static int Filter_lowpassCmd(ClientData d, Tcl_Interp *interp, int argc, 
                             char *argv[])
{
        .
        .
  alpha = 10.0;
  if(Cmds_ParseArgs(interp,2,argc,argv,"wave %s -alpha %f",
                    &wname,&alpha)!=TCL_OK) return TCL_ERROR;

        .
        .
}

Cmds_ParseArgs implements a generic procedure argument parsing. It interprets the argv character array using the control string and then stores the result in its argument. The function expects a control string, and a set of pointer arguments indicating where the results should be stored.

The format string consists of a set of optional flags followed by required arguments and finally a set of optional arguments.

Interfacing with data

All of Tcl is based on strings. CSLUsh uses strings as pointers or object handles to complex data objects. The Obj_GetData and Obj_Create functions are used to retrieve or create a new object handle.

   

static int Filter_lowpassCmd(ClientData d, Tcl_Interp *interp, int argc, 
                             char *argv[])
{
        .
        .
  Wave *w, *ow;
        .
        .
  if(!(w=Obj_GetData(interp,WAVE,wname))) return TCL_ERROR;
        .
        .
  if(Obj_Create(interp,WAVE,ow) != TCL_OK) goto lowpass_err;
        .
        .
}

Each object is identified first by its type (in this case WAVE) and secondly by a number which is assigned to the object when created. Chapter gif discusses objects, and there creation process in greater detail.

Doing the work

Once we have the necessary data, the remainder of the ``glue'' code interfaces with the algorithmic component of the system. In our case we have a simple first-order low-pass filter, and it is therefore not necessary to implement a dedicated stand-alone function call.

  for(i=1; i<w->len; i++)
    ow->samples[i] = (1.0/alpha) * (1.0-alpha)* w->samples[i-1] + 
      (1.0/alpha) * ow->samples[i-1];

Creating new Tcl commands

Given our Tcl glue code we are now ready to create a new Tcl command which will link the dedicated C implemenation with the Tcl level code. For this purpose we use the Cmds_CreateSwitchCmd and Cmds_CreateSubCmd functions. The prototype definitions for these function can be found in the header file cmds.h.

   

static SubCmds fcmds[]={
  {"lowpass",Filter_lowpassCmd},
  {0,0},
};

static int Filter_CommandSetup(Tcl_Interp *interp)
{
  int i;

  Cmds_CreateSwitchCmd(interp,"filter",(ClientData)NULL,
                       (Tcl_CmdDeleteProc *)NULL);
  for(i=0; fcmds[i].subcmd; i++)
    Cmds_CreateSubCmd(interp,"filter",fcmds[i].subcmd,fcmds[i].func,
                      (ClientData)NULL,(Tcl_CmdDeleteProc *)NULL); 
  return TCL_OK;
}

Commands are organized using two to three-word verb phrases. The first verb relates to the general use of the type of commands to follow. The second and/or third parts specify the more specific action of the command. In the example above we have a two-word phrase, with the first word (filter) indicating that the sub commands (vsecond verb) implement filtering algorithms. The second verb (lowpass) relates to the specific action (i.e., low-pass filtering). The Cmds_CreateSwitchCmd creates the main command, namely filter. All sub commands are created using the Cmds_CreateSubCmd function.

Package initialization and setup

The Tcl package facility keeps a simple database of the packages available for use by the current interpreter and how to load them into the interpreter. It supports multiple versions of each package and arranges for the correct version of a package to be loaded based on what is needed by the application.

Tcl packages may either be a collection of scripts or a sharable library of Tcl glue code used for interfacting to dedicated C implementations of algorithms. Packages created using the sharable libraries are identified initialized using the PackageName_Init function, which must be an exportable function (i.e, visible when Tcl loads in the particular package).

int Filter_Init(Tcl_Interp *interp)
{
  int r;
  
  if((r=Tcl_PkgProvide(interp,"Filter",FILTER_PKG_VERSION))!=TCL_OK)
    return r;
  Filter_CommandSetup(interp);
  return Obj_Init(interp);
}

The macro FILTER_PKG_VERSION is used to indicate the particular package version.

Tcl also provides the facility of creating a ``safe'' interpreter. A ``safe interpreter'' does not allow any file access, and therefore protects the user from accidently overwriting data or giving access to his or her machine. This can especially be problem when using the Tcl socket interface for remote Tcl executing. For this reason it is also necessary to specificy the command set which is allowed when Tcl is in its ``safe'' execution mode. Since our filter commands are all safe, the Filter_SafeInit function merely calls the Filter_Init function.

 
int Filter_SafeInit(Tcl_Interp *interp)
{
  return Filter_Init(interp);
}



Session:

/*
 * Copyright (C)1996 Johan Schalkwyk <johans@cse.ogi.edu>
 * Copyright (C)1996 Center for Spoken Language Understanding,
 *                   Oregon Graduate Institute of Science & Technology
 *
 * See the file "license.ogi" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * $Source: /usr/speech/CSLUrep/doc/devsh/dev_2.tex,v $
 * $Revision: 1.3 $
 * $Date: 1996/07/16 23:27:28 $
 * $State: Exp $
 * $Locker:  $
 *---------------------------------------------------------------------------
 */

/* standard C library include file directives */
#include <config.h>                 /* needed by auto configuration utility */

#include <stdio.h>
#include <cf/stdlib.h>
#include <cf/string.h>
#include <math.h>

/* CSLU-C include file directives */
#include <dballoc.h>                /* memory management                   */
#include <wave.h>                   /* speech wave file interfacing        */
#define malloc dbmalloc
#define realloc dbrealloc
#define free dbfree

/* Tcl include file directives */
#include <tcl.h>

/* CSLUsh include file directives */
#include <obj.h>                    /* tcl - object interface              */
#include <cmds.h>                   /* tcl - command line parser           */

/* private include file directives */
#include "filter-pv.h"

static int Filter_lowpassCmd(ClientData d, Tcl_Interp *interp, int argc, 
                             char *argv[])
{
  int i;
  float alpha;
  char *wname;
  Wave *w, *ow;
  
  alpha = 0.98;
  if(Cmds_ParseArgs(interp,2,argc,argv,"wave %s -alpha %f",
                    &wname,&alpha)!=TCL_OK) return TCL_ERROR;

  if(!(w=Obj_GetData(interp,WAVE,wname))) return TCL_ERROR;

  ow = (Wave *)malloc(sizeof(Wave));
  ow->len = w->len;
  ow->numattr = w->numattr;
  ow->attr = (float *)malloc(w->numattr * sizeof(float));
  ow->samples = (short *)malloc(ow->len * sizeof(short));
  memcpy(ow->attr, w->attr, w->numattr * sizeof(float));
  memset(ow->samples, 0, ow->len * sizeof(short));
  
  if(Obj_Create(interp,WAVE,ow) != TCL_OK) goto lowpass_err;
    
  for(i=1; i<w->len; i++)
    ow->samples[i] = w->samples[i] + alpha * ow->samples[i-1];

  return TCL_OK;
lowpass_err:
  free((char *)ow->samples);
  free((char *)ow->attr);
  free((char *)ow);

  return TCL_ERROR;
}


static SubCmds fcmds[]={
  {"lowpass",Filter_lowpassCmd},
  {0,0},
};


static int Filter_CommandSetup(Tcl_Interp *interp)
{
  int i;

  Cmds_CreateSwitchCmd(interp,"filter",(ClientData)NULL,
                       (Tcl_CmdDeleteProc *)NULL);
  for(i=0; fcmds[i].subcmd; i++)
    Cmds_CreateSubCmd(interp,"filter",fcmds[i].subcmd,fcmds[i].func,
                      (ClientData)NULL,(Tcl_CmdDeleteProc *)NULL); 
  return TCL_OK;
}


int Filter_Init(Tcl_Interp *interp)
{
  int r;
  
  if((r=Tcl_PkgProvide(interp,"Filter",FILTER_PKG_VERSION))!=TCL_OK)
    return r;
  Filter_CommandSetup(interp);
  return Obj_Init(interp);
}


int Filter_SafeInit(Tcl_Interp *interp)
{
  return Filter_Init(interp);
}


next up previous contents index
Next: Configure and Compile Up: The Toolkit Developers Guide Previous: Introduction

Johan Schalkwyk
Wed Dec 4 10:20:00 PST 1996