SFS Manual for Users

6. SFS Scripting

In this chapter we shall provide practical advice on how to write scripts with SFS. Scripts allow you to combine the functionality of SFS tools, displays and file structure to create new applications. In particular they allow you to perform the same operation on many files without having to process each file individually in SFSwin.

We'll look at three approaches to scripting: (i) using the CYGWIN shell language, (ii) using SML, and (iii) using MATLAB.

6.1

CYGWIN Shell Scripting

Installation

The CYGWIN environment can be downloaded free of charge from www.cygwin.com. Download the setup.exe program and run it. This will allow you to connect to a mirror server near you, and to download the current list of available components. You only need the basic components for scripting with SFS, but you may care to browse the list to see if there are other packages that might interest you. For example there is a complete C/C++/Java/Fortran programming environment.

For CYGWIN to access the individual programs within SFS you need to place the name of the SFS Program subdirectory into the search path for executable programs used by all Windows shells (including the COMMAND prompt shell as well as the CYGWIN shell). To do this you will need to be logged on as administrator. Then within the Control Panel, find the "System" application, and within that the "Environment Variables" button. On Windows XP, the Environment Variables button is at the bottom of the "Advanced" tab screen. Under the "System Variables", select the "Path" variable and then "Edit". You will see that the Path variable contains a list of directory names separated by semi-colons. If SFS has been installed into "c:\Program Files\SFS" (the default), then add to the end of this string:

;c:\Program Files\SFS\Program

Otherwise add the name of the Program subdirectory where you installed SFS. OK the changes and close the control panel.

You can check that the path is correct by starting a Command Prompt window from the Start programs button, then typing "PATH". A complete list of directories is produced and the SFS Program directory should be included.

Start the CYGWIN environment from the Start button. A BASH shell window will open where you can type shell commands. Type "echo $PATH" to check that the SFS Program directory is included in the executables search path.

You will need to use a plain text editor to create shell scripts. Windows NOTEPAD is one possibility, but TEXTPAD is a highly recommended alternative with much greater functionality.

Simple scripts

One important use of scripts is to combine a number of elementary operations into one: this allows the user to call up a standard sequence of operations with just one menu operation.

For example, let us assume that one standard preparation sequence is to high-pass filter, resample to 10,000 samples/per second and set the overall gain to a standard level. If we were to perform these operations on the file test.sfs, the command lines might be:


genfilt -h 50 test.sfs
resamp -f 10000 test.sfs
level -a -20 test.sfs

We can combine these operations into a single script as follows:


genfilt -h 50 $1
resamp -f 10000 $1
level -a -20 $1

The "$1" code refers to the name of the first argument to the script. If this file was saved as prepare.sh, we could run it on a particular file by selecting menu option Tools|Run Program/Script to get this dialog:

Note that this script only takes a single filename as an argument. It automatically processes the last SPEECH item in the file. We'll look at how to process multiple arguments in the next section.

Here is another example, which might be used to import a SPEECH and an ANNOT item stored in separate files in some external format into a single SFS file. Here we'll need to use the name of the file supplied to the script to derive the name of the annotations file and the name of the output file. Let's assume that input files have names like file.wav, while the annotations are in file.txt, and the output file is to be file.sfs. The following script creates an SFS file of the appropriate name and imports the two files:


base=`echo $1 | sed s/.wav//`
hed -n $base.sfs
slink -isp -tWAV $1 $base.sfs
anload $base.txt $base.sfs

In this script, the first line strips off the ".wav" to leave a variable called "base" containing the base part of the filename. This variable is then used to create the name of the SFS file and the annotation file. Note the use of back-quote characters in the first line. We can run this file using Tools|Run Program/Script and supply the name of the WAV file as the single argument.

Dealing with arguments

A common requirement is that item selections made within SFSWin should also be passed to the script. This allows the script to identify one or more specific items to be processed within the SFS file. Effectively we need the script to process command lines like:

script.sh -i 1.01 -i 4.01 file.sfs

In our first example, we'll show how to access all arguments and how to access just the last argument. We will pass all the arguments to the first program in the script and just the last argument to all the others. All arguments can be referred to in a script with "$*", while to get the last argument we set a variable equal to the index of the last argument (say "narg") and access "${!narg}". Here is a simple example:


narg=$#
txanal $*
fx ${!narg}
fxbuzz ${!narg}

For the example above, this would run


txanal -i 1.01 -i 4.01 file.sfs
fx file.sfs
fxbuzz file.sfs

If it is actually required to pass different option arguments onto different parts of the script, then we'll need to decode the options and set script variables to hold the values. The bash getopts command is an easy way to decode a set of options. In this example we will show how to use getopts to detect the presence of flags −I, −a and −b, with the last taking an argument:


while getopts "Iab:" option
do
    case $option in
    I)    echo "Option -I chosen" ;;
    a)    echo "Option -a chosen" ;;
    b)    echo "Option -b with argument $OPTARG chosen" ;;
    *)    echo "Unknown option: $option"
    esac
done
#
shift $(($OPTIND - 1))
echo "Unprocessed: $*"

After the options have been processed they are removed from the argument list, leaving only the unprocessed arguments.

Here is an example in which both item selection and another argument are processed. In this example, a speech signal and a phonetic transcription are supplied, and the result is a time-aligned annotation item. The process involves the generation of a formant synthesized speech signal and cross alignment with the original signal.


#!/bin/bash
# transcribe a file using formant synthesis
#
# default options
item=sp
trans="t e s t"
#
# decode options
while getopts "i:t:" option
do
    case $option in
    i)    item=$OPTARG ;;
    t)    trans=$OPTARG ;;
    *)    echo "Unknown option: $option"
    esac
done
shift $(($OPTIND - 1))
#
# get SFS file name
fname=$1
#
# create temporary file
hed -n c:/tmp/temp.sfs
remove -e c:/tmp/temp.sfs
#
# synthesize a signal from transcription
phosynth -t "$trans" -s -a c:/tmp/temp.sfs
soft c:/tmp/temp.sfs
#
# do spectral analysis and align
mfcc -r 200 -n 12 -e -l 100 -h 6000 -d1 c:/tmp/temp.sfs
mfcc -r 200 -n 12 -e -l 100 -h 6000 -d1 $fname
vcalign -m20 c:/tmp/temp.sfs $fname
#
# clean up
remove -ico $fname
rm c:/tmp/temp.sfs

Loops and conditions

