Download as pdf or txt
Download as pdf or txt
You are on page 1of 51

Introduction to Computer Programming with MATLAB

Aims
The aims of this short course are to introduce the elements and practicalities of computer programming through
the MATLAB mathematical computing environment.

Prerequisites
Familiarity with using Windows applications, and with the use of a scientific calculator. No previous
programming experience is required.

Outcome
At the end of the course students should be confident about using MATLAB in their own project work, and
should feel more prepared to tackle other procedural languages for computing, such as C++ or Visual Basic.

Text books
If you have access to a computer at home, you are recommended to buy a copy of "The student edition of
MATLAB" version for Windows. The newest version is release 14, but release 13 would be just as good for
this course.
 MATLAB & Simulink Student Version Release 14, ISBN 0-9755787-2-3
 MATLAB Student Version Release 13, ISBN 0-9672195-9-0
The student edition contains a complete MATLAB programming environment and costs about £45. It comes
with an introductory guide. There are also a large number of MATLAB books available, one that is
recommended is "Essential MATLAB for scientists and engineers" by Brian D. Hahn, Arnold, 2001, ISBN 0-
7506524-0-3.

The student edition of MATLAB may be missing the Signal Processing toolbox files needed for lectures 8, 9
and 10. Replacements for the missing functions can be downloaded as a zip file. Unpack the contents of this
file into a DSP direcory in your MATLAB installation, then add the DSP directory to the search path (under
File/Set Path).

There is a free MATLAB simulator called Octave available at http://www.octave.org/, however installation of
this on Windows requires some computer skills. Also not all of the MATLAB functionality we will use on the
course is available in Octave.

Lab Resources
We will equip the computers in Room G5 with MATLAB. There should usually be at least one machine
available for student use at any time of day. Students are recommended to store their program files on the
central server, so that they will not be tied to a specific machine.

Web Resources
A 35 page MATLAB primer by Kermit Sigmon can be downloaded from the course web site. A collection of
online resources can be found at: http://www.glue.umd.edu/~nsw/ench250/matlab.htm. The MATLAB home
page (from the manufacturers) is at: http://www.mathworks.com/

Syllabus
1. Introduction to Programming
1.1. Components of a computer
1.2. Working with numbers
1.3. Machine code
1.4. Software hierarchy
2. Programming Environment
2.1. MATLAB Windows
2.2. A First Program
2.3. Expressions, Constants
2.4. Variables and assignment statement
2.5. Arrays
3. Graph Plots
3.1. Basic plotting
3.2. Built in functions
3.3. Generating waveforms
3.4. Sound replay, load and save
4. Procedures and Functions
4.1. Arguments and return values
4.2. M-files
4.3. Formatted console input-output
4.4. String handling
5. Control Statements
5.1. Conditional statements: If, Else, Elseif
5.2. Repetition statements: While, For
6. Manipulating Text
6.1. Writing to a text file
6.2. Reading from a text file
6.3. Randomising and sorting a list
6.4. Searching a list
7. GUI Interface
7.1. Attaching buttons to actions
7.2. Getting Input
7.3. Setting Output
8. Discrete Linear Systems
8.1. Characterisation of linear systems
8.2. Finite Impulse Response filters
8.3. Infinite Impulse Response filters
8.4. Frequency response
9. Spectral Analysis
9.1. Filterbank analysis
9.2. Fourier analysis
9.3. Spectrograms
9.4. Filterbank synthesis
10. Speech Signal Analysis
10.1. Fundamental frequency estimation – frequency domain
10.2. Fundamental frequency estimation – time domain
10.3. Formant frequency estimation

Extra lecture: Program Development

Assessment
Students will be asked to produce a computer program to specification. The aim will be to demonstrate an
ability to transform a problem into a design, and a design into an operational program. Assessment will be
based on the design and implementation.

Timetable
Lectures and Practical Classes: Mondays 1400-1600 Wolfson House.
Coursework Set: End of autumn term.
Coursework Due in: After reading week, spring term
UCL Department of Phonetics and Linguistics

Introduction to Computer Programming with MATLAB

Lecture 1: Introduction to Programming


Objectives

By the end of the session you should:


 be able to name the basic components of a computer
 understand that computers work with numbers for everything
 be able to describe the basic operations of a computer
 be able to explain how a computer is different to a large calculator
 appreciate the 'software hierarchy' of machine code, assembler, high-level languages and scripts.

Outline

1. Components

At the heart of a digital computer is the central processing unit (CPU) which performs the logical and
arithmetical operations on streams of numbers as controlled by a “program” which is itself a stream of
numbers. The CPU has privileged access to the computer memory which can be thought of a large number of
pigeonholes in which numbers can be stored and later retrieved. The CPU can also send and receive numbers
from a set of input-output ports. These act as gateways from the CPU to other devices, such as keyboards,
displays, printers, disks, analogue I/O devices, or network connections to other computers. Collectively these
external devices are called peripherals. The CPU can select a peripheral and control its operation by sending
appropriate number streams to the port to which it is connected.
2. Working with numbers

The CPU can only manipulate numbers, so all pieces of information that we want the computer to manipulate
must be stored as numbers. Such information includes: numerical values, keyboard or display characters,
arithmetic operations, the addresses of memory locations, the addresses of ports, and even the possible set of
operations that the CPU can perform. All these must be stored as numbers.

The philosophy of numbers for anything and everything is the basis of the power of the computer. When the
CPU does a calculation and generates a numerical answer, that number need not be just the size of something,
but it can be a character, a memory address or even the next operation of the CPU. It is like a calculator doing a
calculation and coming up with a result that meant the calculator should next perform the square root
calculation. i.e. producing a result that was an operation, not just a value. Computers have programs, called
compilers, which produce numerical results which are nothing less than other programs.

3. Machine code

The essential operations of a CPU are actually very limited. A CPU can
1. Load from memory. Given a memory address, retrieve the number stored at that address.
2. Store to memory. Given a memory address and a number, store the number at that address.
3. Load from port. Given a port address, get a number from the port.
4. Store to port. Given a port address and a number, send the number to the port.
5. Arithmetic. Given one or more numbers, perform some basic arithmetic, such as add, subtract,
multiply, divide and other logical operations such as less-than, equals or greater than. Result is
another number.
6. Test. Given a number, change the status of the CPU according to properties of the number, e.g.
equal to zero, less than zero
7. Conditional jump. Depending on the status of the CPU jump to a different list of operations stored in
the memory. For example, if the last test operation returned “equal to zero”, then start executing the
instructions at memory location 1000, otherwise start executing the instructions at memory location
2000.

4. Software hierarchy

The lowest level description of a computer program is just the sequence of numbers which encode the basic
CPU operations. This level is called machine code. Machine code is specific to a given CPU manufacturer
and often specific to a given model type (for example the Pentium CPU has some codes not used by earlier
8086 CPUs). Machine code is very difficult for a human to read or write, so the lowest level of programming
done by humans is in a language in which each basic operation is given a mnemonic code called assembly
language. Humans can read and write using assembly language which can be converted into machine code
using an assembler. Assembly language, like machine code is often specific to a particular CPU manufacturer
or model.

The development of high-level languages meant that humans could program using a formalism that was closer
to their conceptual models of the data being manipulated: characters, real numbers, lists, tables or database
records. Such languages are easier for humans to learn and to use, and furthermore they tend to be available
across different computers; with each manufacturer supplying a conversion program between the high-level
language and the assembly language for their CPU. Examples of high-level languages are Fortran, Pascal,
Basic, C, C++, Java and MATLAB.

Modern computer systems need to deal with complex tasks involving multiple programs interacting
simultaneously, and the sharing of access to files on disks, to network resources and displays. To cope with
these demands, manufacturers supply operating systems (e.g. Windows, Linux), which are themselves
programs which help the user operate the computer and run other application programs. Often individual
application programs need to work together to achieve an objective: for example a word processing application
might call on a drawing package or on a spreadsheet program to do some specific processing within a
document. This idea of combining programs is called scripting, where the specifications for which programs
are to be executed and how they should interact is specified in a script.

Reading
 "Structure and Operation of Digital Computers", Encyclopaedia Britannica, 1995.
 “Computer Architecture”, Computational Science Education Project.
http://csep1.phy.ornl.gov/ca/ca.html.
 “How microprocessors work”, http://computer.howstuffworks.com/microprocessor.htm

Exercises
1. Obtain an account on the Departmental computer systems
2. Log on to a PC in the department with your account name and password.
3. Open the “Network Neighbourhood” folder and find the machine called “holtz”. Find your two allocated
folders (directories) on holtz. The first should have your name, the second should be called “www”.
4. Create a basic home page on the web for your account using Word, FrontPage or Mozilla Composer. Create
a file called “index.html” and save it into your “www” directory on holtz.. Test out the page using Internet
Explorer at
http://www.phon.ucl.ac.uk/home/<your_account_name>/
5. Create a second page. Make a “hyperlink” between your home page and the second page. Check that the
link works in Internet Explorer.
6. Find an image on the web that you like. Save the image to your “www” directory. Make a link to the
image from your home page. Check that it displays.
UCL Department of Phonetics and Linguistics

Introduction to Computer Programming with MATLAB

Lecture 2: Programming Environment


Objectives

By the end of the session you should:


 create and execute simple programs in MATLAB
 understand the difference between constants, variables and expressions
 know how to use the assignment statement
 know how to perform simple manipulations of 1-dimensional arrays (vectors)

Outline

1. MATLAB Windows

The MATLAB Command Window is the main window where you type commands directly to the MATLAB
interpreter. The MATLAB Editor Window is a simple text editor where you can load, edit and save complete
MATLAB programs. The Editor window also has a menu command (Debug/Run) which allows you to submit
the program to the command window. The MATLAB Help Window gives you access to a great deal of useful
information about the MATLAB language and MATLAB computing environment. It also has a number of
example programs and tutorials.

2. A first program

The command
disp(argument);
displays the value of the argument. This can be a number, a string in single quotes, or an expression. For
simple numbers, the arithmetic operators are: +, -, *, / and ^. Try
disp(2*3+1);
or
disp(’Hello World!’);
Try these programs out first on the command line; then practise using the editor to enter the commands, saving
them to a file, loading the file and running the program from inside the editor.

3. Variables and assignment

Variables are named locations in memory where numbers, strings and other elements of data may be stored
while the program is working. Variable names are combinations of letters and digits, but must start with a
latter. MATLAB does not require you to declare the names of variables in advance of their use. This is
actually a common cause of error, since it allows you to refer accidentally to variables that don’t exist. To
assign a variable a value, use the assignment statement. This takes the form
variable=expression;
for example
a=6;
or
name=’Mark’;
To display the contents of a variable, use
disp(variable);

4. Arrays

MATLAB is particularly powerful in the way it deals with tables of data, called arrays. An array is simply a
variable that can contain a number of values arranged in tabular form. Arrays may be one dimensional (like a
list), two dimensional (like a table), or have more dimensions. To set the value of one element of a one
dimensional array, use the notation
variable(index)=expression;
for example
table(1)=3;
table(2)=6;
Note that indexes must be expressions evaluating to positive integers. The smallest index is 1. To access one
element from a one dimensional array, use the notation
variable(index)
for example
a=table(2);
disp(table(2));
For two dimensional arrays, use
variable(index,index)=expression;
to set the value and
variable(index,index)
to retrieve its value. You can store strings in tables, but each string occupies a row, and all rows must be the
same length (think of a two-dimensional array of characters).

You can assign a whole array in one operation using a notation involving square brackets: for example:
array = [ v11 v12 v13; v21 v22 v23];
where v11 is the value in row 1 col 1; v21 is the value in row 2 col 1; etc. The ‘;’ marks the end of a row.

You can generate arrays containing sequences very easily with the ‘:’ operator. The expression
start:stop
generates a sequence of integers from start to stop. The expression
start:increment:stop
generates a sequence from start to stop with the specfied increment. Try
disp(1:10);
disp(1:2:10);

