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;
|