Another important use for scripts is to automate the processing of multiple files. For example, it is often required to process a whole directory of files through the same SFS processing steps.

In this example we change a number of audio files stored in WAV format so that they would be compatible with burning an Audio CD. Whatever the input sample rate is, the output should be stereo files sampled at 44,100 samples/sec.


ipdir=c:/sfs/demo
opdir=c:/tmp
for f in $ipdir/*.wav
do
    echo "Processing $f"
    hed -n temp.sfs
    remove -e temp.sfs
    slink -isp -tWAV $f temp.sfs
    resamp -f 44100 temp.sfs
    g=`basename $f`
    sfs2wav -2 -o $opdir/$g temp.sfs
done

Our final script uses a conditional test on the existence of a file. The script displays all the SFS files in a directory, asking whether each is OK. If the file has been displayed, then a ".done" flag file is created. If the file is not OK, then a ".bad" flag file is created. This script can be stopped and restarted so that it skips over files that have already been processed.


for f in c:/data/ABI/sent/*.sfs
do
    g=`echo $f|sed s/.sfs//`
    if test ! -e $g.done
    then
        Eswin -Gw $f
        echo "OK (y/n)?"
        read x
        if test $x = "n"
        then
            touch $g.bad
        fi
        touch $g.done
    fi
done

References

The following information about shell scripting is available on the web:

6.2

SML Scripting

SML is the built-in SFS scripting language for speech measurement. The language is fully described in section 1.5 of this manual. Here we look at some of its more advanced features which allow it to be used as a general purpose scripting language for SFS.

Installation

The Speech Measurement Language interpreter sml.exe is part of the SFS installation, and does not need to be installed.

However for SML to access the individual programs within SFS you need to place the name of the SFS Program subdirectory into the search path for executable programs used by all Windows shells. To do this you will need to be logged on as administrator. Then within the Control Panel, find the "System" application, and within that the "Environment Variables" button. On Windows XP, the Environment Variables button is at the bottom of the "Advanced" tab screen. Under the "System Variables", select the "Path" variable and then "Edit". You will see that the Path variable contains a list of directory names separated by semi-colons. If SFS has been installed into "c:\Program Files\SFS" (the default), then add to the end of this string:

;c:\Program Files\SFS\Program

Otherwise add the name of the Program subdirectory where you installed SFS. OK the changes and close the control panel.

You can check that the path is correct by starting a Command Prompt window from the Start programs button, then typing "PATH". A complete list of directories is produced and the SFS Program directory should be included.

Standard SML functionality

In this section we will look at using the standard functionality of SML for measuring, calculating statistics, and plotting results.

In this script we calculate some basic statistics about a fundamental frequency item in a single file. We calculate the mean and standard deviation, and the percentage of time spent voicing.


/* fxbasic - get basic stats on an Fx contour */
main {
    var     t,f
    var     vt
    stat    fxstat

    vt = 0
    t = next(FX,-1)
    f = fx(t)
    while (f) {
        if (f > 0) {
            fxstat += f;
            vt = vt + 0.01;
        }
        t = t + 0.01
        f = fx(t)
    }
    print "Fx mean=",fxstat.mean:6:1
    print " +/- ",fxstat.stddev:5:2,"Hz.\n"
    print "Time spent voicing=",100*vt/t:6:2,"%\n"
}

To run this script on a single file, we select the FX item and file we want to process and choose the Tools|Run SML Script option in SFSwin.

If the Output listing file option is left blank the output from the program is displayed on the screen.

The previous example can be improved in a couple of ways: firstly we can change it so that it can be run on a number of files, and so compute the statistics of a number of contours; secondly we can find the median as well as the mean. We address these problems by storing the FX values in a histogram, and update the histogram over a number of files. Only when all files are processed do we calculate statistics:


/* fxstats - SML script to calculate FX statistics
   over multiple files */

var hist[0:1000]    /* histogram */
var nsamp        /* number of samples */
var nfile        /* number of files */

main {
    var    t,f

    print#stderr "Processing ",$filename,"\n"
    if (!select(FX)) break

    t = next(FX,-1)
    f = fx(t)
    while (f) {
        if ((50<=f)&&(f<=800)) {
            hist[f] = hist[f] + 1
            nsamp = nsamp + 1
        }
        t = t + 0.01
        f = fx(t)
    }
    nfile = nfile + 1
}

summary {
    var    i,j,nlose
    stat    val

    print "Processed : ",nfile:3," files\n"
    print "Total time: ",nsamp/100:5:1," s\n"

    /* calculate mean */
    for (i=50;i<=800;i=i+1) {
        for (j=0;j<hist[i];j=j+1) val += i
    }
    print "Mean Fx   :",val.mean:6:1
    print " +/- ",val.stddev:5:2," Hz\n"

    /* calculate median */
    nlose = nsamp/2
    for (i=50;(i<=800)&&(nlose>0);i=i+1) {
        while ((nlose>0)&&(hist[i]>0)) {
            nlose = nlose - 1
            hist[i] = hist[i] - 1
        }
    }
    print "Median Fx :",i:4," Hz\n"
}

You could run this script on a number of files using a COMMAND window, a CYGWIN shell window, or using the Tools|Run Program/Script dialog in SFSwin. In a COMMAND window, the command would look like this:

sml \sfs\examples\fxstats.sml *.sfs

SML is particularly convenient for analysing annotations. Its built-in functions for counting annotations and using them for locating events are easy to use. In this example we'll just report a count of all the different annotation types to be found in a set of files.


/* antypes.sml - find all annotation label types */

/* global data */
string labels[1:1000]
var    labelcount

/* main processing of files */
main {
    var    i,num;
    string    lab

    if (!select(AN)) break;
    num=numberof(".")
    for (i=0;i<num;i=i+1) {
        lab = matchn(".",i)
        /* add label to list */
        if (!entry(lab,labels)) {
            /* add name to list */
            labelcount = labelcount + 1
            labels[labelcount] = lab
        }
    }
}

/* summary processing */
summary {
    var i

    /* print table of labels found */
    print "Labels found:\n"
    for (i=1;i<=labelcount;i=i+1) print labels[i],"\n"
}

Another simple application of SML is to comvert between two annotation labelling systems. Although simple substitutions can be performed with the anmap(SFS1) program, a script is more flexible in coping with context-sensitive mappings. In this example, we use an SML switch statement to map between two labelling conventions:


/* arpa2sampa.sml - convert ARPA phoneme labels to SAMPA */