You can also select sub-parts of the array with the ‘:’ operator. For example,
x(3:5)
represents the array consisting of the third through fifth elements of x. Also
y(2:2:100)
represents the array containing the even number elements of y below index 100.

You can also add subtract, multiply and divide arrays of data using the operators we’ve mentioned previously.
However MATLAB makes a difference between operations that work on a cell-by-cell basis (so-called “dot”
operations) as opposed to operations that work on the arrays as a whole. For example, if you want to multiply
two arrays of equal size to give a third array in which each cell contains the product of the corresponding cells
in the input, then you need to use the “dot-multiply” operator .* for example
C = A.*B;

Finally you can transpose rows and columns of a matrix with the ' operator, for example
disp(A')

Reading
"Getting Started: Expressions" in MATLAB Help.

Exercises
For these exercises, use the editor window to enter your code, and save your answers to files under your
account on the central server. To run the programs, use the Debug/Run menu option or cut and paste the code
into the command window. When you save the files, give them the file extension of ".m".
1. Write a program (ex21.m) to assign the following expressions to a variable A and then to print out the value
of A.

a)
b)
c)
d) (0.0000123 + 5.67×10-3) × 0.4567×10-4
2. Celsius temperatures can be converted to Fahrenheit by multiplying by 9, dividing by 5, and adding 32.
Assign a variable called C the value 37, and implement this formula to assign a variable F the Fahrenheit
equivalent of 37 Celsius (ex22.m).
3. Set up a vector called N with five elements having the values: 1, 2, 3, 4, 5. Using N, create assignment
statements for a vector X which will result in X having these values:
a) 2, 4, 6, 8, 10
b) 1/2, 1, 3/2, 2, 5/2
c) 1, 1/2, 1/3, 1/4, 1/5
d) 1, 1/4, 1/9, 1/16, 1/25
(ex23.m)
4. A supermarket conveyor belt holds an array of groceries. The price of each product (in pounds) is [ 0.6 1.2
0.5 1.3 ] ; while the numbers of each product are [ 3 2 1 5 ]. Use MATLAB to calculate the total bill
(ex24.m).
5. (Advanced) The sortrows(X) function will sort a vector or matrix X into increasing row order. Use this
function to sort a list of names into alphabetical order. (ex25.m)
6. (Advanced) The “identity” matrix is a square matrix that has ones on the diagonal and zeros elsewhere.
You can generate one with the eye() function in MATLAB. Use MATLAB to find a matrix B, such that
when multiplied by matrix A=[ 1 2; -1 0 ] the identity matrix I=[ 1 0; 0 1 ] is generated. That is A*B=I.
(ex26.m)
7. (Homework) Create an array of N numbers. Now find a single MATLAB statement that picks out from
that array the 1,4,9,16,…,√Nth entries, i.e. those numbers which have indices that are square numbers.
UCL Department of Phonetics and Linguistics

Introduction to Computer Programming with MATLAB

Lecture 3: Graph Plots


Objectives

By the end of the session you should:


 be able to create data sets and display simple x-y graphs
 know the names of some basic MATLAB functions and what they do
 be able to calculate some simple waveforms
 be able to replay waveforms and also save and load them from disk

Outline

1. Basic Plotting

To create XY graphs, it is easiest to form your data into two row vectors, one for the x co-ordinates, and one for
the y co-ordinates. The command
plot(x,y)
will then create a figure with points at each y value for each matching x value. You can control the style of any
line drawn through the points by a third string argument to the plot command:
plot(x,y,style);
where style is made up from characters as follows:
 Color strings are 'c', 'm', 'y', 'r', 'g', 'b', 'w', and 'k'. These correspond to cyan, magenta, yellow, red,
green, blue, white, and black.
 Linestyle strings are '-' for solid, '--' for dashed, ':' for dotted, '-.' for dash-dot, and none for no line.
 The marker types are '+', 'o', '*', and 'x' and the filled marker types 's' for square, 'd' for diamond, '^' for
up triangle, 'v' for down triangle, '>' for right triangle, '<' for left triangle, 'p' for pentagram, 'h' for
hexagram, and none for no marker.
For example:
x = [ 1 2 3 4 ];
y = [ 10 15 20 25 ];
plot(x,y,’g-*’);
You can plot multiple lines by repeating the arguments:
plot(x1,y1,x2,y2,…);
or
plot(x1,y1,style1,x2,y2,style2,…);
You can give the graph a title with the
title(label);
command, where label is a character string. Likewise you can add labels to the X and Y axes with
xlabel(label);
and
ylabel(label);
You can add a legend with
legend(label1,label2,label3,…);

2. Built in functions

Generation
zeros() matrix of specified size filled with zeros
ones() matrix of specified size filled with ones
rand() generate pseudo random number(s) between 0 and 1

Arithmetic
rem() remainder after integer division
abs() absolute value (also character -> number)
fix() truncate a value to its integer part (towards zero)
round() round a value to nearest integer.
sqrt() square root
sin() sine (angle in radians)
cos() cosine (angle in radians)
exp() exponential
log() natural logarithm
log10() logarithm base 10

Status
length() length of a vector (longest dimension of matrix)
size() size of a matrix [nrows, ncols]

Miscellaneous
sum() sum the elements of a vector
mean() find mean of elements of a vector
sort() sort the elements of a vector in increasing size
clock() returns date and time as a vector [year month day hour minute seconds]
date() returns date as a string dd-mmm-yyyy

3. Generating Waveforms

Waveforms are just long vectors with one number per amplitude sample. Usually they are best kept scaled so
that each amplitude is between –1 and 1. To generate a sinewave, first generate a time sequence t representing
the times of each sampling instant; for example:
t = 0:0.0001:2;
would generate a two second sequence with a sampling interval of 0.1ms (i.e. 10,000Hz). You can then
generate a sinewave at frequency F with the expression
y = sin(2*pi*F*t);

You can create a pulse by creating a vector of zeros and setting a single element to one. A pulse train has a
series of elements set to one. If these occurred every 100 elements, you might use the expression
y(1:100:10000)=1;
To create a simple sawtooth, you can use the remainder function, for example
y = rem(1:10000,100)/100;
To create a noise waveform, you can use the ‘rand(nrows,ncols)’ function, for example
y = rand(1,10000);

4. Sound Replay, Load and Save

To replay a waveform, you can use


sound(wave,samplerate);
To ensure that the waveform is scaled to the range –1 .. +1 before replay, use
soundsc(wave,samplerate);
instead.

To save a waveform to a file, use


save filename variable;
To load a waveform from a file, use
load filename variable;
To save a waveform in a Windows compatible audio file format, use
wavwrite(waveform,samplerate,filename);
To load a Windows compatible audio file, use
[waveform,samplerate,nbits]=wavread(filename);

Reading
"Getting Started: Graphics" in MATLAB Help.
Exercises
For these exercises, use the editor window to enter your code, and save your answers to files under your
account on the central server. To run the programs, use the Debug/Run menu option or cut and paste the code
into the command window. When you save the files, give them the file extension of ".m".
1. Draw a graph that joins the points (0,1), (4,3), (2,0) and (5,-2). (ex31.m).
2. Draw a target made up of three circular rings of radius 1, 2 and 3 (ex32.m). The formula for the co-
ordinates of a circle are:
x = r.cos(θ) 0 < θ < 2π
y = r.sin(θ) r>0
You may need to use the command axis('equal'), why?
3. The seeds on a sunflower are distributed according to the formula below. Plot a small circle at each of the
first 1000 co-ordinates (ex33.m):

4. Calculate 10 approximate points from the function y=2x by using the formulae:
xn = n
yn = 2n + rand - 0.5
Fit a line of best fit to these points using the function polyfit() with degree=1, and generate co-
ordinates from the line of best fit using polyval(). Use the on-line help to find out how to use these
functions. Plot the raw data and the line of best fit. (ex34.m).
5. Calculate and replay 1 second of a sinewave at 500Hz with a sampling rate of 11025Hz. Save the sound to
a file called "ex35.wav". Plot the first 100 samples (ex35.m).
6. (Advanced) Calculate and replay a 2 second chirp. That is, a sinusoid that steadily increases in frequency
with time, from say 250Hz at the start to 1000Hz at the end. (ex36.m).
7. (Homework) Build a square wave by adding together 10 odd harmonics: 1f, 3f, 5f, etc. The amplitude of
the nth harmonic should be 1/n. Display a graph of one cycle of the result superimposed on the individual
harmonics.
UCL Department of Phonetics and Linguistics

Introduction to Computer Programming with MATLAB

Lecture 4: Procedures and Functions


Objectives

By the end of the session you should:


 know how to create a function that takes arguments
 understand about argument copying and variable scope
 know how to give a function a title and a help text
 know how to request input from the user and to format responses
 know how to manipulate strings of characters

Outline

1. Functions

You can define your own functions to complement those provided by MATLAB. Functions are the building
blocks of your own programs. You should always try and divide your programming task into separate
functions, then design, code and test each one independently. It is common to design from the top down, but
build from the bottom up.

It is good practice to store each function in its own source file, with the name of the source file matching the
function. Thus a function called “myfunc” will be stored in the file “myfunc.m”. This way, both you and
MATLAB can easily find the source file for a function given its name. The first line of a function source file
should then be the function definition line, which has the format:
function outargs=funcname(inargs);
The function name can be a mixture of letters and digits but must start with a letter. It is a good idea to avoid
names that MATLAB is already using. The inargs parameter is a list of variable names separated by commas.
These are the dummy names you will use in the code for the function to ‘stand for’ the actual arguments passed
to the function when it is executed. Likewise the outargs parameter is a list of variable names separated by
commas which stand for the values returned by the function to the calling program. Note that a function can
take zero or more input arguments and return zero or more values. Here are some example function definitions:
function y=square(x);
function av=average(x1,x2,x3,x4,x5);
function printvalue(A);
function B=readvalue();
function [mean,sttdev]=analyse(tab);
Following the function line you should write a one line comment that summarises what the function does. For
example:
% square(x) returns the square of the argument x
This line is printed out if the user types
lookfor funcname;
in the command window. All the comment lines between the function definition and the first executable
statement are printed out when the user types
help funcname;
in the command window. Use this facility to provide some help information to the users of your function.

The body of your function will normally perform some computation based on the input arguments and end by
assigning some values to the output arguments. When the function is called from another program, whatever
values are supplied to the function are copied into the dummy input arguments, then the function is executed,
then the values of the output dummy arguments are inserted into the calculation in the calling program. It is
good practice to end each function with the return statement to remind you that execution returns to the calling
program at this point.

function y=cube(x)
% cube(x) returns the cube of x
y = x * x * x;
return;

a=10;
b=cube(a);
disp(b); % \
disp(cube(a)); % All display 1000
disp(cube(10)); % /

It is good practice to pass all the information you need for a function through the list of input arguments and to
receive all the processed results through the output arguments. Although this requires a lot of copying,
MATLAB does this quite efficiently. Sometimes however, you may have a number of functions that all require
access to the same table of data, and you don’t want to keep copying the table into the function and then
copying the changes back into your program. Imagine if the table had a million elements! Under these
circumstances you can declare variables as ‘global’. This means that they can be accessed both inside your
program and inside a function without having to pass the variable as a function argument. Here is an example:

function initialisetable(num)
% initialise global variable TAB to all the same value
global TAB;
TAB=num*ones(size(TAB));

% main program
global TAB;
TAB=zeros(1,100);
initialisetable(5);

You can also write functions which take a variable number of arguments. In fact MATLAB allows any
function to be called with fewer arguments than the definition, so it is a good idea to always check the number
of arguments supplied. The built in variable ‘nargin’ contains the number of input arguments actually supplied,
and ‘nargout’ contains the number of output arguments. You can use the built in function ‘error()’ to report an
error if the number of arguments is incorrect. For example:
function m=average(x,y)
if (nargin!=2)
error(’two arguments needed in average()’);
end
We'll meet the if statement in the next lesson.

2. Formatted console input-output

You can control the exact way in which values are printed to the screen with the ‘fprintf()’ function (fprintf=
“file print formatted”). This function takes one argument repesenting the formatting instructions, followed by a
list of values to be printed. Embedded within the format string are ‘percent commands’ which control where
and how the values are to be written. Here are some examples:
fprintf('The answer is %g seconds.\n',nsec);
fprintf('Day of the week = %s\n',dayofweek([7 12 1941]));
fprintf('Mean=%.3f ± %.4f\n',mean,stddev);
The command %g represents a general real number, %f means a fixed point number, %d a decimal integer, and %s
a string. You can put numeric values between the ‘%’ and the letter to control the field width and the number
of digits after the decimal point. For example (□=space):

fprintf('%5g',10) □□□10
fprintf('%10.4f',123.456) □□123.4560
fprintf('%10s', 'fred') □□□□□□fred

You can input a value or a string from the command line with the ‘input()’ function. This has two forms
depending on whether you want to input a number or a string:
yval=input('Enter a number: ');
name=input('Enter your name: ', 's');
3. String handling

Simple strings are stored as tables with one row and a number of columns: one column per character. You can
concatenate any table or strings simply by making the contents part of one table. For example:
str1='Hello';
str2='Mark ';
str=[str1 ' ' str2];
You can convert numbers to strings using the ‘sprintf()’ function, which operates analogously to the fprintf()
function but outputs to a string rather than to the screen.
str=sprintf('%10.4f',123.45);

The ‘abs()’ function can be used to find the standard character codes for a string:
disp(abs('Mark'));
77 97 114 107
The ‘char()’ function can be used to convert character codes back to a string:
disp(char([77 97 114 107]));
Mark
The ‘eval()’ function can be used to evaluate an expression stored in a string. This allows you to execute
expressions typed in by the user:
expr=input('Enter an expression (e.g. "2+3*4") : ', 's');
disp(eval(expr));

Reading
"Getting Started: Scripts and Functions" in MATLAB Help.

Exercises
For these exercises, use the editor window to enter your code, and save your answers to files under your
account on the central server. When you save the files, give them the file extension of ".m". To run functions,
call them by name from the command window.
1. Write a function called FtoC (ftoc.m) to convert Fahrenheit temperatures into Celsius. Make sure the
program has a title comment and a help page. Test from the command window with:
FtoC(96)
lookfor Fahrenheit
help FtoC
2. Write a function (roll2dice.m) to roll 2 dice, returning two individual variables: d1 and d2. For
example:
[d1 d2] = roll2dice
d1 =
4
d2=
3
3. Write a function (sinewave.m) to return a sinewave of a given frequency, duration and sample rate.
Replay it to check. For example:
s = sinewave(1000,0.5,11025);
sound(s,11025);
4. Write a program (ex44.m) to input 2 strings from the user and to print out (i) the concatenation of the
two strings with a space between them, (ii) a line of asterisks the same length as the concatenated
strings, and (iii) the reversed concatenation. For example:
Enter string 1: Mark
Enter string 2: Huckvale
Mark Huckvale
*************
elavkcuH kraM
5. (Advanced) Zeller's Congruence is a formula that calculates the day of the week given the date (within a
limited range of years). The formula is:
day = 1 + ([2.6m - 0.2] + d + y + [y/4] + [c/4] - 2c) modulo 7
where the square brackets mean take the integer part, modulo 7 means the remainder when divided by 7,
and
d = day of the month (e.g. 1 to 31)
m = month number with March=1, ..., December=10, January=11,
February=12. Assign January and February to previous year.
c = century number (e.g. 19)
y = year in century (e.g. 97), but remember January and February
The result is the day of the week, with 1=Sunday. Write a function dayofweek (dayofweek.m) which
takes a vector of three numbers representing the day, month and year in conventional notation, and
returns a string containing the name of the day of the week. For example (attack on Pearl Harbour):
dayofweek([7 12 1941])
ans=
Sunday
6. (Homework) A musical semitone is a frequency interval corresponding to the twelfth root of 2 (i.e.
2^(1/12)). We can number semitones with respect to the 'A' above middle C which has the frequency
440Hz, i.e. semitone 0 = 440Hz, semitone 1 = 466.2Hz, etc. Write a program which plays the
Westminster chimes, with
tones=[ 0 –4 –2 –9 –9 –2 0 –4 ];
UCL Department of Phonetics and Linguistics

Introduction to Computer Programming with MATLAB

Lecture 5: Control Statements


Objectives

By the end of the session you should:


 know the form of conditional expressions
 know how to use the if-then-else statement to execute statements conditionally.
 know how to use the while statement to execute statements repetitively
 know how to use the for statement to execute statements a fixed number of times

Outline

1. Conditional Expressions

We have seen a number of arithmetical expressions using operators such as +, – and *. We can also create
“logical” or “conditional” expressions using comparison and Boolean operators. Such expressions always
produce a numerical result that is either 1 for true expressions, or 0 for false expressions. The comparison
operators are ‘<’, ‘<=’, ‘==’, ‘>=’, ‘>’ and ‘~=’. The Boolean operators are ‘&’ (and), ‘|’ (or), ‘~’ (not). You
can use parentheses to bracket expressions to force an evaluation order. For example:
x=10;
y=20;
disp(x < y); % displays 1
disp(x <= 10); % displays 1
disp(x == y); % displays 0
disp((0 < x) & (y < 30)); % displays 1
disp((x > 10) | (y > 100)); % displays 0
disp(~(x > 10)); % displays 1
You can even perform logical operations on arrays. The operations are performed cell-by-cell and the output is
an array of logical values:
area=[ 1 4 9 16 25 36 ];
perimeter=[ 4 8 12 16 20 24 ];
disp(area < perimeter); % displays 1 1 1 0 0 0
Finally, you can use a logical array to select elements from a numerical array. Cells from the numerical array
that match true values in the logical array are extracted. Just use the logical array in the place of the subscript
operator, e.g.:
disp(area(area < perimeter)); % displays 1 4 9

2. If Statement

In the programs we have written so far, every statement is executed every time the program is run. The
conditional statement ‘if condition statement end’ only executes the statement if the condition evaluates to true.
The condition should be a logical expression evaluating to true or false. For example:
if (x < 10)
disp(x); % only displays x when x < 10
end
if ((0 < x) & (x < 100))
disp('OK'); % displays OK if x between 0 and 100
end
You can put more than one statement between the ‘if’ and the ‘end’. You can choose between two courses of
action with ‘if condition statement else statement end’ as in:
if ((0 < x) & (x < 100))
disp('OK');
else
disp('x contains invalid number');
end
You can build a chain of tests with ‘elseif’, as in:
if (n <= 0)
disp('n is negative or zero');
elseif (rem(n,2)==0)
disp('n is even');
else
disp('n is odd');
end
You can embed, or ‘nest’ statements, as in: (What function does this code calculate?)
if (a < b)
if (a < c)
disp(a);
else
disp(c);
end
else
if (b < c)
disp(b);
else
disp(c);
end
end
The indenting of nested statements is not necessary but is recommended.

3. While Statement

The ‘if’ statement allows us to execute a statement conditional on some logical expression. The ‘while’
statement adds repetitive execution to the ‘if’ statement. The format of the while statement is ‘while condition
statement end’ and the statement is repeatedly executed while it is the case that the condition evaluates as true.
In other words the statement is executed over and over again until the condition becomes false. Be warned that
an error in such a statement might lead to your program looping continuously. If your program is looping,
press [Ctrl/c] in the command window to cancel it. The while statement is useful when you do not know in
advance how many times an operation needs to be performed. For example, this code finds the smallest power
of two which is larger than the number n:
n=50;
p=1;
while (p < n)
p = 2 * p;
end
disp(p); % displays 64
The break statement is sometimes useful to cancel a while loop in the middle of a number of statements:
while (1)
req = input('Enter sum or "q" to quit : ','s');
if (req==’q’)
break;
end
disp(eval(req));
end

4. For Statement

The ‘for’ statement is useful when you do know how many times you want to repeat a statement. It is just a
special form of the while loop. To execute a statement 10 times with a while loop would require statements
such as;
i=1;
while (i<=10)
disp(i);
i = i + 1;
end
This can be written more succinctly with the for statement; its basic syntax is ‘for var=sequence statement
end’. For example, the loop above could be written:
for i=1:10
disp(i);
end
Note that in a for loop, the variable is set to each value in the sequence in turn and retains that value when it is
referred to inside the loop. You can create sequences with increments different to one in the usual way with
‘start:increment:stop’. You can even loop through the values in an array. For example:
for even=2:2:100 …
for primes=[ 2 3 5 7 11 13 17 19 23 ] …
For loops are executed efficiently without actually building an array from the sequence. This means that a loop
of 1 million repetitions doesn’t require an array of size one million. You can use the break statement in for
loops as well as in while loops.

Reading

"Getting Started: Flow Control" in MATLAB Help.

Exercises

For these exercises, use the editor window to enter your code, and save your answers to files under your
account on the central server. When you save the files, give them the file extension of ".m". To run functions,
call them by name from the command window.
1. Write a program (ex51.m) that asks the user for an age and then classifies the age according to the
following scheme:
Error < 0 <= Baby < 1 <= Child < 13 <= Teenager < 18 <= Adult < 60 <= Senior < 120 <= Error
For example:
Enter an age: 25
Adult
2. Print a Fahrenheit to Celsius Conversion table for each 5 degrees between 0 and 100 Fahrenheit (ex52.m).
Use the function FtoC() that you wrote in exercise 4.2. Make sure the table is nicely formatted with fixed
field widths and column headers.
3. Write a program (ex53.m) that asks for a series of numbers, ending in the value 0, and calculates the sum
and the mean. For example:
Enter a number (end in 0) : 5
Enter a number (end in 0) : 6
Enter a number (end in 0) : 7
Enter a number (end in 0) : 8
Enter a number (end in 0) : 0
4 numbers entered. Sum=26. Mean=6.5.
4. Adapt the function sinewave.m (sinewave2.m) that you wrote in exercise 4.3 so that it accepts a variable
number of arguments. If the sample rate is not given assume 11025. If the duration is not given assume 1
second. If the frequency is not given assume 1000Hz. For example:
sinewave2(500,0.5,16000);
sinewave2(250,1.5);
sinewave2(2000);
sinewave2;
5. (Advanced) Write a function (mysort.m) that sorts a list of numbers into increasing size (this replicates the
functionality of the built-in sort function). A simple method for sorting a list is called an ‘insertion’ sort,
and it works like this:
A. note that a list of length 1 is already sorted.
B. to add a single element to a sorted list: shift down by one place all those elements which are larger
than the element to be inserted.
C. insert the element in the gap at its correct position.
D. repeat steps B and C for each element remaining to be added.
Test your function by sorting a list of random numbers. (Hint: you will need a while loop inside a for loop).
6. (Homework) Build a pitch perception tester. In a loop play pairs of tones which differ in pitch by 64Hz,
32Hz, 16Hz, etc randomly assigning the higher pitch to the first or second position. Get the user to say
whether the higher tone was first or second. If the user was correct move on to the next smaller frequency
difference, else stop and print the frequency difference of the last successful attempt.
UCL Department of Phonetics and Linguistics

Introduction to Computer Programming with MATLAB

Lecture 6: Manipulating Text


Objectives

By the end of the session you should:


 be able to write simple functions and programs that manipulate text files and tables of strings.
 be able to re-use a number of simple programming templates for some common programming tasks.

Outline

1. Writing to a text file

To save the results of some computation to a file in text format reqires the following steps:
a. Open a new file, or overwrite an old file, keeping a ‘handle’ for the file.
b. Print the values of expressions to the file, using the file handle
c. Close the file, using the file handle
The file handle is a just a variable which identifies the open file in your program. This allows you to have any
number of files open at any one time.