main {
    var n,num
    string old,new

    num=numberof(".")
    for (n=1;n<=num;n=n+1) {
        old=matchn(".",n)
        new=old
        switch (old) {
        case "aa":    new="A:"
        case "ae":    new="{"
        case "ah":    new="V"
        case "ao":    new="O:"
        case "aw":    new="aU"
        case "ax":    new="@"
        case "ay":    new="aI"
        case "ch":    new="tS"
        case "dh":    new="D"
        case "ea":    new="e@"
        case "eh":    new="e"
        case "er":    new="3:"
        case "ey":    new="eI"
        case "hh":    new="h"
        case "ia":    new="I@"
        case "ih":    new="I"
        case "iy":    new="i:"
        case "jh":    new="dZ"
        case "ng":    new="N"
        case "oh":    new="Q"
        case "ow":    new="@U"
        case "oy":    new="OI"
        case "sh":    new="S"
        case "sil":    new="/"
        case "ua":    new="U@"
        case "uh":    new="U"
        case "uw":    new="u:"
        case "y":    new="j"
        case "zh":    new="Z"
        }
        print timen(".",n),"\t",new,"\n"
    }
}

This program simply lists the new annotations to the output. You could capture the output into a file and reload with the program anload(SFS1). However, in the next section we'll see how to load the new labels directly into the SFS file.

Finally in this section, we'll show how SML can produce simple graphs. Here we'll plot a set of histograms of the values of the first 4 channels of some COEFF data set. These might, for example, be the first 4 filter channels of a voc19(SFS1) analysis. Because we don't know the range of values in advance, we make two passes over the data.


/* cohist.sml - demonstration of histogram plotting */

var    hist1[0:1000]
var    hist2[0:1000]
var    hist3[0:1000]
var    hist4[0:1000]
var    loval,hival
var    nbucket
file gop

function var min(x,y)
{
    var x,y
    if (x < y) return(x) else return(y)
}
function var max(x,y)
{
    var x,y
    if (x > y) return(x) else return(y)
}

function var getindex(val)
{
    var val
    var idx
    idx = trunc(nbucket*(val-loval)/(hival-loval))
    if (idx < 0) {
        return(0)
    }
    else if (idx > nbucket-1) {
        return(nbucket-1)
    }
    else {
        return(idx)
    }
}

main {
    var    t,i
    var    num
    var xval[1:2]

    select(CO)

    /* get range of values & # buckets */
    t=next(CO,-1)
    loval=co(5,t)
    hival=co(5,t)
    num=1
    while (t) {
        loval=min(loval,min(co(5,t),min(co(6,t),
                min(co(7,t),co(8,t)))));
        hival=max(hival,max(co(5,t),max(co(6,t),
                max(co(7,t),co(8,t)))));
        num=num+1;
        t=next(CO,t)
    }
    nbucket=1+trunc(sqrt(num));
    print#stderr "loval=",loval," hival=",hival
    print#stderr " nbucket=",nbucket,"\n"

    /* load values into histograms */
    t=next(CO,-1)
    while (t) {
        i=getindex(co(5,t))
        hist1[i]=hist1[i]+1;
        i=getindex(co(6,t))
        hist2[i]=hist2[i]+1;
        i=getindex(co(7,t))
        hist3[i]=hist3[i]+1;
        i=getindex(co(8,t))
        hist4[i]=hist4[i]+1;
        t=next(CO,t)
    }

    /* plot histograms to screen */
    gop=stdout
    plottitle(gop,"COEFF histograms from "++$filename);
    plotparam("vertical=2")
    plotparam("horizontal=2")
    plotparam("type=hist")
    xval[1]=loval
    xval[2]=hival
    plotxdata(xval,1)

    plotparam("title=Channel 1")
    plot(gop,1,hist1,nbucket)
    plotparam("title=Channel 2")
    plot(gop,2,hist2,nbucket)
    plotparam("title=Channel 3")
    plot(gop,3,hist3,nbucket)
    plotparam("title=Channel 4")
    plot(gop,4,hist4,nbucket)
}

This produces the following figure:

To send the graphic output directly to the printer rather than to the screen, change the setting of the gop variable as follows:


    /* plot histograms to printer */
    openout(gop,"|dig -p")

To send the graphic output directly to a graphics file rather than to the screen, change the setting of the gop variable as follows:


    /* plot histograms to a graphic file */
    openout(gop,"|dig -g -s 500x400 -o hist.gif")

Look at the manual page for dig(SFS1) for more information.

Reading and Measuring SFS data sets

Version 4 of SML introduced the new item variable type and a set of functions for reading SFS data sets into an item variable, for measuring and changing data sets, and for saving data sets back to SFS files.

The key functions are:

sfsgetitem(item,filename,itemstring)
This function reads an item specified by 'itemstring' in file 'filename' into item variable 'item'.
sfsdupitem(item1,item2)
This function makes a copy of item stored in variable 'item2' into variable 'item1'.
sfsnewitem(item,datatype,frameduration,offset,framesize,numframes)
This function creates a new empty item in variable 'item' of type 'datatype' (SP, LX, TX, FX, etc), with the time interval associated with each frame set to 'frameduration' and the overall time offset of the item set to 'offset', where each frame is made up of 'framesize' basic elements, and room should be reserved for 'numframes' frames of data. Although 'numframes' cannot be dynamically expanded, it is not necessary for all of the frames allocated by sfsnewitem() to be written to a file with sfsputitem(). The function sets the history to a default value based on the name of the script and the type of the output item.
sfsputitem(item,filename,numframes)
Stores the first 'numframes' frames of data in the data set referred to by 'item' into the file 'filename'.
sfsgetparam(item,param)
Gets the value of a numerical parameter with name 'param' from the data set header referred to by 'item'. Available parameters are: "numframes", "frameduration", "offset", "framesize", and "itemno".
sfsgetparamstring(item,param)
Gets the value of a string parameter with name 'param' from the data set header referred to by 'item'. Available parameters are: "history", "params", "processdate", and "itemno". Returns string value of parameter or ERROR.
sfsgetdata(item,frameno,index)
Returns a value from the data set referred to by 'item'. The value is taken at offset 'index' in frame number 'frameno' .
sfsgetstring(item,frameno)
Returns a string value from frame 'frameno' of the data set referred to by 'item'.
sfsgetfield(item.frameno,field)
Returns a value from the frame header for structured data types. The frame is referred to by number 'frameno', and the field is referred to by number 'field'.
sfsgetarray(item,start,count,array)
Loads a section of any 1-dimensional item into an array. Data is copied from the waveform or track referred to by 'item' starting at offset 'start' for 'count' samples into array 'array'.
sfssetdata(item,frameno,index,value)
Stores a particular numerical expression 'value' into a data set referred to by 'item' at frame number 'frameno' at frame offset 'index'.
sfssetfield(item,frameno,field,value)
Stores a particular numerical expression 'value' into the frame header of a frame number 'frameno' of data set 'item' at field position 'field'.
sfssetstring(item,frameno,string)
Stores a string expression into frame 'frameno' of the data set referred to by 'item'.
sfsprocessitem(item1,progname,item2,rettype)
Processes the data set referred to by 'item2' using the program and arguments in 'progname' and optionally loads a resultant data set of type 'rettype' into output item variable 'item1'. This function first saves item2 to a temporary file and runs the specified program on it. If 'rettype' is not an empty string then it is used to select the item to be loaded back in to item1.