% open file
fid = fopen('myfile.txt','wt'); % 'wt' means "write text"
if (fid < 0)
error('could not open file "myfile.txt"');
end;
% write some stuff to file
for i=1:100
fprintf(fid,'Number = %3d Square = %6d\n',i,i*i);
end;
% close the file
fclose(fid);

2. Reading from a text file

To read some results from a text file is straightforward if you just want to load the whole file into memory.
This requires the following steps:
a. Open an existing file, keeping a ‘handle’ for the file.
b. Read expressions from the file into a single array, using the file handle
c. Close the file, using the file handle
The fscanf() function is the inverse of fprintf(). However it returns the values it reads as values in a matrix.
You can control the 'shape' of the output matrix with a third argument.

A = fscanf(fid,"%g %g %g\n",[3,inf]) % A has 3 rows and 1 col per line


disp(A(1,1)) % display first value on first line
disp(A(1,2)) % display first value on second line
disp(A(2,1)) % display second value on first line

Thus to read back the data we saved above:

% open file
fid = fopen('myfile.txt','rt'); % 'rt' means "read text"
if (fid < 0)
error('could not open file "myfile.txt"');
end;
% read from file into table with 2 rows and 1 column per line
tab = fscanf(fid,'Number = %d Square = %d\n',[2,inf]);
% close the file
fclose(fid);
rtab = tab'; % convert to 2 columns and 1 row per line

Reading a table of strings is more complex, since the strings have to be the same length. We can use the fgetl()
function to get a line of text as characters, but we'll first need to find out the length of the longest string, then
ensure all strings are the same length. Here is a complete function for loading a text file as a table of fixed-
length strings:

function tab=readtextfile(filename)
% Read a text file into a matrix with one row per input line
% and with a fixed number of columns, set by the longest line.
% Each string is padded with NUL (ASCII 0) characters
%
% open the file for reading
ip = fopen(filename,'rt'); % 'rt' means read text
if (ip < 0)
error('could not open file'); % just abort if error
end;
% find length of longest line
max=0; % record length of longest string
cnt=0; % record number of strings
s = fgetl(ip); % get a line
while (ischar(s)) % while not end of file
cnt = cnt+1;
if (length(s) > max) % keep record of longest
max = length(s);
end;
s = fgetl(ip); % get next line
end;
% rewind the file to the beginning
frewind(ip);
% create an empty matrix of appropriate size
tab=char(zeros(cnt,max)); % fill with ASCII zeros
% load the strings for real
cnt=0;
s = fgetl(ip);
while (ischar(s))
cnt = cnt+1;
tab(cnt,1:length(s)) = s; % slot into table
s = fgetl(ip);
end;
% close the file and return
fclose(ip);
return;

Here is an example of its use:

% write some variable length strings to a file


op = fopen('weekdays.txt','wt');
fprintf(op,'Sunday\nMonday\nTuesday\nWednesday\n');
fprintf(op,'Thursday\nFriday\nSaturday\n');
fclose(op);
% read it into memory
tab = readtextfile('weekdays.txt');
% display it
disp(tab);

3. Randomising and sorting a list

Assuming we have a table of values, how can we randomise the order of the entries? A good way of achieving
this is analogous to shuffling a pack of cards. We pick two positions in the pack, then swap over the cards at
those two positions. We then just repeat this process enough times that each card is likely to be swapped at
least once.

function rtab=randomise(tab)
% randomise the order of the rows in tab.
% columns are unaffected
[nrows,ncols]=size(tab); % get size of input matrix
cnt = 10*nrows; % enough times
while (cnt > 0)
pos1 = 1+fix(nrows*rand); % get first random row
pos2 = 1+fix(nrows*rand); % get second random row
tmp = tab(pos1,:); % save first row
tab(pos1,:) = tab(pos2,:); % swap second into first
tab(pos2,:) = tmp; % move first into second
cnt=cnt-1;
end;
rtab=tab; % return randomised table
return;

Sorting a list is easy if you just want some standard alphabetic ordering. But what if you want to choose some
arbitrary ordering function? For example, how could you sort strings when case was not important? Here we
use the ability of MATLAB to evaluate a function by name (feval()) so that we can provide the name of a
function for doing the comparisons the way we want. This function should take two rows and return –1 if the
first row sorts earlier than the second, 1 if the second row sorts earlier than the first and 0 if there is no
preference. Here is a case-independent comparison function:

function flag=comparenocase(str1,str2)
% compares two strings without regard to case
% returns –1, 0, 1 if str1 is less than, equal, greater than str2.
len1=length(str1);
len2=length(str2);
for i=1:min(len1,len2)
c1 = str1(i);
c2 = str2(i);
if (('a' <= c1)&(c1 <= 'z'))
c1 = char(abs(c1)-32); % convert lower case to upper
end;
if (('a' <= c2)&(c2 <= 'z'))
c2 = char(abs(c2)-32); % convert lower case to upper
end;
if (c1 < c2)
flag = -1; % str1 sorts earlier
return;
elseif (c2 < c1)
flag = 1; % str2 sorts earlier
return;
end;
end;
% strings match up to length of shorter, so
if (len1 < len2)
flag = -1; % str1 sorts earlier
elseif (len2 < len1)
flag = 1; % str2 sorts earlier
else
flag = 0; % no preference
end;
return;

Here is a sort function that might be used with this comparison function.

function stab=functionsortrows(tab,funcname)
% sorts the rows of the input table using the supplied
% function name to provide an ordering on pairs of rows
[nrows,ncols]=size(tab);
for i=2:nrows % sort each row into place
j = i;
tmp = tab(j,:); % save row
% compare this row with higher rows to see where it goes
while ((j > 1)&(feval(funcname,tmp,tab(j-1,:))<0))
tab(j,:) = tab(j-1,:); % shift higher rows down
j = j - 1;
end;
tab(j,:) = tmp; % put in ordered place
end;
stab = tab; % return sorted table
return;

4. Searching a list

How might we search a list of items for an item matching a specific value? If the list is unordered, all we can
do is run down the list testing each entry in turn. This function finds the index of a row in a table that contains
(anywhere) the characters in the supplied match string:

function idx=findstring(tab,str)
% find the row index containing a matching string
% returns 0 if the string is not found
[nrows,ncols]=size(tab);
for idx=1:nrows
matches = findstr(tab(idx,:),str);
if (length(matches)>0)
return;
end;
end;
idx=0;
return;

However, the process can be much faster if the listed is sorted and we are searching for an exact match only. A
so-called binary search is the fastest possible way of finding an item in a sorted list:

function idx=binarysearch(tab,val)
% returns the row index of val in sorted table tab
% returns 0 if val is not found
[nrows,ncols]=size(tab);
lo=1;
hi=nrows;
while (lo <= hi)
idx = fix(lo+hi)/2;
if (val < tab(idx,:))
hi = idx - 1;
elseif (val > tab(idx,:))
lo = idx + 1;
else
return;
end;
end;
idx=0;
return;

5. Cell Arrays

Many operations with text and tables of strings are made simpler in MATLAB through the use of "cell arrays".
These are a generalisation of MATLAB matrices such that cells can contain any type of object. This allows
MATLAB to manipulate tables of variable length strings. We will not be going into cell arrays in this course.

Reading

MATLAB Online Manual: Using MATLAB: Index of Examples

Exercises

For these exercises, use the editor window to enter your code, and save your answers to files under your
account on the central server. When you save the files, give them the file extension of ".m". Run your
programs from the command window. You may want to start by implementing the readtextfile() function from
this handout.
1. Write a program (ex61.m) to ask the user to input the name of a text file containing a list of WAV format
sound files. Play these sounds out in random order.
2. Write a program (ex62.m) to ask the user to input the name of a text file containing a list of general
knowledge TRUE/FALSE questions. Prompt the user with each question in turn and save his/her responses
to an output file.
3. Write a program (ex63.m) to input a word list from a file and then to spell check another text file. Treat as
mis-spelled all words not in the word list, and report a list of all mis-spelled words, ensuring that each mis-
spelling is reported once only.
4. (Homework) Write a program that takes the name of a text file containg a list of WAV format sound files.
Concatenate the audio files into a single WAV file with 3 seconds of silence between them. Be sure to
check that sampling rates are compatible.
UCL Department of Phonetics and Linguistics

Introduction to Computer Programming with MATLAB

Lecture 7: Building a Graphical User Interface


Objectives

By the end of the session you should:


 know how to build a simple single-figure dialog to control your application
 know how to add menus, figures, text, buttons and edit boxes to your dialog
 know how to associate MATLAB code to events generated by the dialog controls
 know how to retrieve and modify the properties of the dialog controls
 know how to set up a single control file for your dialog

Outline

1. Elements of a Graphical User Interface

By a graphical user interface, we mean that we can give a MATLAB program the look and feel of a typical
Windows application. The MATLAB GUI design system allows you to create applications consisting of one or
more ‘dialogs’ containing typical ‘controls’ such as buttons, edit boxes, lists and pictures.

One of the important aspects of a Windows application that is unlike the kind of programs we have considered
up to now is that they interact asynchronously with the user. The user can select any function of the program at
any time. This means that you need to store the ‘state’ of your program in a set of variables and be prepared to
execute any function based on the current state at any time.

The MATLAB GUI design system helps you in this by associating functions with each element of the dialog.
Thus when you press a button, click on a menu, or enter a number in an edit box, you can arrange for a function
in your program to be called. Your task is to program the actions related to that function, e.g. opening a file,
playing a sound, or displaying the results of a calculation.

The most common controls are:


 Menu options. Selection calls up an operation by name.
 Push buttons. Clicking calls up some operation.
 Edit boxes. User can enter some text or numerical value.
 List boxes. User can choose among list of items.
 Figures. Program can display graphical results.
 Text. Program can display textual result.

You can use the controls themselves to store data or you can create a set of global variables.

2. How to build a simple dialogue

To start the design program type 'guide' at the MATLAB prompt. You are presented with a blank form upon
which you can position controls. Choose a control from the palette and click and size the control on the page to
position it. Each control is automatically given a name based on its type.

When the layout is complete, you can save the design to a ‘.fig’ file. This will automatically create a matching
‘.m’ program file which you can use to launch the application and store the code that is operated by the
controls. It is not necessary to store all your code in the matching ‘.m’ file; indeed it is a good idea to break up
any large sections of code into its own function blocks stored in separate files. You will see that the layout
designer builds a ‘callback’ function prototype in the program file for each control that provides input to the
application. This function will be called automatically when that control is activated.
You can edit the properties of the controls on the layout editor by right-clicking on them and choosing
‘Property Inspector’. In particular the ‘String’ property is used to store the default text for buttons, list boxes
and edit boxes. The ‘Tag’ property is the name of the control; and until you are familiar with MATLAB, it is
advisable not to change the default name. You can also use the Property Inspector to change the name of the
dialog itself.

You can add menu options to your dialog with the ‘Menu Editor’. If you leave the callback function entry as
“%automatic”, then the menu editor adds callback functions to your program for each menu item. Otherwise
create your own callback function using existing ones as a model, and associate a call to the function with the
menu item manually.

It is important to realise that the ‘.m’ file associated with your application is executed afresh each time there is
some event in the dialog. That is you must store the ‘current state’ of the program in global variables in the
workspace, and not in variables local to a function. You can ensure this by using a ‘global’statement and
initialising them in the part of the file where the figure is initialised.

You can access any property of any control using the ‘Tag’ property of the control and the MATLAB ‘get()’
and ‘set()’ functions.
value = get(handles.ControlTagName,'PropertyName');
set(handles.ControlTagName,'PropertyName','Value');
For example:
text = get(handles.edit1,'String');
set(handles.edit1,'String','100');
Note that most properties have to be get() and set() as strings. Use the num2str() and str2num() functions to
help convert between strings and numeric values.

3. Worked example
function varargout = beatsdemo(varargin)
% BEATSDEMO M-file for beatsdemo.fig
% BEATSDEMO, by itself, creates a new BEATSDEMO or raises the existing
% singleton*.
%
% H = BEATSDEMO returns the handle to a new BEATSDEMO or the handle to
% the existing singleton*.
%
% BEATSDEMO('CALLBACK',hObject,eventData,handles,...) calls the local
% function named CALLBACK in BEATSDEMO.M with the given input arguments.
%
% BEATSDEMO('Property','Value',...) creates a new BEATSDEMO or raises the
% existing singleton*. Starting from the left, property value pairs are
% applied to the GUI before beatsdemo_OpeningFunction gets called. An
% unrecognized property name or invalid value makes property application
% stop. All inputs are passed to beatsdemo_OpeningFcn via varargin.
%
% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one
% instance to run (singleton)".
%
% See also: GUIDE, GUIDATA, GUIHANDLES

% Edit the above text to modify the response to help beatsdemo

% Last Modified by GUIDE v2.5 11-Dec-2005 15:46:27

% Begin initialization code - DO NOT EDIT


gui_Singleton = 1;
gui_State = struct('gui_Name', mfilename, ...
'gui_Singleton', gui_Singleton, ...
'gui_OpeningFcn', @beatsdemo_OpeningFcn, ...
'gui_OutputFcn', @beatsdemo_OutputFcn, ...
'gui_LayoutFcn', [] , ...
'gui_Callback', []);
if nargin & isstr(varargin{1})
gui_State.gui_Callback = str2func(varargin{1});
end

if nargout
[varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT

% --- Executes just before beatsdemo is made visible.


function beatsdemo_OpeningFcn(hObject, eventdata, handles, varargin)
% This function has no output args, see OutputFcn.
% hObject handle to figure
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
% varargin command line arguments to beatsdemo (see VARARGIN)

% Choose default command line output for beatsdemo


handles.output = hObject;

% Update handles structure


guidata(hObject, handles);

%%%ADDED%%%
% initialise global variable cf
global cf;
cf=1000;
%%%%%%%%%%%

% --- Outputs from this function are returned to the command line.
function varargout = beatsdemo_OutputFcn(hObject, eventdata, handles)
% varargout cell array for returning output args (see VARARGOUT);
% hObject handle to figure
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)

% Get default command line output from handles structure