These functions are described in more detail in the SML manual page and in section 5.12.

In this first example, we use the SFS item access routines to compare values across two data sets. This is quite hard to do without using item variables to store both data sets. Here we load the first and last FX item in a number of files and make some measurements about the voicing decisions:


/* vcomp.sml -- compare voicing in two Fx items */

var    voice[1:4]
item    fx1,fx2

main {
    var off1,dur1
    var off2,dur2
    var    i,num,f1,f2,t,idx;

    /* load first and last FX items in file */
    sfsgetitem(fx1,$filename,"fx.");
    sfsgetitem(fx2,$filename,"fx");

    /* get timing parameters */
    off1=sfsgetparam(fx1,"offset")
    dur1=sfsgetparam(fx1,"frameduration")
    off2=sfsgetparam(fx2,"offset")
    dur2=sfsgetparam(fx2,"frameduration")

    /* compare every pair of fx values */
    num=sfsgetparam(fx1,"numframes");
    for (i=0;i<num;i=i+1) {
        f1 = sfsgetdata(fx1,i,0);
        t = off1+i*dur1
        idx = trunc(0.5+(t-off2)/dur2);
        f2 = sfsgetdata(fx2,idx,0);
        if (f1 && f2) {
            if ((f1>0)&&(f2>0)) {
                voice[4] = voice[4]+1;
            }
            else if ((f1>0)&&(f2==0)) {
                voice[3] = voice[3]+1;
            }
            else if ((f1==0)&&(f2>0)) {
                voice[2] = voice[2]+1;
            }
            else {
                voice[1] = voice[1]+1;
            }
        }
    }
}

summary {
  var total

  total=voice[1]+voice[2]+voice[3]+voice[4]

  print "Quantity:\n"
  print "  Files   : ",$filecount:1,"\n"
  print "  Frames  : ",total:1,"\n"
  print "\nVoicing Analysis:\n"
  print "            Test v-   Test v+\n"
  print "  Ref v-  :",voice[1]:8,"  ",voice[2]:8,"\n"
  print "  Ref v+  :",voice[3]:8,"  ",voice[4]:8,"\n"
  print "  Accuracy:",100*(voice[1]+voice[4])/total:8:2,"%\n"
}

In this next example, we load a speech item, make a copy with the waveform reversed, then use sfsprocessitem to replay it:


/* reverse - reverse a speech item and play it out */

item    sp;
item    rsp;

main {
    var    i,numf,s;

    sfsgetitem(sp,$filename,"sp");
    numf=sfsgetparam(sp,"numframes");
    sfsnewitem(rsp,SP,sfsgetparam(sp,"frameduration"),
            sfsgetparam(sp,"offset"),1,numf);

    for (i=0;i<numf;i=i+1) {
        s = sfsgetdata(sp,i,0);
        sfssetdata(rsp,numf-i-1,0,s);
    }

    sfsprocessitem(rsp,"replay",rsp,"");
}

In this last example, we demonstrate access to annotation items:


/* anlist -- list annotation item */

item an

main {
    var    i,numf

    sfsgetitem(an,$filename,"an");
    numf=sfsgetparam(an,"numframes");

    for (i=0;i<numf;i=i+1) {
        print sfsgetfield(an,i,0),"\t"
        print sfsgetfield(an,i,1),"\t"
        print sfsgetstring(an,i),"\n"
    }
}

Creating SFS data sets

In this section we'll look at some scripts which create data sets.

Out first script creates a speech item that is a pulse train that falls in frequency from 150 to 100Hz over 2 seconds.


/* mkfall.sml - make a pulse train on a falling pitch */

var    dur
var    fx1
var    fx2
var    srate
item   sp

init {
    dur = 2
    fx1 = 150
    fx2 = 100
    srate = 10000
}

main {
    var i,numf
    var tx,fx

    /* make a new empty speech item */
    numf = trunc(dur*srate)
    sfsnewitem(sp,SP,1/srate,0,1,numf);

    /* make falling pitch pulse train */
    tx = trunc(srate/fx1)
    for (i=0;i<numf;i=i+1) {
        if (tx==0) {
            sfssetdata(sp,i,0,10000)
            fx = fx1 + i*(fx2-fx1)/numf
            tx = trunc(srate/fx)
        }
        else sfssetdata(sp,i,0,0)
        tx = tx-1
    }

    /* save to file */
    sfsputitem(sp,$filename,numf);

}

In our next example, we convert a COEFF item into a DISPLAY item. This script recreates the operation of the dicode(SFS1) program.


/* convert CO item into DI item */

item co;
item di;

main {
    var    i,j,numf,fsize;
    var e,maxe;

    /* get COEFF data set */
    sfsgetitem(co,$filename,"co");
    numf=sfsgetparam(co,"numframes");
    fsize=sfsgetparam(co,"framesize");

    /* make new DI data set */
    sfsnewitem(di,DI,sfsgetparam(co,"frameduration"),
            sfsgetparam(co,"offset"),fsize,numf);

    /* find largest value */
    maxe = sfsgetdata(co,0,0);
    for (i=0;i<numf;i=i+1) {
        for (j=0;j<fsize;j=j+1) {
            e = sfsgetdata(co,i,j);
            if (e > maxe) maxe=e;
        }
    }
    print "Maximum energy=",maxe,"dB\n";

    /* do conversion of top 50dB into 16 grey levels */
    maxe = maxe-50;
    for (i=0;i<numf;i=i+1) {
        sfssetfield(di,i,0,sfsgetfield(co,i,0));
        sfssetfield(di,i,1,sfsgetfield(co,i,1));
        for (j=0;j<fsize;j=j+1) {
            e = sfsgetdata(co,i,j);
            if (e > maxe) {
                sfssetdata(di,i,j,16*(e-maxe)/50);
            }
            else {
                sfssetdata(di,i,j,0);
            }
        }
    }

    /* save back to file */
    sfsputitem(di,$filename,numf);
}

Lastly, here is a more complex example in which we process a speech signal into a spectrogram, making use of the SML function fft():


/* spblock - example of block processing of speech */

item sp;                /* input speech item */
item co;                /* output spectral coefficients */
var window[0:10000];    /* input window */
var mag[0:10000];       /* spectral magnitudes */
var phase[0:10000];     /* spectral phases */

main {
    var    numf;
    var    fsize;
    var    fdur;
    var    i,j,f;
    var    xsize,cnt;

    /* load speech item from current file */
    sfsgetitem(sp,$filename,"sp.");

    /* get processing parameters */
    numf = sfsgetparam(sp,"numframes");
    fdur = sfsgetparam(sp,"frameduration");
    fsize = 0.025/fdur;        /* 25ms window */
    xsize = 16;                /* FFT size */
    while (xsize < fsize) xsize = xsize*2;
    xsize = xsize/2;

    /* make up a coefficients item */
    sfsnewitem(co,CO,fdur,0,xsize,1+2*numf/fsize);

    /* process in blocks */
    f=0;
    for (i=0;(i+fsize)<numf;i=i+fsize/2) {
        sfsgetarray(sp,i,fsize,window);

        cnt=fft(window,fsize,mag,phase);
        if (cnt!=xsize) abort("size error");

        sfssetfield(co,f,0,i);
        sfssetfield(co,f,1,fsize);
        sfssetfield(co,f,2,0);
        sfssetfield(co,f,3,0);
        sfssetfield(co,f,4,0);
        for (j=0;j<xsize;j=j+1) {
            sfssetdata(co,f,j,20*log10(mag[j]));
        }
        f=f+1;
    }

    /* save spectral coefficients back to file */
    sfsputitem(co,$filename,f);
}

6.3

MATLAB Scripting

MATLAB is a commercial computer programming language and environment from Mathworks Inc. It is an easy to use language for signal processing, and is supplied with implementations of major signal processing functions. Unfortunately, MATLAB only has support for one audio file format, and only has relatively primitive support for building interactive applications. By combining the mathematical power of MATLAB with the interactivity of SFS, new applications of greater flexibility and utility can be constructed. We give some examples in this section.

Installation

MATLAB should be installed from CD using its normal set up procedure. Normally this will mean installation to a folder on the C:\ drive of Windows.