varargout{1} = handles.output;
% --- Executes during object creation, after setting all properties.
function edit1_CreateFcn(hObject, eventdata, handles)
% hObject handle to edit1 (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles empty - handles not created until after all CreateFcns called

% Hint: edit controls usually have a white background on Windows.


% See ISPC and COMPUTER.
if ispc
set(hObject,'BackgroundColor','white');
else
set(hObject,'BackgroundColor',get(0,'defaultUicontrolBackgroundColor'));
end

%%%ADDED%%%
%%%%%%%%%%%%%%%%%%%%%%%% Callback Functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%
function edit1_Callback(hObject, eventdata, handles)
% hObject handle to edit1 (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
%
% just call the pushbutton if text changes ...
pushbutton1_Callback(hObject,eventdata,handles);
return;
% --- Executes on button press in pushbutton1.
function pushbutton1_Callback(hObject, eventdata, handles)
% hObject handle to pushbutton1 (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
%
% reference global variable
global cf;
% get beat frequency from text box
bf=str2num(get(handles.edit1,'String'));
% make beats by combining two sinewaves
t=0:1/11025:1;
y=0.5*sin(2*pi*(cf-bf/2)*t)-0.5*sin(2*pi*(cf+bf/2)*t);
sound(y,11025);
% set default axes to figure and plot
axes(handles.axes1);
plot(t,y);
return;

% --------------------------------------------------------------------
function menu1000_Callback(hObject, eventdata, handles)
% hObject handle to f1000 (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
global cf;
cf=1000;
set(handles.menu1000,'Checked','on');
set(handles.menu2000,'Checked','off');
set(handles.text1,'String','Beats Demonstration (1000Hz)');
return;
% --------------------------------------------------------------------
function menu2000_Callback(hObject, eventdata, handles)
% hObject handle to f2000 (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
global cf;
cf=2000;
set(handles.menu1000,'Checked','off');
set(handles.menu2000,'Checked','on');
set(handles.text1,'String','Beats Demonstration (2000Hz)');
return;

Reading

MATLAB Online Manual: Creating Graphical User Interfaces

Exercises

For these exercises, use the editor window to enter your code, and save your answers to files under your
account on the central server. When you save the files, give them the file extension of ".m". Run your
programs from the command window.
1. Write a program (ex71.fig, ex71.m, sinewave.m) that shows a dialog containing four buttons. The buttons
have labels: ‘250Hz’, ‘500Hz’, ‘1000Hz’ and ‘2000Hz’. When you click on them a 1 second tone of the
specified frequency is replayed.
2. Modify the last example (ex72.fig, ex72.m) to display the first 0.01 second of the tone as well.
3. Modify the last example (ex73.fig, ex73.m) to allow the user to specify the frequency of one tone.
4. Modify the last example (ex74.fig, ex74.m, waveform.m) to provide a menu option to select between a sine
wave, a pulse train and a square wave. Report the currently selected waveform type in a text field.
5. (Homework) The following MATLAB command
[filename, pathname] = uigetfile('*.wav', 'Pick a WAV file');
will display a Windows dialog box allowing you select a file. The name of the file and the name of the
containing folder are returned. Build a GUI application which allows you to (i) open a WAV file and
display it, (ii) play out the audio at the correct and at user specified sampling rates.
UCL Department of Phonetics and Linguistics

Introduction to Computer Programming with MATLAB

Lecture 8: Discrete Linear Systems


Objectives

By the end of the session you should:


 understand how an impulse response can be used as an alternative to the frequency response for the
characterisation of a discrete linear system
 know how to implement a digital system from knowledge of its impulse response using convolution
 know how to build and apply simple non-recursive filters
 know how to build and apply simple recursive filters
 know how to plot the frequency response of a filter

Outline

1. Characterisation of Linear Systems

A discrete linear system is a digital implementation of a linear time-invariant system. Its input is a vector
representing the sampled input signal (we will use x[]), its output is a vector of the same size as the input
representing the sampled output signal (y[]).

The system itself has to obey the usual conventions of linearity, particularly that it obeys the principle of
superposition, so that f(a+b) = f(a) + f(b), where f() is the processing performed by the system and a and b are
arbitrary signals. Linear time-invariant systems can be characterised in the frequency domain by their
frequency response. The frequency response of a system tells us how a sinusoidal input is changed in
magnitude and phase as a function of its frequency. Although it is possible to implement systems in the
frequency domain, it is more typical to implement them in the time domain: sample by sample. To do this we
use the time domain equivalent of the frequency response, known as the impulse response. A digital impulse
response is just the sequence of values that emerge from a discrete system when a unit impulse is applied to the
input (namely: x=[1 0 0 0 0 0 …]).

If we know the impulse response of a discrete linear system (and it is of finite length) we can readily implement
the system in a simple way. To understand this observe that any sampled signal is really just a series of scaled
impulses whose amplitudes are the instantaneous amplitudes of the original analogue signal and which occur
regularly at the sampling instants. Thus if the input signal is just a series of impulses and we know what the
system does to impulses and we know the system obeys the principle of superposition then we can calculate the
output of the system to any input signal. We do this by triggering an impulse response at each input sample
scaled according to the amplitude of the sample. The sum of the overlapping triggered scaled impulse
responses is the output signal. Mathematically this is called convolution, and can be expressed as:

Where the summation is over the length of the impulse response. MATLAB provides a conv() function to
perform convolution between two vectors. To apply a linear system of impulse response h[] to an input signal
x[], we run:

y = conv(h,x);

When conv() is used in this way the output signal is longer than the input signal by the length of the impulse
response (it is as if the input was padded with trailing zeros). Here is a simple example:

h=[10 -9 8 -7 6 -5 4 -3 2 -1];
x=[0 1 0 0 0 0 0 0 0 0 ];
y=conv(h,x);
subplot(2,3,1); plot(h);
subplot(2,3,2); plot(x);
subplot(2,3,3); plot(y);
x=[0 1 0 0 0 0 0 1 0 0 ];
y=conv(h,x);
subplot(2,3,4); plot(h);
subplot(2,3,5); plot(x);
subplot(2,3,6); plot(y);
2. Finite Impulse Response (FIR) Filters

To obtain the impulse response of a system specified in the frequency domain we can use an inverse Fourier
transform to get a sampled impulse response then truncate or window the impulse response to a suitable length.
The inverse Fourier transform is described in lecture 9.

MATLAB provides a function fir1() for generating a truncated impulse response for the implementation of
low-pass, high-pass and band-pass filters. The call:
h = fir1(n,w)
generates a truncated impulse response in h[] of length n samples, corresponding to a low-pass filter with a cut-
off at a normalised frequency w. In MATLAB filter design functions, frequencies are normalised to half the
sampling rate, i.e. a normalised frequency of 0.5 corresponds to one quarter of the sampling rate (don't ask).

Here we generate 1 second of white noise signal at 10,000 samples/sec and low-pass filter it at 2500Hz with a
filter of size 100 samples.
x = rand(1,10000);
soundsc(x,10000);
pause;
h = fir1(100,0.5);
y = conv(h,x);
soundsc(y,10000);

High-pass filters are designed with


h = fir1(n,w,'high');
and band-pass filters are designed with
h = fir1(n,[w1 w2]);
where w1 and w2 are the normalised frequencies of the band edges.

In general you need truncated impulse responses that are long enough to capture the majority of the energy in
the full impulse response. Filters with 100 coefficients are not uncommon. Filters with sharp edges need longer
impulse responses.

3. Infinite length impulse response filters

Finite impulse response filters have a number of good characteristics: they are simple to understand, have good
phase response, and are always stable. However they are inefficient. Each output sample requires a
convolution sum that is the size of the impulse response. That is for an impulse response of length N, the
system implementation will require N multiply-add operations per output sample.

Increased efficiency can be obtained by allowing the linear system calculation to use not only the current and
past samples of the input signal, but also past samples from the output signal. Often the calculation performed
by a linear system can be simplified if it can re-use some of the calculation that it performed on previous
samples. This is called recursive system design, in contrast to finite impulse response filters which can be
called non-recursive. Recursive systems are more complex to build, often have messy phase responses, and can
be unstable. However they can be significantly more efficient and have impulses responses of infinite length.
A recursive system is specified by two vectors a[] and b[]: the b[] coefficients are convolved with the current
and past input samples, while the a[] coefficients are convolved with the past output samples.

Here is a diagram of the idea:

In this figure the input signal x is processed sample-by-sample into the output signal y. At the stage shown, we
are processing sample number n. To calculate output sample y(n), the filter multiplies the current and past
input samples x(n), x(n-1), x(n-2), x(n-3), …, x(n-k) by the set of b coefficients: b(0), b(1), b(2), b(3), …, b(k);
and sums them. The filter then multiplies the past output samples: y(n-1), y(n-2), y(n-3), …, y(n-k) by the a
coefficients: a(1), a(2), a(3), …, a(k) and sums them. It then combines these to form y(n), according to this
formula:

In MATLAB, this whole process is performed by the filter() function:


y = filter(b,a,x)
where as before, x is the input signal, y the output signal, and where b and a are the coefficients that define the
recursive linear system.

The MATLAB signal processing toolbox contains a number of different functions for deigning recursive low-
pass, high-pass and band-pass filters. We shall only refer to one simple design, the Butterworth filter. This
design generates filters with maximally flat responses in the pass band, although they may not have as steep a
cut-off as other designs.

A butterworth low-pass filter can be constructed with


[b,a] = butter(n,w);
Where n is the number of coefficients required in the filter, and w is the normalised cut-off frequency.
Typically n is much smaller for recursive filters than for FIR filters, values of 10 to 20 are common.

A butterworth high-pass filter can be constructed from


[b,a] = butter(n,w,'high');
while a band-pass filter from w1 to w2 can be constructed with
[b,a] = butter(n,[w1 w2]);

In this simple example we filter one second of white noise with a low-pass filter at 2500Hz:
x = rand(1,10000);
soundsc(x,10000);
plot(x(100:200))
pause;
[b,a]=butter(10,0.5)
y = filter(b,a,x);
soundsc(y,10000);
hold on; plot (y(100:200), ‘b‘)
4. Frequency Response

To display a graph of the frequency response of a filter, use the function freqz(). For a recursive system with
coefficients [b,a], this can be called with
freqz(b,a,n,Fs);
The argument n is the number of points to calculate; 512 is a suitable size. The argument Fs allows you to scale
the graph to any given sample rate. For a non-recursive system, where only the impulse response is available,
use:
freqz(h,1,512,Fs);

By default, freqz() plots both the magnitude response and the phase response. You can plot just the magnitude
response (in decibels) with this code:
[h,f] = freqz(b,a,512,Fs);
plot(f,20*log10(abs(h)));
Here h[] stores the complex response and f[] stores the frequency indices.

Reading

Units 4 and 7 of the "Introduction to Digital Signal Processing" course notes provide a more extended
introduction to these topics: http://www.phon.ucl.ac.uk/courses/spsci/dsp/

This book is highly recommended for the student wanting to understand DSP in more detail: P.A. Lynn, W.
Fuerst, B. Thomas, "Introductory Digital Signal Processing with Computer Applications", John Wiley, 1997.

Exercises

For these exercises, use the editor window to enter your code, and save your answers to files under your
account on the central server. When you save the files, give them the file extension of ".m". Run your
programs from the command window.
1. Write a program (ex81.m) that asks the user for a filename (WAV file), then loads the file, low-pass filters
it at 1000Hz and replays the original and the filtered version.
2. Write a program (ex82.m) that builds a low-pass filter, a high-pass filter and a band-pass filter and then
plots their frequency response curves on the same graph. Use frequency edges of 1000 and 3000Hz and a
sampling frequency of 10,000Hz.
3. Write a program (ex83.m) to compare the frequency responses of a 40 coefficient non-recursive
implementation and a 20 coefficient recursive implementation of a low-pass filter at a normalised cut-off of
one quarter of the sampling rate. Interpret the results.
4. (Homework) Write a simple GUI program for interactive filtering. It should allow the user to choose a
filter type and cut-off frequency, then display the filter frequency response. It should also be possible to
load a WAV file, then display and replay the unfiltered and filtered signals. This is how it might look:
UCL Department of Phonetics and Linguistics

Introduction to Computer Programming with MATLAB

Lecture 9: Spectral Analysis


Objectives

By the end of the session you should:


 know how to build and perform a filterbank analysis of a signal
 know how to use the discrete fourier transform to convert a waveform to a spectrum and vice versa.
 know how to divide a signal into overlapping windows
 know how to calculate and display a spectrogram

Outline

1. Filterbank analysis

The most flexible way to perform spectral analysis is to use a bank of bandpass filters. A filterbank can be
designed to provide a spectral analysis with any degree of frequency resolution (wide or narrow), even with
non-linear filter spacing and bandwidths. A disadvantage of filterbanks is that they almost always take more
calculation and processing time than discrete Fourier analysis using the FFT (see below).

To use a filter bank for analysis we need one band-pass filter per channel to do the filtering, a means to perform
rectification, and a low-pass filter to smooth the energies. In this example, we build a 19-channel filterbank
using bandwidths that are modelled on human auditory bandwidths. We rectify and smooth the filtered
energies and convert to a decibel scale.

function e=auditoryfbank(x,fs)
% AUDITORYFBANK performs an auditory filterbank analysis on a signal
% E=AUDITORYFBANK(X,FS) passes signal X, sampled at FS samples/sec
% through a 19-channel auditory filterbank, smoothing and downsampling
% energies to 100 frames per second. Output is in decibels.
%

% get output energy sample points


x=reshape(x,[length(x) 1]); % force to be a column
eidx=fs/100:fs/100:length(x); % sampling indices (100 frame/sec)
ne=length(eidx); % number of output energy frames

% these are the filter cut-offs


cuts=[180 300 420 540 660 780 900 1075 1225 1375 1525 1700 1900 2100 2300 2550 2850
3150 3475 4000];
nf=length(cuts)-1; % number of filters

% initialise the output energy table


e=zeros(ne,nf);

% build a smoothing filter


[sb,sa]=butter(4,2*50/fs); % low-pass at half output frame rate

% for each channel in turn


for i=1:nf
fprintf('Calculating channel %d (%d-%dHz)\n',i,cuts(i),cuts(i+1));
% build the band-pass filter
[b,a]=butter(4,[2*cuts(i)/fs 2*cuts(i+1)/fs]);
%plot spectrum of the filters
Figure (3); hold on;
freqz(b,a)
% filter the signal
y=filter(b,a,x);
% rectify and smooth
sy=filter(sb,sa,abs(y));
% sample and convert to decibels
sy=max(sy,eps); % force to be > 0
e(:,i)=100+20*log10(sy(eidx)); % downsample and convert to dB
end

The input to this function is a waveform, x, sampled at fs samples/second. The output is a table of energies, e,
in which each row represents 10ms of the signal, and each column the energy in decibels found in a band-pass
filtered frequency region at that time.

In this example, we build a chirp and pass it through the filterbank, then plot the output energies in each
channel against time:

% build a chirp from 500 to 2000Hz


t=0:1/10000:1;
f=linspace(500,2000,length(t));
y=sin(2*pi*f.*t);
soundsc(y,10000);
% put through filter bank
e=auditoryfbank(y,10000);
% scale to 0..1, and transpose
emin=min(min(e));
emax=max(max(e));
te=((e-emin)/(emax-emin))';
% offset each channel by channel number for plot
for i=1:size(te,1)
te(i,:) = te(i,:)+i;
end
plot(1:size(te,2),te,'k-');
ylabel('Filter channel');
xlabel('Frame number');
2. Spectral analysis using Fourier transform

The discrete-time discrete-frequency version of the Fourier transform (DFT) converts an array of N sample
amplitudes to an array of N complex harmonic amplitudes. If the sampling rate is Fs, the N input samples are
1/Fs seconds apart, and the output harmonic frequencies are Fs/N hertz apart. That is the N output amplitudes
are evenly spaced at frequencies between 0 and (N-1).Fs/N hertz.

To compute the DFT in MATLAB, we use the function fft(x,n). This function takes a waveform x and the
number of samples n. When n is less than the length of x, then x is truncated; when n is longer than the length
of x, then x is padded with zeros. The output is an array of complex amplitudes of length n. You can obtain the
magnitude of each spectral component with abs(), and its phase with angle() (result in radians).

In this example, we generate a complex periodic signal, then calculate and display the magnitude spectrum and
the phase spectrum:

% generate a complex signal


fs = 10000; % sampling rate
t = 0:1/fs:0.1; % sampling instants
x = sin(2*pi*1500*t) + sin(2*pi*4000*t); % two sinusoids
%
% perform 1000-point transform
y = fft(x,1000); % y contains 1000 complex amplitudes
y = y(1:500); % just look at first half
m = abs(y); % m = magnitude of sinusoids
p = unwrap(angle(y)); % p = phase of sinusoids, unwrap()
% copes with 360 degree jumps
% plot spectrum 0..fs/2
f = (0:499)*fs/1000; % calculate Hertz values
subplot(2,1,1), plot(f,m); % plot magnitudes
ylabel('Abs. Magnitude'), grid on;
subplot(2,1,2), plot(f,p*180/pi); % plot phase in degrees
ylabel('Phase [Degrees]'), grid on;
xlabel('Frequency [Hertz]');

The fft() function calculates the discrete fourier transform in the most efficient way possible, but transforms
of length 2m (m=integer) are considerably fastest to calculate. Use sizes of 512, 1024, etc for the fastest speed.

Typically we want the magnitude or the power of a spectrum in decibels, which can be obtained with:

Y=fft(x,1024);
magnitude = abs(Y);
powerdB=20*log10(magnitude+eps);
To convert back from harmonic amplitudes to a waveform use the inverse discrete Fourier transform function
ifft(). Be aware that this function takes complex harmonic amplitudes and delivers complex sample
amplitudes. Use the function real() to get the real part of the signal amplitudes:

Y=fft(x1,1024); % waveform -> spectrum


%%% process Y in some way %%%
x2=real(ifft(Y)); % spectrum -> waveform

3. Windowing a signal

Often we want to analyse a long signal in overlapping short sections called “windows”. For example we may
want to calculate an average spectrum, or to calculate a spectrogram. Unfortunately we cannot simply chop the
signal into short pieces because this will cause sharp discontinuities at the edges of each section. Instead it is
preferable to have smooth joins between sections. Raised cosine windows are a popular shape for the joins:

You can use the MATLAB function hamming() to design smooth windows of a given length, and then you can
use code such as the following to divide the signal into sections:

% divide up a signal into windows


x = sin(2*pi*500*(1:10000)/10000); % example signal
nx = length(x); % size of signal
w = hamming(32)'; % hamming window
nw = length(w); % size of window
pos=1;
while (pos+nw <= nx) % while enough signal left
y = x(pos:pos+nw-1).*w; % make window y
%%%% process window y %%%%
plot(y); hold on;
pos = pos + nw/2; % next window
end

4. Spectrograms

MATLAB has a built-in function specgram() for spectrogram calculation. This function divides a long signal
into windows and performs a fourier transform on each window, storing complex amplitudes in a table in which
the columns represent time and the rows represent frequency.

Call the specgram function on a signal x with a call:

[B,f,t] = specgram(x,nfft,fs,window,overlap)

Here, nfft is size of the fourier transform, use this to control the number of frequency samples on the vertical
axis of the spectrogram; fs is the sampling rate of the input signal; window is the number of input samples per
vertical slice, use this to control the analysis bandwidth; overlap is the number of samples by which adjacent
windows overlap, use this to control the number of vertical slices per second. The output B is a table of
complex amplitudes; f is a vector of frequency values used to label the rows, and t is a vector of time values
used to label the columns.

Here is a complete example, in which we also use the imagesc() and colormap() functions to display the results
on a conventional grey scale representing the top 50dB of the signal:

% get a portion of signal


[y,fs]=wavread('six.wav',[2000 12000]);
%
% calculate the table of amplitudes
[B,f,t]=specgram(y,1024,fs,256,192);
%
% calculate amplitude 50dB down from maximum
bmin=max(max(abs(B)))/300;
%
% plot top 50dB as image
imagesc(t,f,20*log10(max(abs(B),bmin)/bmin));
%
% label plot
axis xy;
xlabel('Time (s)');
ylabel('Frequency (Hz)');
%
% build and use a grey scale
lgrays=zeros(100,3);
for i=1:100
lgrays(i,:) = 1-i/100;
end
colormap(lgrays);

Reading

An essential introduction to spectral analysis can be found in Rosen & Howell, Signals and Systems for Speech
and Hearing, Academic Press, 1990; ISBN: 0125972318.

Units 5 and 6 from the Introduction to Digital Signal Processing course at


http://www.phon.ucl.ac.uk/courses/spsci/dsp/ provides more mathematical detail about fourier analysis and
windowing.

The best introduction to signal processing is the book: Introductory Digital Signal Processing with Computer
Applications, by P.A. Lynn, W. Fuerst, B. Thomas, Paperback - 494 pages 2nd edition (1998) John Wiley and
Sons; ISBN: 0471976318

Exercises

For these exercises, use the editor window to enter your code, and save your answers to files under your
account on the central server. When you save the files, give them the file extension of ".m". Run your
programs from the command window.
1. Write a program (ex91.m) that asks the user for a filename (WAV file), then loads the first 1024 samples,
applies a Hamming window and calculates the power spectrum. Plot the windowed signal and the spectrum
on a single figure with waveform at the top and the spectrum at the bottom. Make sure you express the
spectral amplitudes in decibels.
2. Adapt the last exercise (ex92.m) to calculate and display the average spectrum for the whole file.
3. Write a program (ex93.m) that calculates a high-resolution linear filterbank analysis of a given file and
displays the results on a grey scale, following spectrogram conventions. Use at least 128 overlapping band-
pass filters with bandwidths of 300Hz and an output rate of 1000 frames/sec.
4. (Homework) Write a program that filters a signal in the frequency domain. That is: it takes a signal, divides
it up into overlapping window segments, then for each segment calculates a FFT, modifies the spectral
magnitudes according to the required frequency response, then performs an inverse FFT to make a filtered
segment. The filtered segments can then be added together to create the filtered output signal.
UCL Department of Phonetics and Linguistics

Introduction to Computer Programming with MATLAB

Lecture 10: Speech Signal Analysis


Objectives

By the end of the session you should:


 know one way to estimate the fundamental frequency of a section of speech signal from its spectrum
 know one way to estimate the fundamental frequency of a section of speech signal from its waveform
 know one way to estimate the formant frequencies from a section of speech signal
 appreciate some of the problems of fundamental frequency and formant frequency estimation

Outline

1. Fundamental frequency estimation – frequency domain

The general problem of fundamental frequency estimation is to take a portion of signal and to find the dominant
frequency of repetition. Difficulties arise from (i) that not all signals are periodic, (ii) those that are periodic
may be changing in fundamental frequency over the time of interest, (iii) signals may be contaminated with
noise, even with periodic signals of other fundamental frequencies, (iv) signals that are periodic with interval T
are also periodic with interval 2T, 3T etc, so we need to find the smallest periodic interval or the highest
fundamental frequency; and (v) even signals of constant fundamental frequency may be changing in other ways
over the interval of interest.

A reliable way of obtaining an estimate of the dominant fundamental frequency for long, clean, stationary
speech signals is to use the cepstrum. The cepstrum is a Fourier analysis of the logarithmic amplitude spectrum
of the signal. If the log amplitude spectrum contains many regularly spaced harmonics, then the Fourier
analysis of the spectrum will show a peak corresponding to the spacing between the harmonics: i.e. the
fundamental frequency. Effectively we are treating the signal spectrum as another signal, then looking for
periodicity in the spectrum itself.

The cepstrum is so-called because it turns the spectrum inside-out. The x-axis of the cepstrum has units of
quefrency, and peaks in the cepstrum (which relate to periodicities in the spectrum) are called rahmonics.

To obtain an estimate of the fundamental frequency from the cepstrum we look for a peak in the quefrency
region corresponding to typical speech fundamental frequencies. Here is an example:

% get a section of vowel


[x,fs]=wavread('six.wav',[24120 25930]); %read 1810 samples in this range
ms1=fs/1000; % maximum speech Fx at 1000Hz
ms20=fs/50; % minimum speech Fx at 50Hz
%
% plot waveform
t=(0:length(x)-1)/fs; % times of sampling instants
subplot(3,1,1);
plot(t,x);
legend('Waveform');
xlabel('Time (s)');
ylabel('Amplitude');
%
% do fourier transform of windowed signal
Y=fft(x.*hamming(length(x)));
%
% plot spectrum of bottom 5000Hz
hz5000=5000*length(Y)/fs;
f=(0:hz5000)*fs/length(Y);
subplot(3,1,2);
plot(f,20*log10(abs(Y(1:length(f)))+eps));
legend('Spectrum');
xlabel('Frequency (Hz)');
ylabel('Magnitude (dB)');
%
% cepstrum is DFT of log spectrum
C=fft(log(abs(Y)+eps));
%
% plot between 1ms (=1000Hz) and 20ms (=50Hz)
q=(ms1:ms20)/fs;
subplot(3,1,3);
plot(q,abs(C(ms1:ms20)));
legend('Cepstrum');
xlabel('Quefrency (s)');
ylabel('Amplitude');

To search for the index of the peak in the cepstrum between 1 and 20ms, and then convert back to hertz, use:

[c,fx]=max(abs(C(ms1:ms20)));
fprintf('Fx=%gHz\n',fs/(ms1+fx-1));

The cepstrum works best when the fundamental frequency is not changing too rapidly, when the fundamental
frequency is not too high and when the signal is relatively noise-free. A disadvantage of the cepstrum is that it
involves computationally-expensive frequency-domain processing.

2. Fundamental frequency estimation – time domain

The cepstrum looks for periodicity in the log spectrum of the signal, whereas our perception of pitch is more
strongly related to periodicity in the waveform itself. A means to estimate fundamental frequency from the
waveform directly is to use autocorrelation. The autocorrelation function for a section of signal shows how
well the waveform shape correlates with itself at a range of different delays. We expect a periodic signal to
correlate well with itself at very short delays and at delays corresponding to multiples of pitch periods.

This code plots the autocorrelation function for a section of speech signal:
% get a section of vowel
[x,fs]=wavread('six.wav',[24120 25930]);
ms20=fs/50; % minimum speech Fx at 50Hz
%
% plot waveform
t=(0:length(x)-1)/fs; % times of sampling instants
subplot(2,1,1);
plot(t,x);
legend('Waveform');
xlabel('Time (s)');
ylabel('Amplitude');
%
% calculate autocorrelation
r=xcorr(x,ms20,'coeff');
%
% plot autocorrelation
d=(-ms20:ms20)/fs; % times of delays
subplot(2,1,2);
plot(d,r);
legend('Autocorrelation');
xlabel('Delay (s)');
ylabel('Correlation coeff.');

You can see that the autocorrelation function peaks at zero delay and at delays corresponding to  1 period,  2
periods, etc. We can estimate the fundamental frequency by looking for a peak in the delay interval
corresponding to the normal pitch range in speech, say 2ms(=500Hz) and 20ms (=50Hz). For example:

ms2=fs/500 % maximum speech Fx at 500Hz


ms20=fs/50 % minimum speech Fx at 50Hz
% just look at region corresponding to positive delays
r=r(ms20+1:2*ms20+1)
[rmax,tx]=max(r(ms2:ms20))
fprintf('rmax=%g Fx=%gHz\n',rmax,fs/(ms2+tx-1));

The autocorrelation approach works best when the signal is of low, regular pitch and when the spectral content
of the signal is not changing too rapidly. The autocorrelation method is prone to pitch halving errors where a
delay of two pitch periods is chosen by mistake. It can also be influenced by periodicity in the signal caused by
formant resonances, particularly for female voices where F1 can be lower in frequency than Fx.
3. Formant frequency estimation

Estimation of formant frequencies is generally more difficult than estimation of fundamental frequency. The
problem is that formant frequencies are properties of the vocal tract system and need to be inferred from the
speech signal rather than just measured. The spectral shape of the vocal tract excitation strongly influences the
observed spectral envelope, such that we cannot guarantee that all vocal tract resonances will cause peaks in the
observed spectral envelope, nor that all peaks in the spectral envelope are caused by vocal tract resonances.

The dominant method of formant frequency estimation is based on modelling the speech signal as if it were
generated by a particular kind of source and filter:

This type of analysis is called source-filter separation, and in the case of formant frequency estimation, we are
interested only in the modelled system and the frequencies of its resonances. To find the best matching system
we use a method of analysis called Linear Prediction. Linear prediction models the signal as if it were
generated by a signal of minimum energy being passed through a purely-recursive IIR filter.

We will demonstrate the idea by using LPC to find the best IIR filter from a section of speech signal and then
plotting the filter's frequency response.

% get a section of vowel


[x,fs]=wavread('six.wav',[24120 25930]);
% resample to 10,000Hz (optional)
x=resample(x,10000,fs);
fs=10000;
%
% plot waveform
t=(0:length(x)-1)/fs; % times of sampling instants
subplot(2,1,1);
plot(t,x);
legend('Waveform');
xlabel('Time (s)');
ylabel('Amplitude');
%
% get Linear prediction filter
ncoeff=2+fs/1000; % rule of thumb for formant estimation
a=lpc(x,ncoeff);
%
% plot frequency response
[h,f]=freqz(1,a,512,fs);
subplot(2,1,2);
plot(f,20*log10(abs(h)+eps));
legend('LP Filter');
xlabel('Frequency (Hz)');
ylabel('Gain (dB)');
To find the formant frequencies from the filter, we need to find the locations of the resonances that make up the
filter. This involves treating the filter coefficients as a polynomial and solving for the roots of the polynomial.
Details are outside the scope of this course (see readings). Here is the code to find estimated formant
frequencies from the LP filter:

% find frequencies by root-solving


r=roots(a); % find roots of polynomial a
r=r(imag(r)>0.01); % only look for roots >0Hz up to fs/2
ffreq=sort(atan2(imag(r),real(r))*fs/(2*pi));
% convert to Hz and sort
for i=1:length(ffreq)
fprintf('Formant %d Frequency %.1f\n',i,ffreq(i));
end

This produces the output:

Formant 1 Frequency 289.2


Formant 2 Frequency 2310.6
Formant 3 Frequency 2770.9
Formant 4 Frequency 3489.9
Formant 5 Frequency 4294.8

Reading

Unit 8 from the Introduction to Digital Signal Processing course at


http://www.phon.ucl.ac.uk/courses/spsci/dsp/ provides more detail about linear prediction.

A mathematical treatment can be found in: J. Makhoul. Linear prediction: A tutorial review. Proceedings of the
IEEE, 63(4):561-580, April 1975.

A good set of lecture notes on the mathematics of speech signal processing can be found at:
http://mi.eng.cam.ac.uk/~ajr/SA95/SpeechAnalysis.html

Exercises

For these exercises, use the editor window to enter your code, and save your answers to files under your
account on the central server. When you save the files, give them the file extension of ".m". Run your
programs from the command window.
1. Write a program (ex101.m) that asks the user for a filename (WAV file), and plots a fundamental frequency
track against time for the whole signal using the cepstrum method. Use a window size of 30ms and an
output frame rate of 100 Fx samples/second.
2. Modify the previous exercise to use the autocorrelation method (ex102.m). In this program set the
fundamental frequency value to zero when the peak of the autocorrelation function is less than 0.5. This
acts as a crude voicing decision.
3. Write a program (ex103.m) that asks the user for a filename (WAV file), and produces a scatter plot of
formant peaks against time. Use a window size of 30ms and an output frame rate of 100 per second.
4. (Homework) Write a GUI program which asks you to say a vowel and plots the location of the vowel on a
“Vowel Quadrilateral” diagram (effectively F1 vs F2).
UCL Department of Phonetics and Linguistics

Introduction to Computer Programming with MATLAB

Extra Lecture : Application Development


Objectives

By the end of the session you should:


 have more experience of how functions and control statements are used in the construction of programs
 have more experience with the processes of software design, development and test

Outline

1. Program Specification

In this lecture we will develop a simple ‘in-memory’ database application for storing data 'values' indexed by
'keys'. The specification is as follows:
a. Data values are strings of characters
b. Index keys are strings of characters
c. Both values and keys can be held as fixed length strings
d. Each value can have one or more keys
e. Each key can point to one or more values
f. We need a function to initialise the database
g. We need a function to add a record consisting of a value with its keys
h. We need a function to find all values matching a given key

2. Program Design

We will limit ourselves to simple MATLAB tables to store the data. This means that we will need one table to
hold all the different values, one table to hold all the different keys, and one table to hold the association of
keys with values. Other organisations would have been possible.

We will deal with the keys and the values as fixed length strings. This means that the tables will then have a
fixed number of columns. However, we will need a function to pad the keys to the width of the key table, and
the values to the width of the value table.

We will need these top-level functions:

dbinitialise(maxsizevalue,maxsizekey)
dbaddrecord(value,key,key,…)
values=dbfindrecords(key)

The dbinitialise() function will set up or clear the tables for the database. To initialise these it needs to know
how many columns to use to hold the largest value and the largest key.

The dbaddrecord() function will take a value as a string and one or more keys as strings and add them to the
database.

The dbfindrecords() function will take a key as a string and return an array of all the matching values in the
database.

This is how we expect it to work

dbinitialise(10,10);
dbaddrecord('huckvale','mark');
dbaddrecord('bloggs','fred','arthur');
dbaddrecord('smith','fred');
dbaddrecord('smith','andrew','mark');
disp(dbfindrecords('mark'));
huckvale
smith

Notice that these commands also test out different aspects of the functionality of the program, including the use
of multiple and duplicate keys.

The data tables themselves need to be hidden 'inside' the functions and shared between them This suggests a set
of global variables. Each function will contain the same global data statement which will give it access to the
three tables and their current sizes, as follows:

global dbkey, dbnumkey, dbindex, dbnumindex, dbvalue, dbnumvalue

Note here also that by making all the names of functions and variables start with a common prefix, we reduce
the possibility of clashes with MATLAB or user names.

The dbinitialise() function will create the data tables and initialise the current number of values stored in the
tables to zero.

We are ready to design the function to add a record. We see that it comprises three more basic operations: add
a value to the value table, add a key to the key table, and associate the two using the index table. Furthermore
we will only need to call the first function once, but the last two functions repeatedly for each key. This
suggests we break up this function into more basic functions. We might write some 'pseudocode' for the idea:

ptr=dbaddvalue(value)
while (keyslefttoadd)
code=dbaddkey(key)
dbaddindex(code,ptr)
end

We could also break down the database retrieval function into more basic functions, like

code=dbfindkey(key)
ptrs=dbfindindex(code)
vals=dbvalue(ptrs)

In the last line we are using the list of pointers (ptrs) to retrieve all the matching values from the value table.

3. Implementation

function dbinitialise(maxsizevalue,maxsizekey)
% dbinitialise(maxsizevalue,maxsizekey) creates a new database
% for values up to size maxvaluesize, and keys up to size
% maxkeysize
%
global dbkey dbnumkey dbindex dbnumindex dbvalue dbnumvalue
%
dbkey=char(zeros(1,maxsizekey));
dbnumkey=0;
dbindex=zeros(1,2);
dbnumindex=0;
dbvalue=char(zeros(1,maxsizevalue));
dbnumvalue=0;
return;

We initialise the tables to a single row containing zeroes so that the correct number of columns is established.
The 'num' variables tell us how many entries there are in each table, and also where the next value can be put in
each table.

function code=dbaddkey(key)
% code=dbaddkey(key) adds a key to the key table
% returning a numeric code
%
global dbkey dbnumkey dbindex dbnumindex dbvalue dbnumvalue
%
% pad the key
key=pad(key,size(dbkey,2));
%
% see if already in table
for i=1:dbnumkey
if (strcmp(dbkey(i,:),key))
code=i;
return;
end
end
%
% add to table
dbnumkey=dbnumkey+1;
dbkey(dbnumkey,:)=key;
code=dbnumkey;
return;

In this function we just run down the table checking to see if the key value is in the table already. If it isn't we
add it.
function p=pad(s,n)
% p=pad(s,n) pads string s with spaces to make it n char long
p=[s char(abs(' ')*ones(1,n)) ];
p=p(1:n);

This function pads out any string with spaces until it is a given width. This may mean the string is truncated,
too.
function ptr=dbaddvalue(value)
% ptr=dbaddvalue(value) adds a value to the value table
% returning a data pointer
%
global dbkey dbnumkey dbindex dbnumindex dbvalue dbnumvalue
%
% pad the record
value=pad(value,size(dbvalue,2));
%
% add to end of table
dbnumvalue=dbnumvalue+1;
dbvalue(dbnumvalue,:)=value;
ptr=dbnumvalue;
return;

Values are always added to the end of the table, we don't bother checking for duplicate values.

function dbaddindex(code,ptr)
% dbaddindex(code,ptr) links a key code to a value pointer
%
global dbkey dbnumkey dbindex dbnumindex dbvalue dbnumvalue
%
% add code, ptr pair to end of index table
dbnumindex=dbnumindex+1;
dbindex(dbnumindex,1)=code;
dbindex(dbnumindex,2)=ptr;
return;

Each index table row associates two numbers: the numeric code for the key in column 1, and the numeric code
for the value in column 2.

function dbaddrecord(value,varargin)
% dbaddrecord(value,key,key,...) adds a record to the database
% indexed by each of the supplied keys
%
global dbkey dbnumkey dbindex dbnumindex dbvalue dbnumvalue
%
ptr=dbaddvalue(value);
for i=1:length(varargin)
code=dbaddkey(char(varargin(i)));
dbaddindex(code,ptr);
end
return;

This function gets the numeric pointer for the value and a numeric code for each key and adds the {code,
pointer} pairs to the index table. To cope with the variable number of input arguments, we use the 'varargin'
variable – this will contain a vector of all the arguments after the first argument. We can then take each key
argument in turn, convert it to a string and add it as a key.

function code=dbfindkey(key)
% code=dbfindkey(key) finds code for key or returns 0 for not found
%
global dbkey dbnumkey dbindex dbnumindex dbvalue dbnumvalue
%
% pad the key
key=pad(key,size(dbkey,2));
%
% see if in table
for i=1:dbnumkey
if (strcmp(dbkey(i,:),key))
code=i;
return;
end
end
code=0;
return

This function runs down the key table looking for a match. It returns the key code, or zero if the key is not
found.

function ptrs=dbfindindex(code)
% ptrs=dbfindindex(code) finds all record pointers indexed
% by a given code
%
global dbkey dbnumkey dbindex dbnumindex dbvalue dbnumvalue
%
% select from table
ptrs=[];
for i=1:dbnumindex
if (dbindex(i,1)==code)
ptrs=[ptrs dbindex(i,2)];
end;
end;
return;

This function runs down the index table looking for entries matching the key code. It builds a vector of
matching value pointers.
function values=dbfindrecords(key)
% values=dbfindrecords(key) returns all records indexed by key
%
global dbkey dbnumkey dbindex dbnumindex dbvalue dbnumvalue
%
code=dbfindkey(key);
if (code > 0)
ptrs=dbfindindex(code);
if (length(ptrs) > 0)
values=dbvalue(ptrs,:);
else
values=[];
end
else
values=[];
end;
return;

This function looks up the given key then if found looks up the list of value pointers. If there are some values
to retrieve it gets them directly from the value table.

4. Testing

This function does a very basic test of the functionality:

function dbtest
dbinitialise(10,10)
dbaddrecord('huckvale','mark');
dbaddrecord('bloggs','fred','arthur');
dbaddrecord('smith','fred');
dbaddrecord('smith','andrew','mark');
disp(dbfindrecords('mark'));

This test estimates how quickly it works

function dbperf
% dbperf get some performance measures for database
%
dbinitialise(10,10);
%
% generate 1000 records
keys=char(zeros(1000,10));
vals=char(zeros(1000,10));
for i=1:1000
keys(i,:)=sprintf('%06d%04d',fix(100000*rand(1,1)),i);
vals(i,:)=sprintf('%06d%04d',fix(100000*rand(1,1)),i);
end;
%
% add 1000 records
tic;
for i=1:1000
dbaddrecord(vals(i,:),keys(i,:));
end;
fprintf('Took %g seconds to add 1000 records\n',toc);
%
% look up 1000 records
tic;
for i=1:1000
v=dbfindrecords(keys(i,:));
if ((length(v)==0)|~strcmp(v,vals(i,:)))
error('incorrect value returned!');
end;
end;
fprintf('Took %g seconds to find 1000 records\n',toc);

The MATLAB functions 'tic' and 'toc' run an internal stopwatch.

5. Refinement

Our dbfindindex function does not make use of MATLAB's functionality for dealing efficiently with matrices.
If we use the MATLAB find() function for searching a table, the determination of the pointers that match a
given code can be simplified to:

function ptrs=dbfindindex(code)
% ptrs=dbfindindex(code) finds all record pointers indexed
% by a given code
%
global dbkey dbnumkey dbindex dbnumindex dbvalue dbnumvalue
%
% select from table
ind=find(dbindex(:,1)==code);
ptrs=dbindex(ind,2);
return;

More optimisation could be achieved by keeping the index table sorted by key code value, then it would be
quicker to find rows containing a particular code.

A significant source of inefficiency is the fact that we have to scan the key table when adding a key to see if it
has been added already, and when finding a key to get the corresponding code. If we know in advance the
maximum number of different keys, we can simply store each key in a particular row in the table, then directly
test to see if that key is present or not. The idea is to build an empty table large enough to store the maximum
number of keys, then compute a 'hash' value which turns a key into a row number of that table. If that row is
empty, then the key is not in the table, if that row contains the correct key then we have found the correct row,
if that row contains a different key then we have a collision (two keys with the same hash code) so we scan
forward in the table until we find an empty record or the correct key.

First we change the initialise function to add a parameter indicating the maximum number of keys:

function dbinitialise(maxsizevalue,maxsizekey,maxkeys)
% dbinitialise(maxsizevalue,maxsizekey,maxkeys) creates a new database
% for values up to size maxvaluesize, and up to maxkeys keys up to size
% maxkeysize
%
global dbkey dbnumkey dbindex dbnumindex dbvalue dbnumvalue
%
dbnumkey=fix(1.5*maxkeys);
dbkey=char(zeros(dbnumkey,maxsizekey));
dbindex=zeros(1,2);
dbnumindex=0;
dbvalue=char(zeros(1,maxsizevalue));
dbnumvalue=0;
return;

To prevent too many collisions, we actually make the hash table 50% bigger than needed.

To compute the hash value from the key, we multiply the character codes by a set of factors and sum them up.
The key lines are:

% calculate a hash value


fac=99*(1:size(dbkey,2));
code=1+rem(fac*abs(key)',dbnumkey);

Almost any computation would work, but ideally you want a hash computation which gives you very few
collisions. Notice how we use the rem() function to ensure that the hash value is a valid row index.

The code to add a key to the hash table then becomes:

function code=dbaddkey(key)
% code=dbaddkey(key) adds a key value to the database
% returning a numeric code
%
global dbkey dbnumkey dbindex dbnumindex dbvalue dbnumvalue
%
% pad the key
key=pad(key,size(dbkey,2));
%
% calculate a hash value
fac=99*(1:size(dbkey,2));
code=1+rem(fac*abs(key)',dbnumkey);
%
% loop until found or empty
while (abs(dbkey(code,1))~=0)
if (strcmp(dbkey(code,:),key))
return;
end
code=1+rem(code,dbnumkey);
end
%
% not in table, add in
dbkey(code,:)=key;
return;

And the code for finding a key in the hash table becomes:

function code=dbfindkey(key)
% code=dbfindkey(key) finds code for key or returns 0 for not found
%
global dbkey dbnumkey dbindex dbnumindex dbvalue dbnumvalue
%
% pad the key
key=pad(key,size(dbkey,2));
%
% calculate a hash value
fac=99*(1:size(dbkey,2));
code=1+rem(fac*abs(key)',dbnumkey);
%
% loop until found or empty
while (abs(dbkey(code,1))~=0)
if (strcmp(dbkey(code,:),key))
return;
end
code=1+rem(code,dbnumkey);
end
code=0;
return;

Together, these improvements make the code work about 10 times faster.

Exercises

For these exercises, use the editor window to enter your code, and save your answers to files under your
account on the central server. When you save the files, give them the file extension of ".m". Run your
programs from the command window.
1. Write a function 'dbsave(filename)' that saves the database into the named file.
2. Write a function 'dbload(filename)' that loads the database from the named file.
3. (Advanced) Write a function 'dbdelete(value)' that removes a value and all its keys from the database.
Ensure that you only delete a key completely if it does not refer to any other value!
4. (Homework) Implement the database functions in this handout, then modify them so that values can be
retrieved with a partial match to the key. For example, a key value of "ma" would return all values that
have keys which start "ma…".

You might also like