The MATLAB/SFS API can either be installed separately (from http://www.phon.ucl.ac.uk/resource/sfs/), or comes as part of the most recent SFS distributions. The MATLAB/SFS API is usually installed in the matlab subdirectory of the main SFS installation directory.

To call the MATLAB/SFS API functions, it is necessary to put the API directory name on the MATLAB search path for functions. To do this, start MATLAB and choose the File/Set Path menu option. Click on Add Folder, and browse to the SFS MATLAB API directory. Click Save, and Close.

When you are calling MATLAB functions in the examples below, it is also necessary that the directories containing the function definition files (.m files) are also included on the MATLAB search path.

Creating and exploring SFS files

In this section we shall look at the API functions sfsfile and sfsgetitem. We'll demonstrate these operating within the MATLAB environment.

The sfsfile() function is defined as follows:


function ilist=sfsfile(fname,func)
% SFSFILE performs file level operations on an SFS file.
%
%   ilist=SFSFILE('file.sfs') will return a list of the item
%   numbers of the data sets present in file.sfs.  These
%   numbers may be used to read the data sets using the
%   function SFSGETITEM. If the file does not exist an error
%   code is returned: -1 for file not found, -2 if file could
%   not be opened.  If the file is empty, a zero-length array
%   is returned.
%
%   status=SFSFILE('file.sfs','create') will attempt to create
%   a new SFS file called file.sfs.  If the file already exists,
%   an error is returned.
%
%   status=SFSFILE('file.sfs','empty') will empty file.sfs of
%   all its current contents.  The main file header is retained.

The sfsgetitem() function is defined as follows:


function [header,data]=sfsgetitem(filename,itemsel)
% SFSGETITEM reads in a data set from an SFS file.
%
%   [h,d]=SFSGETITEM(filename,itemsel) will read the data set
%   header into a structure h, and the data set itself into the
%   array d.  The itemsel string can be either a string version
%   of the item number (use NUM2STR() to convert item number),
%   or an item type short code, or one of the other item
%   selection schemes as described in the SFS manual.  Use the
%   SFSFILE() function to get a list of item numbers in the 
%   file. Look at the SFS programmers manual for information 
%   about the meaning of the fields in the header structure.
%
%   h=SFSGETITEM(filename,itemsel) just reads the header of the
%   data set.
%
%   The following fields are defined in the header structure:
%       history       - process history
%       params        - process parameters
%       processdate   - process date
%       datatype      - data set main type
%       subtype       - data set number
%       floating      - 1=floating,0=integer,-1=structured
%       datasize      - element data size
%       framesize     - number of elements per frame
%       numframes     - number of frames
%       length        - total length of data set in bytes
%       frameduration - duration of one frame
%       offset        - time offset of data set
%       datapresent   - present/deleted/linked flag
%       windowsize    - number frames in one window
%       overlap       - number of frames in window overlap
%       lxsync        - pitch synchronous
%       lastpos       - last known sample position
%
%   The data format for common SFS data types is as follows:
%      1. SPEECH:  column vector
%      2. LX:      column vector
%      3. TX:      column vector
%      4. FX:      column vector
%      5. ANNOT:   array of structures (posn, size, label)
%      6. SYNTH:   array of numframes rows, framesize columns
%      9. DISPLAY: array of numframes rows, framesize+2 columns
%                  col1=posn, col2=size
%     10. VU:      array of numframes rows, framesize+5 columns
%                  col1=posn, col2=size, col3=flag, col4=mix,
%                  col5=gain
%     11. COEFF:   array of numframes rows, framesize+5 columns
%                  col1=posn, col2=size, col3=flag, col4=mix,
%                  col5=gain
%     12. FORMANT: array of numframes rows, framesize+5 columns
%                  col1=posn, col2=size, col3=flag, col4=mix,
%                  col5=gain, formant data is stored in column
%                  triplets: freq,amp,band
%     16. TRACK:   column vector

The following MATLAB function tests to see if a file exists and is an SFS file. If it is an SFS file then it lists the data sets in the file.


function summary(fname)
% SUMMARY Display summary of data items in an SFS file
%
%   SUMMARY('file.sfs') will produce a listing of the item
%   numbers, number of frames and processing history for
%   each item in file.sfs

ilist=sfsfile(fname);
if (ilist < 0)
    fprintf('error accessing "%s"\n',fname)
else
    fprintf('SFS file "%s" contains:\n',fname)
    for i=1:length(ilist)
      h=sfsgetitem(fname,num2str(ilist(i)));
      fprintf('%2d.%02d %6d frames %s\n',...
        h.datatype,h.subtype,h.numframes,h.history);
    end
end

The following MATLAB function tries to create an empty SFS file of a given name. If the file exists and is an SFS file, then it is just emptied.


function newsfsfile(fname)
% NEWSFSFILE(fname) attempts to create a new SFS file
%
%   If the file fname exists and is an SFS file, then it
%   is just emptied of its current contents

status=sfsfile(fname);
if (status == -1)
    % doesn't exist
    sfsfile(fname,'create');
elseif (status == -2)
    % exists but not SFS
    delete(fname);
    sfsfile(fname,'create');
else
    % exists and is SFS
    sfsfile(fname,'empty');
end

Reading and Measuring Data Sets

In this section we continue to look at functions that operate within the MATLAB environment, and which read from and measure data sets created by SFS.

In this example, we load a speech signal from an SFS file and replay some part of it according to some additional arguments. The help text gives details


function replay(fname,item,arg3,arg4,arg5)
% REPLAY replay an audio signal from an SFS file
%
%   REPLAY(fname,item) replays a SPEECH or LX item specified
%   by the item parameter from the SFS file fname.  Replay
%   is performed by the WAVPLAY function that waits until
%   audio replay is complete before returning.
%
%   REPLAY(fname) replays the last SPEECH item in the file.
%
%   REPLAY(fname,item,start,stop) replays the item starting
%   at time start and ending at time stop.
%
%   REPLAY(fname,item,start) replays the item starting
%   at time start and ending at the end of the file.
%
%   REPLAY(fname,item,anitem,anstart,anstop) replays the
%   item starting at the time of an annotation called anstart
%   and ending at an annotation anstop.  Annotations are
%   taken from the item anitem in the file.

stime=0;
etime=-1;
if (nargin==5) & ischar(arg3)
    % load annotations to find start & stop times
    [ah,ad]=sfsgetitem(fname,arg3);
    % find start
    s=1;
    for i=1:length(ad)
        if strcmp(ad(i).label,arg4)
            stime=ad(i).posn*ah.frameduration+ah.offset;
            s=i;
            break;
        end;
    end;
    % find stop following
    for i=s+1:length(ad)
        if strcmp(ad(i).label,arg5)
            etime=ad(i).posn*ah.frameduration+ah.offset;
            break;
        end;
    end;
elseif (nargin==4)
    % start and stop times specified
    stime=arg3;
    etime=arg4;
elseif (nargin==3)
    % start specified
    stime=arg3;
elseif (nargin<2)
    % no item specified
    item='sp';
end;
% load signal
[h,d]=sfsgetitem(fname,item);
if (etime==-1)
    etime=h.numframes*h.frameduration+h.offset;
end;
% find starting and ending index
sidx=1+floor((stime-h.offset)/h.frameduration);
sidx=min(max(sidx,1),h.numframes);
eidx=floor((etime-h.offset)/h.frameduration);
eidx=min(max(eidx,1),h.numframes);
% replay
wavplay(d(sidx:eidx),1.0/h.frameduration,'sync');

Creating SFS data sets

To create new data sets in SFS files requires the use of the SFS/MATLAB API functions sfsnewitem and sfsputitem.

The sfsnewitem function is defined as:


function [header,data]=sfsnewitem(datatype,...
               frameduration,offset,framesize,numframes)
% SFSNEWITEM creates an empty SFS data set of a given type
% and size
%
%   [h,d]=sfsnewitem(datatype,frameduration,offset,framesize,
%   numframes) creates an SFS item header structure in the
%   variable h, and a blank data set in the variable d.  The
%   datatype parameter sets the main data type (1=speech,2=lx,
%   3=tx,4=fx, etc) - see the SFS user manual for complete
%   list. The frameduration parameter sets the basic sampling
%   time unit - all internal times are integer multiples of
%   this time.  The offset parameter sets a single global
%   temporal offset for the whole item.  The framesize
%   parameter sets the number of data items used in each frame
%   of data (not used for vector data). The numframes
%   parameter sets the number of frames required in the blank
%   data set.
%
%   It is recommended that after using SFSNEWITEM to create
%   a new header structure, the history field is initialised
%   with a new and unique string describing the processing
%   performed. The default history string has the form
%      matlab/<item-type>(script=<value of progname>)
%   where progname is an assumed global variable.
%
%   Refer to SFSGETITEM for the format of data sets for
%   different SFS types.

The sfsputitem function is defined as:


function numf=sfsputitem(filename,header,data,numframes)
% SFSPUTITEM stores a data set into an SFS file
%
%   numf=sfsputitem('file.sfs',h,d,numframes) stores the SFS
%   item described by the header structure h and the data set
%   d into file.sfs.  The numframes parameter sets the number
%   of frames (rows) of data to be written to the file over-
%   riding the value in h.numframes.  The number of frames
%   actually written is returned.
%
%   numf=sfsputitem('file.sfs',h,d) stores the data set as
%   above, but using the number of frames specified in
%   h.numframes.
%
%   Refer to SFSGETITEM for the format of data sets for
%   different SFS types.

In this example, we create apply a filter to a speech signal, and save the filtered signal as a new speech item in the SFS file.


function sfsfilter(fname,item,hertz,ftype)
% SFSFILTER Applies a filter to a speech signal
%
%   SFSFILTER(fname,item,hertz,ftype) applies an 8th-order
%   Butterworth filter to the speech signal 'item' in the SFS
%   file fname.  The hertz parameter gives the cut-off frequency
%   (or frequencies) in hertz.  The ftype parameter selects the
%   filter type from: 'low'=low-pass, 'high'=high-pass, 'band'=
%   band-pass, 'stop'=band-stop.  The filtered speech is saved
%   back into the SFS file.
%
%   For example:
%      sfsfilter('file.sfs','1.01',1000,'low');
%      sfsfilter('file.sfs','sp',[1000 2000],'band');

%
% get item from SFS file (h=header, d=data set)
%
[h,d]=sfsgetitem(fname,item);
%
% make filter (uses butter() design from signal-processing
% toolbox)
%
if strcmp(ftype,'low') | strcmp(ftype,'band')
    [b,a]=butter(8,2*hertz*h.frameduration);
else
    [b,a]=butter(8,2*hertz*h.frameduration,ftype);
end;
%
% make a new item header as copy of input item header
%
oh=sfsnewitem(h.datatype,h.frameduration,h.offset,...
                               h.framesize,h.numframes);
%
% create a suitable processing history string
%
if (length(hertz)==2)
 oh.history=sprintf(...
 'matlab/SP(%d.%02d;script=sfsfilter,hertz=%g:%g,ftype=%s)',
 h.datatype,h.subtype,hertz(1),hertz(2),ftype);
else
 oh.history=sprintf(...
 'matlab/SP(%d.%02d;script=sfsfilter,hertz=%g,ftype=%s)',
 h.datatype,h.subtype,hertz,ftype);
end;
%
% apply filter to input signal
%
od=filter(b,a,d);
%
% save filtered signal to file
%
sfsputitem(fname,oh,od);

In this example we calculate a spectrogram of a speech signal and save it either to a DISPLAY or to a COEFF item in the file:


function spectrogram(fname,item,wtime,otype)
% SPECTROGRAM Calculates a spectrogram of a signal
%
%   SPECTROGRAM(fname,item) calculates and stores a
%   wide-band spectrogram from a speech item in an
%   SFS file fname, storing the spectrogram as a
%   DISPLAY item in the file.
%
%   SPECTROGRAM(fname,item,wtime) calculates a
%   spectrogram using analysis windows of size wtime
%   seconds (0.003=wide band, 0.020=narrow-band).
%
%   SPECTROGRAM(fname,item,wtime,'coeff') calculates
%   a spectrogram as above but stores the results as
%   a COEFF item in the file.

%
% set defaults
%
if (nargin < 4) otype='display'; end;
if (nargin < 3) wtime=0.003; end;
%
% get item from SFS file (h=header, d=data set)
%
[h,d]=sfsgetitem(fname,item);
%
% get FFT size
%
n=256;
while ((n*h.frameduration) <= wtime) n=2*n; end;
%
% get hamming window
%
len=round(wtime/h.frameduration);
w=hamming(len);
%
% perform spectrogram calculation
%
y = filter([1, -0.95], 1, d);       % pre-emphasis
[b,f,t]=specgram(y,n,1.0/h.frameduration,w);
numf=length(t);
%
% convert to dB (and rotate so time goes down rows)
%
e=20.0*log10(abs(b)'+eps);
%
% build SFS item to hold spectrogram
%
if strcmp(otype,'coeff')
 oh=sfsnewitem(11,h.frameduration,h.offset,n/2,numf);
 oh.history=sprintf(...
  'matlab/CO(%d.%02d;script=spectrogram,wtime=%g)',...
  h.datatype,h.subtype,wtime);
 od=[round(t/h.frameduration) len*ones(numf,1) ...
  zeros(numf,1) zeros(numf,1) zeros(numf,1) e];
else
 oh=sfsnewitem(9,h.frameduration,h.offset,n/2,numf);
 oh.history=sprintf(...
  'matlab/DI(%d.%02d;script=spectrogram,wtime=%g)',...
  h.datatype,h.subtype,wtime);
 e=max(16*(e-max(max(e))+50)/50,zeros(size(e)));
 od=[round(t/h.frameduration) len*ones(numf,1) e];
end
%
% store in SFS file
%
sfsputitem(fname,oh,od);

Lastly in this section we convert a fundamental frequency track to a set of annotations (I'm not sure why ...)


function fx2an(fname,item)
% FX2AN Creates annotations from FX contour in SFS file
%
%   FX2AN(fname,item) loads an FX item from the SFS file
%   fname. It then builds an annotation item that
%   indicates the starts and ends of each voiced region,
%   with the labels indicating the mean FX in each region.
%
%   FX2AN(fname) performs the same operation on the last
%   FX item in the file fname.

% load FX data
if (nargin<2) item='fx'; end;
[h,d]=sfsgetitem(fname,item);
% put the starts and lengths of regions into an array
rcnt=1;          % number of regions
r(rcnt,1)=1;     % start index of first region
r(rcnt,2)=0;     % length of first region
for i=2:h.numframes
    if (d(i-1)==0) & (d(i)>0)
        % end of voiceless region
        r(rcnt,2)=i-r(rcnt,1);
        rcnt=rcnt+1;
        r(rcnt,1)=i;
    elseif (d(i-1)>0) & (d(i)==0)
        % end of voiced region
        r(rcnt,2)=i-r(rcnt,1);
        rcnt=rcnt+1;
        r(rcnt,1)=i;
    end;
end;
r(rcnt,2)=h.numframes-r(rcnt,1);
% find mean values of each section
for i=1:rcnt
    m(i)=mean(d(r(i,1):r(i,1)+r(i,2)-1));
end;
% build output annotations
[oh,od]=sfsnewitem(5,h.frameduration,h.offset,1,rcnt);
oh.history=sprintf(...
    'matlab/AN(%d.%02d;script=fx2an)',h.datatype,h.subtype);
for i=1:rcnt
    od(i).posn = r(i,1)-1;
    od(i).size = r(i,2);
    od(i).label= num2str(m(i),'%.2fHz');
end;
sfsputitem(fname,oh,od);

Running MATLAB applications from SFSWin

The examples in the previous sections were functions which took the name of SFS files as arguments alongside item numbers and other operating parameters. However these functions can only be run from within MATLAB programming environment, which can be restrictive and inconvenient.

In this section we shall see how these scripts can be adapted so that they may be called from the SFSWin program, using MATLAB as simply a script processing engine, just as we have used the BASH shell and SML as engines in sections 6.1 and 6.2.

The key to calling MATLAB functions from SFSWin is the MATLABEX program. This program takes arguments specified by command line switches in the same way as other SFS programs, but then calls the MATLAB engine to execute a particular function. The MATLABEX command line looks like this:

matlabex funcname (-i item) [ parameters ] sfsfile

For example:

matlabex sfsecho -i 1.01 -i 5.01 -p 23 -h "coeff" -q test.sfs

Note that command line parameters must be individually specified with a leading '-' and that values must be separated from the parameter names with a space.

When MATLABEX calls a MATLAB function, that function must be written as an m-file and saved in a directory on the MATLAB search path. The function should be declared as:

function exitcode=funcname(sfsfilename,itemselections,params)

The sfsfilename parameter is the name of the SFS file. The itemselections parameter is a vector of item numbers. The params parameter is a vector of structures with string fields name = parameter name, value = parameter value. Here is a suitable script:


function status=sfsecho(fname,ilist,plist)
% SFSECHO echoes the input parameters from MATLABEX
%

fprintf('SFSECHO called with parameters:\n');
fprintf('\tfname : %s\n',fname);
fprintf('\tilist :');
fprintf(' %5.2f',ilist);
fprintf('\n');
fprintf('\tplist :');
for i=1:length(plist)
    fprintf(' %s=%s',plist(i).param,plist(i).value);
end;
fprintf('\n');
status=0;
If this function were called with

matlabex sfsecho -i sp. -i an -p param -Q \sfs\demo\demo.sfs

This would lead to the output:


SFSECHO called with parameters:
        fname : \sfs\demo\demo.sfs
        ilist :  1.01  5.02
        plist : -p=param -Q=
Script returns code: 0

Let us now write a wrapper for the sfsfilter() function we met in the last section. The idea now is to take command line arguments specifying cut off frequencies and convert these into suitable arguments for sfsfilter().

We shall use parameters "-l lowfreq" to specify a low-frequency edge, and "-h highfreq" to specify a high frequency edge.


function exitcode=sfsfilterex(fname,ilist,plist)
% SFSFILTEREX runs the SFSFILTER function for MATLABEX
%
%   Options:
%      -i item         select input speech item
%      -l lowfreq      specify a low frequency edge
%      -h highfreq     specify a high frequency edge
%
%   For example:
%      -l 500          low-pass at 500Hz
%      -h 1000         high-pass at 1000Hz
%      -l 100 -h 200   band-pass between 100 and 200
%      -l 200 -h 100   band-stop between 100 and 200

%
% sort out items
%
if (isempty(ilist)) ilist=[1]; end;
%
% sort out filter
%
lofreq=-1;
hifreq=-1;
for i=1:length(plist)
    if (strcmp(plist(i).param,'-l'))
        lofreq=str2num(plist(i).value);
    end;
    if (strcmp(plist(i).param,'-h'))
        hifreq=str2num(plist(i).value);
    end;
end;
%
% call function
%
if ((lofreq==-1) & (hifreq==-1))
    fprintf('no cut-off frequencies specified!\n');
elseif (hifreq==-1)
    sfsfilter(fname,num2str(ilist(1)),lofreq,'low');
elseif (lofreq==-1)
    sfsfilter(fname,num2str(ilist(1)),hifreq,'high');
elseif (lofreq < hifreq)
    sfsfilter(fname,num2str(ilist(1)),[lofreq hifreq],'band');
else
    sfsfilter(fname,num2str(ilist(1)),[hifreq lofreq],'stop');
end
%
exitcode=0;

This code can be run in MATLAB with:


>> plist(1).param='-l';
>> plist(1).value='500';
>> sfsfilterex('test.sfs',1.01,plist);

Or this code could be run from within SFSwin using the Tools|Run Program/Script command:

Finally, we build a complete MATLABEX script for the modulation and demodulation of signals using the modulate() and demod() functions from the MATLAB signal-processing toolbox. This shows some tips and tricks for dealing with signals.


function exitcode=modulation(fname,ilist,plist)
% MODULATION - performs FM and AM modulation and demodulation
%
%  MODULATION(fname,ilist,plist) processes the speech item 
%  specified in ilist in the SFS file fname, using parameters
%  specified in plist.
%
%  MODULATION is designed to be run by MATLABEX with the
%  following command line parameters:
%
%    -m fcarrier      Modulate using given carrier frequency
%    -d fcarrier      Demodulate using given carrier frequency
%    -t "am"          Amplitude modulation (default)
%    -t "fm"          Frequency modulation
%    -t "pm"          Phase modulation

%
% sort out items
%
if (isempty(ilist)) ilist=[1]; end;
%
% sort out options
%
fcarrier=10000;
mtype='am';
demodulate=0;
%
for i=1:length(plist)
    if (strcmp(plist(i).param,'-m'))
        % modulate
        fcarrier=str2num(plist(i).value);
        demodulate=0;
    end;
    if (strcmp(plist(i).param,'-d'))
        % demodulate
        fcarrier=str2num(plist(i).value);
        demodulate=1;
    end;
    if (strcmp(plist(i).param,'-t'))
        % modulation type 
        mtype=plist(i).value;
    end;
end;
%
% load speech signal
%
[h,d]=sfsgetitem(fname,num2str(ilist(1)));
srate=1.0/h.frameduration;
%
% upsample to 2*(carrier + bandwidth)
%
if (demodulate==0)
   [num,den]=rat(2*(fcarrier+srate/2)/srate,0.001)
   d=resample(d,num,den);
   srate=num*srate/den;
   fprintf('Up-sampled to %g samples/sec\n',srate);
end
%
% make output header
%
oh=sfsnewitem(h.datatype,1.0/srate,h.offset,1,length(d));
%
% do processing
%
if (demodulate)
    y=demod(d,fcarrier,srate,mtype);
    oh.history=sprintf('demodulate(%d.%02d;carrier=%g,type=%s)',...
        h.datatype,h.subtype,fcarrier,mtype);    
else
    y=modulate(d,fcarrier,srate,mtype,(fcarrier/srate)*2*pi);
    oh.history=sprintf('modulate(%d.%02d;carrier=%g,type=%s)',...
        h.datatype,h.subtype,fcarrier,mtype);
end;
%
% downsample to 2*bandwidth
%
if (demodulate)
   [num,den]=rat((2*(srate/2-fcarrier))/srate,0.001)
   y=resample(y,num,den);
   srate=num*srate/den;
   fprintf('Down-sampled to %g samples/sec\n',srate);
   oh.frameduration=1.0/srate;
end
% 
% save to file
%
sfsputitem(fname,oh,y,length(y));
exitcode=0;


© 2004 Mark Huckvale University College London