Audio Programming
Audio Programming
PROGRAMMING BOOK
EDITED BY
FOREWORD BY
MAX MATHEWS
nclude
<stdio.h> #include <stdio.h> define SAMPLING_R
#include <stdio.h> #include <stdio.h> define SAMPLING_RATE 44100 #define NUM_SECONDS 3 #define NUM_SAMPLES
NUM_SECONDS * SAMPLING_RATE)
e <stdio.h>
#include <stdio.h>
SECONDS * SAMPLING_RATE)
#define NUM
2010001731
This book is humbly dedicated to our teachers, mentors, and friends Max V. Mathews,
F. Richard Moore, and Barry Vercoe. They paved the way and they showed the way. They
were great explorers who freely and passionately shared their discoveries, their treasures
their source code. Moreover, they invited every one of us to join them on the journey, to follow their example, and to nd ways to become a part of their great adventure and to build
upon the solid foundation that they laid down for us. All the contributors to this book stand
on the shoulders of these three giants of computer music. It is our hope that the book will
help the next generation to fully appreciate the great gifts of Mathews (MUSIC V), Moore
(cmusic), and Vercoe (Csound), and that it will help them nd their own unique and inspiring way to take the world one step further on this extraordinary audio adventure.
Contents
xi
C Programming Basics
0 An Overview of the C Language with Some Elements of CBB
Victor Lazzarini and Richard Boulanger
1 Programming in C
Richard Dobson
55
185
329
383
431
463
viii
Contents
539
557
581
617
629
655
677
Appendixes
A Command-Line Tools Reference
Jonathan Bailey
697
719
739
783
771
521
ix
Contents
797
G Glossary 823
John ftch with Richard Dobson, Victor Lazzarini, and Richard Boulanger
H An Audio Programmers Guide to Mathematical Expressions
John ftch
Contents of the DVD
References 873
About the Authors
Index 881
869
879
855
Foreword
xii
Foreword
with chamber groups and orchestras and thereby add rich new timbres to the already beautiful timbres of acoustic instruments.
What now is the musical challenge of the future? I believe it is our understanding of the
power and limitations of the human brain, and specically discovering which sound waves,
sound patterns, timbres, and sequences humans recognize as beautiful and meaningful
musicand why. This book holds the key to copiously producing the software, sounds, and
music we need to truly and deeply explore these many and hidden dimensions of our
musical minds.
Max V. Mathews
Preface
But how does an oscillator really work? My 40-year journey to The Audio Programming Book
began with that question. Some of the answers came from Tom Piggott (my rst electronic
music teacher, and the one who got me started with analog synthesizersan EML200 and
an Arp2600).
More answers came from Alan R. Pearlman, founder and president of the ARP Synthesizer
Company, the man who commissioned my rst symphony, Three Soundscapes for Synthesizers
and Orchestra.
Still more answers came from Dexter Morrill, who offered me a Visiting Composers Residency at Colgate University, where I made my rst computer music. It took days back then,
but I rendered Happy Birthday in MUSIC 10 and played the rendered soundle for my dad
over the phone on his birthday.
And more answers came from Bruce Pennycook, who was also in residence at Colgate. (We
would work through the night and end our sessions with breakfast at a local diner; I picked
his brain every spare minute; he taught me how to do stereo panning and gave me his subbass oscillator instrument, LOW.)
I started to really answer the question in Barry Vercoes summer workshop at the Massachusetts Institute of Technology, in which I learned music11. (I will never forget Barry lling
whiteboard after whiteboard, and revealing, one morning, that an oscillator consisted of a
phasor and a table.)
I made my way from MIT to the Center for Music Experiment at the University of California at San Diego, where I learned about cmusic from Dick Moore, Gareth Loy, and Mark
Dolson. My rst cmusic composition, Two Movements in C, featured a new trick that they
taught me to do with two oscillators: FM synthesis.
Life brought me back to Boston, and Barry invited me to continue my work at MITs new
Media Lab, where I got to explore and beta-test his new language, Csound. By his side, I was
able to further my understanding and to share some of the answers I had found along the
way through The Csound Book.
Overlapping with my time at the Computer Audio Research Lab in San Diego and the MIT
Media Lab, I got to know and work with Max V. Mathews. He invited me to work in his
xiv
Preface
studio at Bell Labs. (He would sleep in the recording booth there so that I could compose and
program.) We have worked together for more than 25 years now, touring, performing, lecturing, and sometimes sailing. It was from him that I learned the programming language C. We
would spend days and days going over every single line of his Conductor and Improv programs, his Scanned Synthesis program, his PhaserFilters program, and his MUSIC V program.
(Imagine my surprise upon discovering that an oscillator is also an envelope generator, and
then the mind-bending fact that if you scan a mechanically modeled wavetable, an
oscillator can be a lter.)
But still, it took John ftch, Richard Dobson, Gabriel Maldonado, and Victor Lazzarini to
teach me to actually program an oscillatorright here in The Audio Programming Book.
Yes, for me The Audio Programming Book answers my rst question and many others. I
think you will agree that ftch, Dobson, Maldonado, and Lazzarini are wonderful teachers,
and that the other contributors are their young apprentices. I hope the book will answer
all your questions and will raise new ones too.
Richard Boulanger
Acknowledgments
This book has had a long period of gestation, and while we have been at it the world has
changed; some things have disappeared, and some new things have been invented. But the
basic elements of what we wanted to teach are still as relevant today as they were when we
started. Along the way, we have also been helped and supported by many others, and
we would like to give our thanks to them here.
from Victor Lazzarini
I would like to acknowledge the support of my parents. Their support throughout my education years and beyond, both affective and nancial, was essential to my developing career
and life. Even though they might at some point have doubted the wisdom of embarking on
a music degree instead of medical school, they have never been anything but supportive.
I would also like to give thanks to the An Foras Feasa research institute in Ireland, which
provided the funds for my sabbatical at a time when I was heavily involved in nishing this
book. In particular, I would like to say thanks to its director, Margaret Kelleher, and to John
Keating, whose ofce I was able to use in that period. At the NUI Maynooth Music Department, I would like to thank all my colleagues for their friendship and support, Fiona Palmer
for allowing my research leave at a busy time for the department, and Barra Boydell for his
advice, guidance, and example in all things academic. I would also want to acknowledge the
help and support of my research colleagues Joe Timoney and Tom Lysaght, with whom I
have worked for many years and who taught me a lot about signal processing.
My involvement in this book would not have happened had I not made a decision one
morning in 2005 to join the Csound users discussion list. A Csounder since 1993 and an
audio developer since 1996, I had always been a retiring sort of person who was not involved
in the community (although I had contributed to Csound privately by sending things to
John ftch). But that morning, for some reason, I decided to join the list. and this whole new
world suddenly appeared, almost by extension. So I would like to extend my gratitude to
fellow Csounders for the years of interesting conversation and bug reports. Of these new
friends, I would in particular like to acknowledge my new friends Steven Yi and Oeyvind
xvi
Acknowledgments
Brandtsegg. I would also want to thank John ftch and Richard Dobson, who have been
great mentors to me on the art of programming and making noise with it. Last, but not least,
I need to thank my colleague and co-editor Dr. B. for providing the opportunity rst to
contribute to this book and then to co-edit it. His support and guidance have contributed
substantially to my development as a researcher and a composer.
Another group of people I would like to acknowledge and thank is the Linux Audio Developers family, like-minded individuals who are in the business of teaching, learning, and
sharing computer music code as members of the great brotherhood of free and open-source
software. We would not be where we are now in our eld without the efforts of countless
people contributing to FOSS projects, in many guisesa practice that dates back to the
beginnings of computer music.
Finally, I have to thank my three kids, Danny, Ellie, and Chris, for their love and for keeping me on rm ground at times when my mind was about to take off. Another special dedication goes to my wife Alice, who has put up with my work-related bad moods and my
computer-bound disappearances. Her love and support have helped me through a lot of
bad weather, for which I am very grateful.
from Richard Boulanger
Over the years, there have been many brilliant and dedicated students who have helped me
work on, work out, and work through the chapters, the code, and the ideas of the contributing authors. Not only have they worked on this project with me, but they have also been
a huge part of my compositions, my concerts, my lectures, and my research. Their spirit is
in the pages of this book and is channeled through all of my creative work. To date, the
gifted and dedicated students who have served as my musical assistants and collaborators
have included Paris Smaragdis, Eric Singer, Jacob Joaquin, Young Choi, Luigi Castelli, Greg
Thompson, and Kathryn Bigbee. I am eternally grateful to them.
A number of my students wrote chapters for this book. I want to recognize and thank
them for their contributions to my classes and my teaching. When you read and study their
contributions here, I am sure that you too will recognize the dedication, commitment, and
brilliance of Joo Won Park, Barry Threw, Tae Min Cho, Andrew Beck, Federico Saldarini, and
Johannes Bochmann, and you will know how lucky I have been to work with them.
A number of undergraduate and graduate students read specic chapters and offered feedback that helped to focus the authors and signicantly improve the nal content of this
book and the associated DVD. The student reviewers from the Berklee College of Music
were Bobby Pietrusko, David Buchs, Jason McClinsey, Victor Magro, Matthew Thies,
Gautam, Paul Marinaro, Danny Patterson, Brian Baughn, Nicolas Villa, Tom Owen, and
Sean Meagher, those from New York University were Won Lee and Nicholas Barba, the
reviewer from McGill University was Huey How, and the reviewers from the MIT Media Lab
xvii
Acknowledgments
were Noah Vawter, Victor Adan, and Adam Boulanger. Thanks to all of you for making the
time and making a difference.
From the very beginning, I had one student who not only worked through all the chapters
but also found the time to put his talent for graphic design to work; he contributed most
of the gures that appear in this book. His name is Robert Gerard Pietrusko. This talented
musician, engineer, programmer, and designer was able to translate some murky code and
concepts into transcendent and illuminating illustrations.
As the manuscript approached the nish line, two students offered to help me: Jonathan
Bailey and Tim Lukens. The three of us put together a solid Audio Programming class at
Berklee, and they helped me to write strong tutorial chapters to support that class. In addition to making the time to write some very musical code, both of these guys found the time
to write signicant and important chapters of their own for the bookJonathan, in fact,
wrote two. There is no possible way that I can thank Jonathan Bailey and Tim Lukens
enough for all the work that they did to make this book happen, and to make our chapters
what they are. Their musical code is at the very core of my writing and my teaching, and
for that I am most grateful to them both.
Clearly, my students have been a profound and deep source of inspiration to me, but at
Berklee the real support for my research and my development has come from my department chairmen, David Mash and Kurt Biederwolf, and from my deans, Don Puluse and
Stephen Croes. For more than 22 years they have supported my travel, my studies, my
courses, and my compositions; moreover, they have brought my collaborators to the college
to work with my students and to work with me. They continue to support my growth and
development, and I will be forever grateful to them for this wonderful gift.
A number of colleagues read and reviewed the preliminary chapters and code for this book.
Their insights helped to focus this book on the target audience. I would like to thank Javier
A. Garavaglia, Michael Rhoades, Luis Jure, Sal G. Soa, Josep M. Comajuncosas, Mark Ballora,
David L. Phillips, and Steven Yi.
Of all the comments and the constructive criticism that I received over the years, perhaps
the most important wake-up call came from my closest colleague, collaborator, and friend,
Lee Ray. About two years ago, I asked Lee to check out the nal draft. He read every
pageat the time, all 3,000. And after two months, Lee probably made one of the most difcult phone calls of his life. He let me know everything that was broken, missing, dull,
out of place, out of date, redundant, inferior, and in some cases inappropriate. Time had
clouded my judgment and my ability to see what was there and, more important, what I
was missing. Lees insights opened my eyes again and helped to get me back on the right
track.
In the beginning, the number of contributing authors was slightly larger than the nal
group. They began this journey with me, but for one reason or another some contributions
do not appear in the book. Still, I want to thank the contributors for helping to move the
xviii
Acknowledgments
project forward and for reading, reviewing, suggesting, and inspiring. On behalf of all the
contributing authors I would like to thank Michael Gogins, Javier Garavaglia, Micha Thies,
IOhannes m zmoelnig, Craig Stuart Sapp, Nick Didkovsky, and Matt Ingalls. These are the
most productive, prolic, and inspiring developers on the computer music scene today, and
I am grateful for their help.
Clearly, I am grateful to all my contributing authors for their brilliant contributions, but
there are four contributing authors who have been working with me on this project from
the very beginning, almost 10 years ago. Over this period, computer technologies, compilers,
processors, and platforms changed dramatically, and most times the amazing technological breakthroughs broke everything that we had built (and trashed everything I had tested
and edited). There were so many times that we had to set out in an entirely new direction
that any normal programmer or professor would have just given up. Still, when everything
was broken, these four were determined enough and committed enough to pick up the
pieces, rewrite their code, and start all over. Time and time again, they forged ahead. I
learned a great deal from each of them, and I stand in awe of all the creative possibilities
they have manifest through the software that they have written. A small glimpse of that
dynasty is now a part of this book. Richard Dobson, Gabriel Maldonado, John ftch, and Eric
Lyon: on behalf of all of your future and current students (and I consider myself to be one
among them), I thank you all for everything you have taught me over these past 10 years.
One other contributor deserves special mention: Victor Lazzarini. Victor came along when
we were well into the project, and yet, with his code and his words, he raised the book up to
a new level of relevance and excellence. Where there was a hole, he would ll it. Where there
was a missing piece, he would nd it (in his brilliant mind) and then write it up (and code it
up) in what seemed like no time at all. And when all his chapters were written, and all his
code was fully tested and running on all platforms, he offered to help edit and test the
mountain of words and code. Before I knew it, I had a brilliant, passionate, and dedicated
partner on this project. Together we would tag team around the world and around the
clock, and we managed to get two years work done in a matter of months.
I asked Doug Sery at the MIT Press if I could ofcially offer Victor the title of co-editor. (He
was already doing the job.) I was prepared to argue that he had become indispensable, that
he had spent six months making one signicant contribution after another, and that he
brought an essential and substantive level of expertise to the project (as was evident from
the content of his chapters and his code). But I didnt need to present my case at all. Doug
had noticed the difference in the quality and content of the product. He had noticed the
accelerated rate of production too. In fact, in less than a minute, Doug gave me the thumbs
up. Because of Victors expert editorial contributions, this book is now in your hands. I am
honored and proud to have him as a major contributing author and as my co-editor.
I cant end these acknowledgments without thanking my parents, Dick and Claire
Boulanger. Its sad because I work hard to impress them, but when they look at any single
page of this book they both shriek with horror and wish I would just slow down and
xix
Acknowledgments
smell the roses and spend some time enjoying life with them by the pool. Oh well. Even if
there isnt a single page in here that makes one bit of sense to you, know that its all for you
and because of you, mom and dadwith respect, gratitude, and love. And now that its
done, there might be a bit more pool time.
And I cant end these acknowledgements without apologizing to my three wonderful sons,
Adam, Philip, and Adrien. I hope you can forgive me for my absence over the past 10 years,
and for the all-too-frequent short-fuse verbal explosions when I was struggling at the computer to nd a word and you would have liked me to listen, for a minute or two, to some of
your words.
When I started this book, Adam was graduating from high school; now he is married and
graduating from MIT with a Ph.D. Where did that time go? Philip is playing classical cello
professionally in Chicago and pursuing his second masters degree. When I started this project, he was practicing to get a decent chair in the Massachusetts Southeast District Orchestra.
Adrien was in elementary school (second grade, I think), and we were both obsessed with
Pokemon. As the text was delivered to the MIT Press, Adrien was starting college.
I hope Adrien will use this book in one of his CS classes someday. I hope Adam (Dr. B. v2.0)
will teach one of his classes from this book someday, or his daughter Anas. And I hope Philip and I can get back to performing and touring and making new music togetherworking
on our Book of Dreams.
This book is about making new music by discovering new ways of transforming and
transguring sounds. I hope it will open a door to tomorrows concert hall and online classroom for my sons and for you, your students, and your children.
Finally, I offer my deepest and most heartfelt thanks to my wife Colette. This beautiful,
caring, and loving cardiac-rehab nurse put all the pieces of this quite literally broken and
broken-hearted man back together. I had a heart attack along the way, and Colette brought
me back to life and back to this project. Without her love, her understanding, and her sacrice, neither the book nor I would have seen the light of this day.
But in the end, it was the faith, support, and encouragement of Doug Sery that kept this
project alive, in me and at the MIT Press. Everyone interested in audio programming truly
has him to thank for this fantastic bookand we sincerely do thank you, Doug.
Introduction
In chapter 0, we provide a complete overview of the C language and some essential elements
of C. Using examples written from a musicians perspective, we guide the reader through
the elements of these two languages that will be used in the audio programming chapters of
this book.
In chapter 1, Richard Dobson introduces readers with no prior experience to the basics of
C programming. All the examples employ a music-oriented focus, from MIDI-frequency calculations to the textual breakpoint le and a tuning-fork emulation including the creation of
raw binary soundles. It also introduces primary tools such as Audacity and Gnuplot.
In chapter 2, Richard Dobson introduces the specially written PortSF soundle library. This
is used to develop a number of useful programs designed to demonstrate the generation and
processing of basic envelopes and waveforms (including examples of additive synthesis). In
the course of introducing further important C language elements, it explores the development of the table-lookup oscillator and other basic tools.
In chapter 3, Gabriel Maldonado shows us how to send audio data to the operating systems standard output, to a raw binary le, to a RIFF-Wave le, and directly to the audio interface in real time by using different libraries: the Tiny Audio Library, PortSF, and PortAudio.
In chapter 4, John ftch introduces important software engineering principles such as
object-oriented design. These are articulated through the construction of a simple wave-table
playback program. The description includes debugging the resulting program and some additional notes on the thought processes of program construction.
In chapter 5, Victor Lazzarini introduces more formal aspects of digital signal processing
theory, including some basic mathematical concepts. Programming examples demonstrate
some essential operations on audio signals. The chapter concludes with a look into the
implementation of FM synthesis.
In chapter 6, Lazzarini explores several basic elements of waveform processing, including
synthesis and the transformation of audio signals. These processes are presented as components implemented as C functions, exposing the details of their operation. The chapter
concludes with an introduction to plug-in development, with the VST architecture used as
an example.
xxii
Introduction
xxiii
Introduction
Appendix D covers the basics of real-time audio programming using the PortAudio library.
It demonstrates the two modes of operation supported by the library, blocking and asynchronous, with comparative programming examples.
Appendix E introduces MIDI programming using the PortMIDI library. It features three
examples covering output, input, and the combination of these two.
Appendix F is a short introduction to how computers work and to the functioning of their
main components. Further, this appendix shows how computer languages are compiled into
a form that can be executed.
Appendix G is a glossary that provides support for the entire book. It includes entries that
range from simple denitions, to more extended ones, with some tutorial elements. This
appendix offers a concise introduction to a range of words and topics that readers may
encounter in this book or in other resources. The readers are presumed to have music as their
primary eld of knowledge; accordingly, the glossary concentrates on subjects relating to
programming, audio engineering, and computer architectures. Computing has created a
very large body of jargon words. This appendix explains those you are most likely to meet
as a computer musician.
Appendix H presents and explains the most important mathematical concepts for the
audio programmer and computer musician, and other mathematical concepts that audio
programmers and computer musicians might encounter.
C Programming Basics
The programming languages C and C are used throughout this book, and yet the details
of the languages are often secondary to the more important issues of audio programming.
After all, this is The Audio Programming Book, and programming audio is the main goal. However, it is appropriate to reserve a little space here at the beginning for a general discussion of
the languages themselves. C is often described as a small language, and therefore we believe
it possible to present, discuss, and review the whole of it in a very compact way.
0.1
C and CBB
Most people get somewhat confused when learning about the programming languages C and
C. Sometimes the two are confused into one; sometimes C is taken to be C and vice
versa. They are, effectively, two separate and different languages, so they should not be confused. In practice, they are very similar and share many elements, but we should be careful
not to mistake one for the other. As a rule of thumb, just think of C as a C with extra
elements. While not the complete picture, this is often all you need to know.
Sometimes C is taught with some elements of C that simplify the coding. While this
might be reasonable for someone who knows what is C and what is C in the code, for
beginners this is very confusing. A secondary issue here is that C allows a more sophisticated programming approach, known as object-oriented programming, which is complex to
tackle for the novice. In this chapter, we will only deal with C at rst; at the end, we will introduce some features of C.
0.2
Building C Programs
0.2.1
C is what we call a compiled language. This is because programs, written with C code in a text
le, are generated by a package of programs called the compiler. These programs transform
Chapter 0
the text code into binary codes that are understood by the machine and the operating system. The process of compilation/program building is discussed below.
Interpreted languages, such as Python, are generally higher-level languages that are dependent on an interpreter program, which translates the text code into machine actions directly.
Their advantage is that the result of the programming can be seen immediately. On the
other hand, generally speaking, the overhead of interpreting the code can make these
languages very inefcient for intensive tasks (such as calculating the output of a synthesis
routine, in the case of a music system).
0.2.2
Compiling
After opening your console/terminal window, you can invoke the compiler program can be
called by the simple command
cc mysource.c
where cc is the C compiler and mysource.c is the name of your source le. (gcc is a very
widespread compiler, if you are using it, just change the command for gcc.) The output of
this command will be a binary executable le called a.out, which you can rename (using mv)
if you want. This le will sit in the directory you are working on.
For more practical uses, you can output the binary executable to a different le, using
-o . . . (just as in Csound -o means output to):
cc -o myprogr mysource.c
On Windows machines your program lename will have the extension .exe. On other
systems no extensions are used.
0.2.3
Source Files
Source les are text (ASCII) les containing the source code written in C, which will be used
to build the program. There can be just one or many of these needed to build a program.
0.2.4
Header Files
Header les (usually with extension .h) are les with denitions and parts of code which are
included in (i.e. they will be copied to) the source les. The compiling process will use the
code in these header les in the compilation process.
0.2.5
Libraries
C is a very lean language. It has a number of built-in denitions, rules, etc.; the rest is done
by elements (functions, data denitions, etc.) existing in libraries. These are pre-compiled
(i.e. they are already in binary) and are added to the executable at the last phase of the build
of the program. In this phase, called linking, references to libraries are linked to the actual
binary code.
There are libraries for virtually anything you want to do in C: math, sorting, comparing
things, controlling processes, playing audio and MIDI, etc. When using different libraries,
you will have to inform the compiler which one(s) you are using. This can be done with
the relevant command-line options. (For more details, see appendix A.)
0.2.6
A C executable program requires that at least one function be present in it. This is what we
call the entry point of the program: where it starts executing and also providing the exit point,
i.e. when the function ends, the program also nishes. By default this function is called
main(), but this can be changed by a compiler ag (although we will not normally do it).
This function can take two forms:
int main( );
and
Chapter 0
a-z
0-9
[]
()
<
.
>
,
|
:
+
;
-
'
/
$
*
"
=
#
%
&
tab space
There are only 32 keywords (which are reserved symbols) in the C language. They are combined with the formal syntax to form the language proper. These symbols cannot be used for
any other purpose:
auto
double
int
struct
break
else
long
switch
case
enum
register
typedef
char
extern
return
union
const
float
short
unsigned
continue
for
signed
void
default
goto
sizeof
volatile
do
if
static
while
0.4
An Example Program
Our rst example is the one traditionally given in C courses:
#include <stdio.h> /* header file for IO */
int main()
/* main function */
{
printf("Hello World\n"); /* print message */
return 0; /* function returns 0 (0K) */
}
Here we have all the typical components of a program:
n
n
n
n
n
n
If you build this program with the commands shown above and then run it, you will get
Hello World printed to the terminal.
0.5
Data Types
The rst element of C programming is based on the concept of a variable. Basically, variables
are memory slots that are reserved for you to stored numeric values or characters in. In C, we
need to declare these at the start of a function before we can use them, by stating the variable type and its name. The basic data types for variables are the following:
intan integer (a whole number)
floata oating-point value (with a decimal part)
doublea double-precision oating-point value
chara single character.
In addition, we will also see the type void, which is used to indicate no type or any type,
depending on its use. As for names, all variables must begin with a letter or an underscore.
0.5.1
An int variable, in most systems, can store a value in the 32-bit range from 2,147,483,648
to 2,147,483,647, but this is system dependent. For example,
int a;
creates a memory slot for an int and calls it a. Other types of integers also exist: a short
integer or short is stored as a 16-bit number (two bytes), and its range is from 32768 to
32767. Integers can be signed (the default) or unsigned. The latter are positive only and
have twice the positive range of a signed integer (from 0 to 4,294,967,295). The type long
can be used to mean 32-bit integers.
unsigned int ua; /* an unsigned integer */
unsigned long ulb; /* an unsigned long integer */
short sample; /* a 16-bit integer */
0.5.2
Floating-point data types are so named because they store a decimal-point number in two
parts: an exponent (which tracks the point position) and a mantissa (which holds the actual
numbers over which the point oats).
float: A oating-point number has about seven digits of precision and a range of about
1.E36 to 1.E36. A float takes four bytes to store. Example: float result;
double: A double precision number has about 13 digits of precision and a range of about
1.E303 to 1.E303. A double takes eight bytes to store. Example: double value;
Chapter 0
0.5.3
Characters
The type char holds a single character, stored in one byte. Example:
char c;
This type is most often used to store ASCII characters (which are themselves seven-bit codes),
but in fact can be used for any single-byte numerical use.
0.5.4
Casting
Table 0.1
Order of operations in C and C.
Operator
Associativity
Left to right
Right to left
* / %
Left to right
+ << >>
Left to right
Left to right
<
<=
> >=
Left to right
== !=
Left to right
&
Left to right
Left to right
Left to right
&&
Left to right
||
?:
Left to right
Right to left
Right to left
Left to right
0.5.6
Arithmetic Ordering
Expressions are calculated using a certain precedence order, shown in table 0.1. If in doubt of
the precedence of operations, use parentheses to group them.
0.5.7
A variable has to be declared before it is used, so that the compiler can create the memory
slot for it. You can also assign an initial value to a variable when you declare it. For example,
int a, i=1;
declares two variables (separated by commas) and initializes the second to 1.
0.5.8
Variable Scope
Variables declared within a program block are valid, and in existence, only inside that
block (and enclosed blocks). A program block is delimited by braces, so a function is a program block. These variables are called local to separate them from variables declared outside
any block, which are global and are seen by all functions within the source-code le. It is best
to avoid global variables.
10
Chapter 0
0.5.9
Constants
Constants are numeric values that cannot be changed throughout a program. Integer constants are normally written in base-10 format (decimal system): 1, 2. For long integer
constants an L is added: 2L, 10L. Floating-point constants will have two forms: with an f
at the end, for oats and just with a decimal point somewhere for doubles (2.f is a float;
2.0 is a double).
Integer constants can also be written as either hexadecimals (base 16) or octals (base 8). An
octal constant is preceded by 0 and a hexadecimal by 0x.
The decimal 31 (0001 1111 in binary) can be written as an octal (037) or as a hexadecimal: 0x1F.
Octal units will range from 0 to 7. Hexadecimal ones will range from 0 to F, with AF representing the decimals 1015. For instance, F in hexadecimals represent (1-valued) set bits,
so a 16-bit bitmask 0xFFFF is a series of 16 set bits (1111 1111 1111 1111).
Macros can be used to give constant names. The pre-processor statement #define will do
this for you, and so
#define END 10000
will substitute 10000 for any instances of the word END, so you can use END as a constant in
your code. The preprocessor takes care of this for you.
0.6
11
Table 0.2
The ANSI C format speciers.
Specier
Type
%c
char
Single character
%d (%i)
int
Signed integer
%e (%E)
float or double
Exponential format
%f
%g (%G)
float or double
float or double
Signed decimal
Use %f or %e as required
%o
int
%s
Sequence of characters
%u
int
Unsigned decimal
%x (%X)
int
we only have a format string and, as this contains no % characters it results in Hello World
being printed without anything extra. As an example of a format specier, %d means print a
value as a signed decimal integer, so
printf("Total = %d", total);
will print Total = and then the value of the variable total.
Here are some important notes on format speciers:
1. The specier following % indicates the type of variable to be displayed as well as the
format in which that the value should be displayed.
2. If you use a specier with the wrong type of variable, you will see some strange things on
the screen, and the error often propagates to other items in the printf list.
3. You can also add l in front of a specier to mean a long form of the variable type, and h
to indicate a short form. Short integers are 16-bit (two bytes) whereas long ones are 32-bit
(four bytes). For example, %ld means a long integer variable (usually four bytes), and %hd
means a short int. Notice that there is no distinction between a four-byte oat and an
eight-byte double. The reason is that a oat is automatically converted to a double precision
value when passed to printf, so the two can be treated in the same way.
0.6.2
The % speciers that you can use in ANSI C are given in table 0.2. A modier that will determine how the value will be printed can precede each specier. The most general modier is
of the form
flag width.precision
12
Chapter 0
Table 0.3
The ANSI C format modiers.
Flag
Use
Left justify.
space
0
#
Table 0.4
Examples using the # modier.
Specier
Display
%#o
%#x
%#f or %#e
%#g
where width species the number of characters used in total to display the value and
precision indicates the number of characters used after the decimal point. The format
modiers are shown in table 0.3.
For example, %10.3f will display a oat using ten characters with three digits after the
decimal point. (The ten characters would consist of the decimal point and a minus sign,
if there were one.) The specier %-1Od will display an int left justied in a ten-character
space. The specier %+5d will display an int using the next ve character locations and
will add a plus sign or a minus sign to the value. The # modier can be used as shown in
table 0.4.
Finally, ASCII control codes can be used in a string as shown in table 0.5. These escape
sequences are used to display non-printing or special characters. They essentially control
how the text is positioned on the screen.
0.6.3
scanf
The scanf function works in much the same way as the printf, as it has the general form
scanf(controlstring,...)
In this case the control string species how strings of characters, usually typed on the keyboard, should be converted into values and stored in the listed variables. To store values,
the scanf function has to have the addresses of the variables rather than just their values.
13
Table 0.5
Escape sequences and their results.
Escape sequence
\b
Backspace
\f
Formfeed
\n
Newline
\r
\t
Carriage return
Horizontal tab
\v
Vertical tab
\'
Single quote
\"
Double quote
\0
Null character
\a
Alert (beep)
So we will pass the address of the variable (&variable) instead of just the variable. In general, all of the relevant format speciers listed in connection with printf can be used with
scanf. The scanf function processes the control string from left to right, and each time it
reaches a specier it tries to interpret what has been typed as a value. If you input multiple
values, then these are assumed to be separated by white space (i.e. blanks, newline, or tabs).
For example,
scanf("%d %d",&i,&j);
will read in two integer values into i and j. The integer values can be typed on the same line
or on different lines as long as there is at least one white space between them. Note, however, that in the case of the %c specier scanf does not skip white space and it will read
the next character typed, regardless of what it is.
The width modier can be used in scanf to limit the number of characters accepted to
width. For example,
scanf("%lOd",&i)
would use, at most, the rst ten digits typed as the new value for i.
0.6.4
A Simple Example
14
Chapter 0
Control of Flow
C provides a number of ways of controlling the ow of execution of a program, so that it
does not always have to proceed statement by statement as in the previous example. Before
we examine the different forms of controlling the ow, we have to introduce ways of checking the truth of conditions and statements: logical expressions.
0.7.1
Logical Expressions
A logical expression is an expression used to test a certain condition in a ow control statement. For example, a > 0 is true if a contains a value greater than zero, and b < 0 is true if b
contains a value less than zero. The concept of truth in C is very simple: 0 if not true and
non-0 if true. So a logical expression generally evaluates to 0 or non-0 (say, 1). The following
relational operators are used in logical expressions:
>
<
>
<=
==
!=
Note that the equality operator is == and not simply =. The last operator means NOT equal
to. Because truth conditions are zero or non-zero, a zero-valued variable can also be used to
indicate falsehood, and a non-zero variable to indicate truth.
0.7.2
Conditional Execution
One of the programming devices use to control the ow of a program is called conditional
execution. This is performed by an if statement:
if (a > 0) printf("%d is positive", a);
15
If the condition is false, the program continues with the next instruction. In general, the if
statement is of the form
if (condition) statement;
The statement above can be a single statement or a program block. In addition, it is also
possible to select between two statements, one to be obeyed when the condition is true and
one to be obeyed when the condition is false. You can do this using
if (condition) statement1;
else statement2;
This is a variation of the if statement. In this case statement1 is carried out if the condition is true and statement2 if the condition is false. Notice that both parts of the if-else
statement can be blocks or single statements terminated by a semicolon:
if (f2 ==0) printf("division by zero !!!\n");
else printf("%f/%f = %f\n",f1, f2, f1/f2);
A third form of if can be used for selecting several options:
if (condition) statement1;
else if(condition2) statement2;
. . .
else if(conditionN) statementN;
else else-statement;
This can be used if several conditions need to be checked, and the last else is used to catch
the case where none of the above are true.
0.7.3
Conditional Operator
Switch
An alternative to select from several options is the switch statement, a multiple selection
statement. A variable is successively tested against a list of integral constants. When a match
16
Chapter 0
is found, the program executes from the matched label onwards. The general form of the
switch statement is
switch(expression)
{
case constant1: statement sequence; break;
case constant2: statement sequence; break;
. . .
case constantN: statement sequence; break;
default: statement sequence; break;
}
Each case is labeled by one or more integer-valued constants. The default label is reached
if no matches are found. The default is optional. If all matches fail and default is absent,
no action takes place. When a match is found, the program switches execution to the statement sequence associated with that case. The use of break is important, because the execution will continue forward within that program block, possibly executing more than one of
the cases. Using break ensures that the program block is exited after the relevant statements have been executed. For example:
switch (i)
{
case 1:
printf("one");
break;
case 2:
printf("two");
break;
case 3:
printf("three");
break;
case 4:
printf("four");
break;
default:
printf("out of range");
}
This program fragment recognizes the numbers 14 and prints the value of a variable in
English. The switch statement differs from if in that switch can only test for equality,
whereas the if conditional expression can be of any type. Also, switch will work with
only int and char types.
17
0.7.5
Here we demonstrate the three forms of conditional execution introduced above. This is a
little music theory program that calculates the interval between two notes and prints its
semitone length and name. We ask the user to input two notes, which we take in as characters and then translate into numerical pitch classes (011). The reason for the dummy variable is that the user is expected to separate the two note names by a carriage return (or
enter) character, or even a blank space. As we are scanning characters, these have to be
accounted for, so the dummy variable is used as a placeholder. (Though we could have used
note2, for the sake of clarity we have not.)
#include <stdio.h>
int main()
{
char note1,note2, dummy; /* note names, dummy char */
int pc1, pc2, interval; /* pitch classes, interval */
printf("Please enter two natural notes.\nfirst note: ");
scanf("%c%c",¬e1, &dummy);
printf("second note: ");
scanf("%c",¬e2);
switch(note1){ /* translating from note name to pitch class */
case 'C': case 'c':
pc1 = 0;
break;
case 'D': case 'd':
pc1 = 2;
break;
case 'E': case 'e':
pc1 = 4;
break;
case 'F': case 'f':
pc1 = 5;
break;
case 'G': case 'g':
pc1 = 7;
break;
case 'A': case 'a':
pc1 = 9;
break;
case 'B': case 'b':
pc1 = 11;
break;
18
Chapter 0
default:
printf("error: %c is not a natural note\n",note1);return 1;
}
switch(note2){
case 'C': case 'c':
pc2 = 0;
break;
case 'D': case 'd':
pc2 = 2;
break;
case 'E': case 'e':
pc2 = 4;
break;
case 'F': case 'f':
pc2 = 5;
break;
case 'G': case 'g':
pc2 = 7;
break;
case 'A': case 'a':
pc2 = 9;
break;
case 'B': case 'b':
pc2 = 11;
break;
default:
printf("error: %c is not a natural note\n",note2);return 1;
}
/* calculate the interval and keep it modulo 12 */
interval = pc2 - pc1;
if(interval < 0) interval += 12;
else if(interval > 11) interval -= 12;
/* print the number of semitones. The special case of
unison (0) has to be handled correctly, so we use the
conditional operator for this */
printf("%d semitones up or %d semitones down\n", interval,
interval ? 12-interval : 0 );
/* now we print out the interval name */
switch(interval){
case 1:
printf("minor 2nd up or major 7th down\n");
break;
case 2:
19
down\n");
down\n");
down\n");
5th down\n");
4th down\n");
down\n");
down\n");
down\n");
down\n");
}
return 0;
}
As we will see later, this program is longer than it has to be if we use all the tools that the
C language offers. However, it demonstrates what is possible with the elements introduced
so far.
0.8
Loops
Until now we have been using programs that have a certain number of instructions that are
read and executed, line after line depending on control ow statements (if, else, or
20
Chapter 0
switch). If we want some parts of the program to be repeated, we will have to use a neat
programming device: the loop.
0.8.1
You can repeat any statement using either the while loop:
while(condition) { . . . }
or the do while loop:
do { . . . }while(condition);
The condition is just a test to control how long you want the program block to carry on
repeating. In the case of the while loop, before the code block is carried out the condition
is checked, and if it is true the block is executed one more time. If the condition turns out to
be false, the loop is exited. In the case of the do while loop, it will always execute the code
within the loop at least once, since the condition controlling the loop is tested at the bottom
of the loop.
One of the typical ways of controlling a loop is to employ a counting variable, which will
be incrementing every time the loop repeats:
a = 0;
While( a < 10){
. . .
a=a+1;
}
The increment is such a common operation that we have a special operator for it:
++a; or a++;
In this case, ++a increments a before using its value, whereas a++ means use the value in a,
then increment the value stored in a. Decrement operators (--) also work in the same way.
In addition, += and -= are used to increment or decrement by more than 1.
Other ways of stopping a loop include querying the user with scanf() and examining the
value of an arithmetic expression.
0.8.2
The loop controlled by a counting variable is so common that a special form is designed to
provide a compact way of doing it. This is implemented by the for loop
for (counter=start; counter < end; counter++) statement;
which is equivalent to
21
counter=start;
while (counter < end)
{
statement;
counter++;
}
Here is an example creating the classic Fahrenheit-to-Celsius conversion table:
for (fahr = 0 ; fahr <= 300 ; fahr = fahr + 20)
printf("%4d %6.1f\n", fahr, (5.0/9.0)*(fahr-32));
0.8.3
Here we show the use of a loop to implement a repetitive task. In this program, we want to
print the notes of a major scale in ascending steps. This involves adding two semitones to a
starting note at every repetition, except after the third note, when we have just one semitone. In order to print the note names, we translate the pitch class numbers by bringing
them to the range 011 (modulo 12, we can use the % operator since all notes are now
non-negative).
#include <stdio.h>
int main()
{
int note, i;
printf("Please enter the key (in pitch-class number, 0-11): ");
scanf("%d",¬e);
/* make sure start note is not negative */
while (note < 0) note += 12;
/* build the scale */
for (i=0; i < 7; i++){
/* translate pitch-class to note name */
if(note%12==0) printf("C ");
else if(note%12 == 1) printf("Db ");
else if(note%12 == 2) printf("D ");
else if(note%12 == 3) printf("Eb ");
else if(note%12 == 4) printf("E ");
else if(note%12 == 5) printf("F ");
else if(note%12 == 6) printf("Gb ");
else if(note%12 == 7) printf("G ");
else if(note%12 == 8) printf("Ab ");
else if(note%12 == 9) printf("A ");
else if(note%12 == 10) printf("Bb ");
22
Chapter 0
The break statement allows you to exit a program block from anywhere within it (we saw
how it can be used in a switch block). So it can be used to exit a loop from inside it, without
using the loop conditional test. The continue statement is used to jump directly to the test
from anywhere in the loop, skipping any remaining statements.
0.9
Bitwise Operations
C permits a set of low-level operations, known as bitwise, that add some useful functionality
to the language. These operators, as the name implies, are dened to work at the bit level,
on the individual zeroes and ones that make up the binary information. They are dened
for integer use.
0.9.1
Bitwise Logic
A number of operators are dened for bitwise operations; they compare each bit of one
operand with the corresponding bit of the other operand:
&
|
^
~
bitwise AND
bitwise inclusive OR
bitwise exclusive OR
ones complement (unary operator).
Bitwise AND (&) returns a set bit (1) only when both sides of the operation have that bit set.
It is often use with bitmasks to lter bytes off an integer:
short mask = 0xFF00, value, masked;
value = 0x0111;
masked = mask & value;
In the example above, the mask will only let the higher byte pass, ltering off the lower
one. Thus the value of masked will be 0 0100:
23
Bitshift Operators
multiplication by 2 n .
multiplication by 2 n .
24
Chapter 0
Thus, a fast way of multiplying or dividing by 2 is to left or right shift a number by one
position. The division will be rounded down to an integer.
0.10
Functions
In C, functions are also known as subroutines or procedures. A function is simply a block of
code grouped together and given a name. This block of code can then be invoked just by
using its name in a statement. For instance a function can be declared to print a message
and exit:
void message()
{
printf("This is my message\n");
}
Now that it is dened, you can use it:
int main()
{
message();
return 0;
}
As the function does not return anything, it is dened with the return type void, meaning
nothing. It also does not take any arguments, so there is nothing inside the parentheses.
Note that any variables declared inside a function (a code block) are only valid and seen
inside that block. These variables are local and they will disappear when function exits. In
order to pass in values to local variables and to get the answers out of functions, we will use
arguments (parameters) and return values.
0.10.1
Passing values to a function is done via its arguments. These are dened within the parentheses, each one with its type and separated by commas. Arguments declared like this also
behave as local variables for the function. In order to pass the answer (if any) that the function provides, we use the keyword return:
int sum(int a, int b)
{
int result;
result=a + b;
return result;
}
25
This denes a function (called sum) with two parameters, a and b, both integers, returning
an integer, and can be used as
a = sum(2,2);
This is a call to the sum function with a set to 2 and b set to 2, and so result is set to 4.
You can also initialize parameters to the results of expressions such as
a = sum(x+y,z*w);
This will set a to the result of x+y and b to z*w. The values of the arguments are copied into
them as the function is called. C passes copies of variables as arguments, not the variables
themselves. In summary, a function has the general form
type FunctionName(arguments)
{
statements
}
Notice that the function will always exit when it reaches a return statement. The rest of the
code beyond it will be skipped. The return statement can return a value or (in the case of
void return types) nothing at all.
0.10.2
Prototypes
Before a function is used, the compiler must be given the return type and any arguments
for it. Thus, in order to use it, we will rst have to declare it. We need not completely dene
(implement) it before it is used; we need only declare it. In that case, we can provide the
function prototype: its type, name, and arguments. For instance,
int sum(int, int);
is the prototype for the sum function dened above. The function denition can then go
elsewhere in the source codeafter main(), or even in a different source le. By default, if a
function is not declared before use, it is assumed to be an int functionwhich is not always
correct.
0.10.3
Now we can use functions to implement tasks that might be repeated with different inputs,
so we can have a more compact program. Here we put the translation code of our rst music
theory example program into a function, then call it when we need pitch classes for the
interval calculation:
26
Chapter 0
#include <stdio.h>
/* this function implements translation */
int nameToPc(char name){
switch(name){
case 'C': case 'c':
return 0;
case 'D': case 'd':
return 2;
case 'E': case 'e':
return 4;
case 'F': case 'f':
return 5;
case 'G': case 'g':
return 7;
case 'A': case 'a':
return 9;
case 'B': case 'b':
return 11;
default: /* error code */
return 100;
}
}
int main()
{
char note1,note2, dummy;
int interval;
printf("Please enter two natural notes.\nfirst note: ");
scanf("%c%c",¬e1, &dummy);
printf("second note: ");
scanf("%c",¬e2);
/* to calculate the interval, we call nameToPc() to translate */
interval = nameToPc(note2) - nameToPc(note1);
if(interval > 20 || interval < -11) {
printf("either %c or %c are invalid notes\n", note1, note2);
return 1;
}
if(interval < 0) interval += 12;
else if(interval > 11) interval -= 12;
printf("%d semitones up or %d semitones down\n", interval,
interval ? 12-interval : 0 );
return 0;
}
27
0.10.4
The Standard C Library provides a great number of functions that perform very useful tasks,
including input and output, math, string and character manipulation, and memory allocation. The advantage of using this library is that it has more or less the same behavior across
platforms and is generally guaranteed to work everywhere. Table 0.6 shows some of the functions in the Standard C Library.
0.11
Arrays
So far we have been able to create memory slots only for single variable types, but in many
applications we will require whole blocks of memory for storing values contiguously. This
can be done using arrays. For example,
int a[5];
declares an array called a with ve elements. The rst element is a[0] and the last a[4], so
all the arrays are zero-based. In general you have
type array[size]
which declares an array of the specied type and with size elements. The rst array element is array[0] and the last is array[size-1]. Arrays can be initialized as follows:
int a[5] = {1,2,3,4,5};
The for loop and arrays go together very well. The for loop can be used to generate a
sequence of values to pick out and process each element in an array in turnfor instance,
for (i=0; i<5; i++) a[i] = i+1;
to ll the array with 1, 2, 3, 4, 5.
0.11.1
Two-Dimensional Arrays
28
Chapter 0
Table 0.6
Some of the more popular functions from the standard C library.
stdio.h
I/O functions
getchar()
putchar()
printf()
Outputs to stdout
scanf()
string.h
String functions
strcat()
strcmp()
strcpy()
ctype.h
Character functions
isdigit()
isalpha()
isalnum()
islower()
isupper()
math.h
Mathematics functions
acos()
asin()
atan()
cos()
sin()
exp()
fabs()
sqrt()
time.h
time()
difftime()
clock()
stdlib.h
Miscellaneous functions
malloc()
rand()
srand()
29
0.11.2
Strings are arrays of characters, and the C programming language uses the convention that
the end of a string of characters is marked by a null character (ASCII code 0). To store the
null character in a character variable, you can use the notation \0. However the compiler
will automatically add a null character and store each character in a separate element
when you use a string constant. A string constant is indicated by double quotation marks, a
character constant by single quotation marks): "A" is a string constant and 'A' is a character
constant. It is important to realize and remember that strings are always arrays and so cannot
be assigned directly, as in
char string[40];
string="name"
However, you can print strings using printf and read them into character arrays using
scanf. You can also initialize a string with a string constant:
char name[40] = "hello";
But to manipulate strings we will need functions that deal with the fact that they are arrays
(and that they are terminated with a null character). Standard library string functions, such
as strcmp() and strcat(), are designed to operate on such arrays of characters.
0.11.3
We can now make our scale program more elegant by using an array of strings as a translation table. First, we get the key as a string, use a loop to look it up on the table, and obtain
the index to it, which is our pitch class (from 0 to 11). Then we can use the table again to
print out the note name. We use the strcmp() library function to match the strings. It
returns 0 if the strings are the same.
#include <stdio.h>
#include <string.h>
int main()
{
int note, i;
char key[3];
char* scale[12]={"C","Db","D","Eb",
"E","F","Gb","G",
"Ab","A","Bb","B"};
printf("Please enter the key(capitals only, "
"use b for flats,
eg. Eb):");
scanf("%s",key);
/* use table to translate note name to pitch class */
30
Chapter 0
Pointers
As we mentioned earlier, a variable is a memory slot that has been given a name. For example, int x; is an area of memory that has been given the name x, and x=10; stores the data
constant 10 in memory slot x. The computer accesses its own memory by using a memory
map with each location of memory uniquely dened by a number called an address. A pointer
is a variable that holds this location of memory, the address of a variable. We declare it just
like any other variable, but we use an asterisk to mark it. For example,
int *pa;
is a pointer to an integer. To use a pointer, we will employ two special operators: & and *. We
have already seen that the & operator returns the address of a variable. For example,
int *pa, a;
declares pa, a pointer to int, and an int, and the instruction
pa=&a;
stores the address of a in pa. We say that pa is pointing at a.
31
The operator * is the indirection operator. A * in front of a pointer variable gives the
value stored in the variable pointed at. That is, pa stores the address of a variable, and *pa
is the value stored in that variable.
a = 10;
b = *pa; /* b is now also 10 */
*pa = 12; /* a is now 12 */
In summary:
n
n
n
0.12.1
32
Chapter 0
or
for(i=0; i<n; i++) pa[i]=rand()%n+1;
If you dene pa as a pointer, you can use array indexing notation with it as well as pointer
arithmetic.
0.12.2
We now see that a string is just a character array with the end of the valid data marked by
an ASCII null character \0. Manipulation of strings is based on pointers and special string
functions. For example, the strlen(str) function returns the number of characters in the
string str. It counts the number of characters up to the rst null in the character array, so
it is important to use a null-terminated string.
We need a function to copy strings, because simple assignment between string variables
does not work. For example,
char a[l0],b[10];
b = a;
does not copy characters. It just makes pointer b set point to the same set of characters that a
points to, but a second copy of the string is not created (in fact, this code will not even compile). What we need is strcopy(a,b), which copies every character in a into the array b up
to the rst null character. Similarly, strcat(a,b) adds the characters in b to the end of
the string stored in a, and strcmp(a,b) compares the two strings, character by character,
and returns 0 if the results are equal.
Notice that, to assign a character array to a string, you cannot use
a = "hello";
because a is a pointer and "hello" is a string constant. However, you can use
strcopy(a,"hello");
because a string constant is passed in exactly the same way as a string variable, i.e. as a
pointer. And remember, a string can always be initialized. For example:
char a[6]="hello";
0.12.3
As an example of the use of pointer arithmetic (i.e. incrementing and decrementing memory
addresses), we present a program that calculates the result of transposing a note by any number of semitones (positive or negative). This program uses an array that is a table of note
names. We set a pointer to the start address of that array and then increment it until we
nd the base note. We then add to this pointer the interval in semitones (after bringing
33
this to the range 011), then make sure the address is in the correct range (otherwise we
wrap it around). This will result in the pointer being set to the address in the table where
the correct note name is:
#include <stdio.h>
#include <string.h>
int mod12(int note){
while(note < 0) note += 12;
while(note >= 12) note -=12;
return note;
}
int main() {
char note[3], **p1, **p2,
*table[12] = {"C","C#","D","D#",
"E","F","F#","G",
"G#","A","A#","B"};
int interval;
printf("Enter base note (capitals, use # for sharps, eg. A#): ");
scanf("%s", note);
printf("Enter interval in semitones: ");
scanf("%d", &interval);
/* point p1 to the beginning of the array and p2 to its end */
p1 = table;
p2 = table+11;
/* now find the base note position,
incrementing the pointer until we find it */
while(strcmp(*p1,note)){
p1++;
if(p1 > p2) { /* if we're past the end */
printf("could not find %s\n", note);
return 1;
}
}
/* add the interval to the address of the base note */
p1 += mod12(interval);
/* if beyond the end of the table, wrap it around */
if(p1 > p2) p1 -= 12;
/* print result */
printf("%s transposed by %d semitones is %s\n",
note, interval, *p1);
return 0;
}
34
Chapter 0
0.12.4
Pointers to Functions
In C it is possible not only to have pointers to built-in data types, but also to have pointers to
functions. With this facility, we will be able to manipulate functions as pointers, pass them
to other functions, put them in arrays, and so on. It is quite an advanced principle, but very
useful. For instance,
void (*pf)();
is a pointer to a function that takes no parameters and does not return anything. In that
case, we can assign
void message(){ printf("my message\n"); }
pf = message;
and call the function using a pointer:
(*pf)(); /* will call message() */
or, even simpler,
pf();
This is because (*pf)() and pf() are equivalent, both being the function that is pointed at.
In general, we have this form for function pointer declarations:
return-type (*pointer-name) (arguments);[
Why do we need something like this? Well, function pointers are useful for callbacks, i.e.
functions not invoked directly by you but by existing code. For instance, suppose we have
the function
void message_printer(int times, void (*callback)(char *msg),
char *user_mess){
int i;
for(i=0; i < times; i++) callback(user_mess);
}
This invokes a user-dened function to print out a user-supplied message, a callback. The
user would then supply a callback for message-printer() to call it, as in the following
example:
void my_important_message(char *mess){
printf("VERY IMPORTANT: %s \n," mess);
}
void my_warning_message(char* mess) {
printf("WARNING: %s\n," mess);
}
35
int main() {
message-printer(10, my_important_message, "functions can be pointers");
message-printer(1, my_warning_message, "but be careful");
return 0;
}
This advanced concept can be much more useful than the trivial example above, but this
gives you an idea of what a callback is. These types of programming constructs are often
seen in audio systems, where the programmer supplies a callback that will be used by the
system to perform a particular task (such as sending audio samples to the soundcard).
0.13
Structures
The array is an example of a data structure made up of a single data type. If you need something that holds a variety of types together in a single block, you will need a single data structure using a single name, provided in the C language by struct.
0.13.1
With C structures we have a new, user-dened, data type. To use them, rst we need to
dene what the new type looks like, using struct:
struct note
{
char name[3];
int duration;
char intensity[5];
};
Then we can declare a variable of that type, to be used in the program:
struct note first;
Notice that the new variable is called first and is of the type note. This can be considered
as valid a data type as any of the built-in types, such as float or int. In general, you can
dene a structure using
struct name
{
list of member variables
};
and you can have as many member variables as you need. Once dened you can declare as
many examples of the new type as you like, using
struct name list of variables;
36
Chapter 0
For example,
struct note first, second, third;
and so on. If you want to avoid using struct for variable declaration, you can use typedef.
For instance,
typedef struct _note
{
char name[3];
int duration;
char intensity[5];
} note;
note first;
denes the structure, and a new type based on it called note, then uses the newly created
data type directly. To use the data type, you can access each member by name, using a period
to link the variable name and the requested structure member:
first.name
first.duration
Once you have used a qualied name to get down to the level of a member variable, it
behaves like a normal variable of the type dened in the structure. For example,
first.duration = 120;
is a valid assignment to an int, and
strcpy(note.name, "C");
is a valid statement. Notice that to access the member we use the structure variable name
and not the structure type name. You can also dene a structure that includes another structure as a component and of course that structure can contain another structure and so on.
Structures can be initialized using syntax that is similar to array initialization:
note first = { "Ab", 80, "mf" };
As another example of the use of structures, we would like to dene a type to hold complex numbers. A complex number is composed of two partsa real part and an imaginary
partthat can be implemented as single or double precision values. This suggests dening
a new struct type:
typedef struct comp
{
float real;
float imag;
} complex;
37
38
Chapter 0
{
float re,im;
complex a,b;
get two complex numbers as inputs */
printf("Please enter the first complex number (re, im): ");
scanf("%f%f", &re, &im);
a.real = re; a.imag = im;
printf("Please enter the second complex number (re, im): ");
scanf("%f%f", &re, &im);
b.real = re; b.imag = im;
/* multiply them */
a = mult(a,b);
printf("Their product is %f + %fi \n", a.real, a.imag);
return 0;
}
Finally, notice that passing a struct by value (to a function) can use up a lot of memory
in the case of large data structures (which is not the case in the example above). This is
because a copy of the structure is made for the function argument. To avoid this, you can
use pointers to structures.
0.13.2
Pointers to Structures
You can declare a pointer to a structure in the same way as any pointer. For example,
person *ptr
denes a pointer to a person. You can use a pointer to a struct in the same way as any
pointer, but the typical means of structure access makes it look rather awkward. For example,
(*ptr).age
is the age component of the person structure that ptr points ati.e. an int. You need the
brackets because a period has a higher priority than an asterisk. The use of a pointer to a
struct is so common that you can use
prt->age
to mean the same thing as (*ptr).age. The notation gives a better idea of what is happening: prt points (->) to the structure and age selects which component of the structure we
want.
0.13.3
Structures can only contain built-in data types, so a function cannot be directly dened
inside a structure. However, as we can declare pointers to functions, we could use them to
39
hold functions, which in some cases might be useful. For instance, it would be nice to be
able to have an increment function for the complex data type. First, we could declare a structure containing a function pointer
typedef struct comp {
double real, imag;
void (*incr)(struct comp *p);
} complex;
Then we could dene a function to operate on the data (well use a pointer, as it is more
efcient):
void incr1(complex *p){ p->real++; p->imag++; }
To use this structure and its associated function, we can write the following code:
complex a = { 0, 0, incr1 };
a.incr(&a);
0.13.4
Dening functions to work on data structures is very useful. In C, there will be an easier
way to do it. Structures with functions associated with them are in fact a point of connection
between C and C, as they become classes in C. In simple terms, a C class is a structure in which you can dene members that are functions (not only function pointers). You
will also be able to control access to the data members of a class. Moreover, the same .
access system applies to the class and the use of pointers and the -> operator. These elements will be further discussed in later sections of this chapter.
0.14
40
Chapter 0
pa = malloc(sizeof(int)*N);
where N is the number of ints you want to create. But because malloc is used to allocate an
arbitrary memory block, it returns a void pointer, which must then be cast to the right type:
pa = (int *) malloc(sizeof(int)*N);
This makes the value returned by malloc a pointer to int. After this denition, you can use
ptr as normal. For example,
pa[n]
is the nth element of the array. When you are nished with the memory, youll have to use
free(pa); to free it.
0.15
Command-Line Arguments
Now let us look at how to get arguments off a command line. This uses the second form of
main() shown at the beginning of the chapter. Command-line arguments are passed to a
program through two arguments to the main() function. The parameters, are called argc
and argv, are seen in the following main prototype:
int main(int argc, char **argv)
The argc parameter holds the number of arguments on the command-line and is an integer. It will always be at least one because the name of the program is the rst argument. The
argv parameter is an array of string pointers. All command-line arguments are passed to
main() as strings. For example:
#include <stdio.h>
int main(int argc, char *argv[])
{
int i;
for (i=1; i<argc; i++) printf("%s", argv[i]);
return 0;
}
This program will print out all of its arguments, starting with the program name.
0.15.1
We will now use many of the C language tools learned in this chapter to create a fully functional program for analyzing serial music. Suppose we want to apply the rules of twelve-tone
music to create a matrix with all the serial forms that derive from a single tone row. The rst
row of the matrix will receive the original twelve notes, from left to right. The inversion of
41
the series will ll the rst column of the matrix, from top to bottom. To complete the matrix,
the other rows will receive transposed versions of the original series, each one starting with
the note in the rst column. When the matrix is complete, we will be able to read all the
transposition forms of the original series in the rows from left to right, the retrograde forms
in the same rows from right to left, the inversions in the columns from top to bottom, and
the retrograde of inversion forms in the opposite sense. Here are the steps that we will have
to take to achieve this:
1. As in previous examples, we will take pitch classes from 0 to 11, which form the original
series, from the command-line arguments, and place this series in the rst row of the matrix.
2. We will use two arithmetic formulae for the inversion and transposition of the original
series. The inverse form, appearing in the rst column, is given by
Pm,1 [(Pm1,1 (P1,m1 P1,m )] mod12
(1)
(2)
42
Chapter 0
}
/* loop until all available notes are entered*/
for(n = 0; n < 12; n++)
series[0][n] = mod12(atoi(argv[n+1]));
/* create inversion in column 1 */
for(m = 1; m < 12; m++)
series[m][0] = mod12(series[m-1][0] + series[0][m-1]
- series[0][m]) ;
/* create all transpositions */
for(m = 1; m < 12; m++)
for(n = 1; n < 12; n++)
series[m][n] = mod12(series[0][n] + series[m][0]
- series[0][0]);
for(m = 0; m < 12; m++){
/* print the pitch classes, row by row, using the
translation table */
for(n = 0; n < 12; n++) printf(" %s ", table[series[m][n]]);
printf("\n");
}
return 0;
}
This concludes our tour of C, which demonstrates how compact yet expressive and efcient it can be. As a complement, let us look briey at some elements of C.
0.16
Moving to CBB
The C programming language is a super-set of C. As such, it supports and allows all the C
coding we have done so far, but it introduces new elements and additional resources, which
hopefully make life easier for the programmer. There are many new features in C (in fact,
C is anything but a small language), only a few of which we will introduce here. We must
remember that if strict C is required, then we cannot use any C features in a program.
When writing pure C code, we should use the le extension .c; when writing C code,
we should use .cpp. This will avoid confusion and make sure that the building tools call
the right compiler (for C or for C).
0.16.1
The C programming language allows variables to be declared only at the start of a code block
({ }), whereas C allows variables to be declared anywhere. For instance:
43
/* C example */
void func(){
int a,b,c;
a = g();
b = f();
c = a+b;
}
// C++ example
void func(){
int a;
a = g();
int b;
b = f();
int c = a + b;
}
0.16.2
C provides a simpler mechanism for memory allocation, which is perhaps less awkward
than malloc() and friends. This mechanism is implemented with two C operators: new
and delete.
The former is used to allocate memory for any built-in or user-dened variable type, as in
// memory allocation for float pointer variable a
44
Chapter 0
float *a
// ditto
int *b =
// ditto
MyStruct
= new float;
for int pointer variable b
new int;
for struct Mystruct pointer variable s
*s = new MyStruct;
In C, when dening a structure, we are also creating a new data type, so we do not need
the typedef keyword to dene it. In fact, a struct is a special case of a programming structure called a class, which can also contain constructors and methods (functions that are members of a class). For the moment, it is sufcient to know that we can write
struct MyStruct {
int a, b;
float f;
};
// no need for typedef...
MyStruct obj;
obj.a = 1; // etc...
0.16.5
Line Comments
C also supports a single-line comment, using // as the rst characters in a line of code.
We have been using this type of comments in the examples above. This turns all the text on
the rest of the line into a comment (until a new line character is seen).
0.17
Data Abstraction
Data abstraction is another important element of programming that is enabled in a more
consistent way by C than by C. It consists of dening new data types that would model
45
46
Chapter 0
struct_name::member_func()
For example, the constructor will be dened as
Osc::Osc(float *tab, float ph, int len, int vsize, int sr){
table = tab;
phase = ph;
length = len;
vecsize = vsize;
ndx = 0.f;
rate = sr;
output = new float[vecsize];
}
and the processing function will be
float *Osc::Proc(float amp, float freq) {
float incr = freq*length/rate;
for(int i=0; i < vecsize; i++){
output[i] = amp*table[(int)ndx];
ndx += incr;
while(ndx >= length) ndx -= length;
while(ndx < 0) ndx += length;
}
return output;
}
The above code demonstrates that functions belonging to a struct have access to their
local variables and to the member variables of the structure they belong to. This is very useful because table indexes and wave tables can be kept outside the function and separate from
the rest of the program. In addition, each instance of the Osc data type will have its own
individual member variables, and the user need not supply them to the processing function.
0.17.1
Function Overloading
47
struct Osc {
// dataspace
float amp; // amplitude
float freq; // frequency
float *table; // ftable
float phase; // phase offset
float ndx; // ndx
int length; // ftable length
int vecsize; // vector size
float rate; // sampling rate
float *output; // output audio block
// methodspace
Osc(float amp, float freq, float *tab, float ph=0.f,
int len=def_len,int vsize=def_vsize, int sr=def_sr);
~Osc() { delete[] output; }
// fixed amp & freq
float *Proc();
// variable control-rate amp & freq
float *Proc(float amp,float freq);
// audio-rate amp
float *Proc(float *amp,float freq);
// audio-rate freq
float *Proc(float amp,float *freq);
// audio-rate amp & freq
float *Proc(float *amp,float *freq);
};
0.17.2
Usage Examples
xed parameters
Osc zing(0.5f,440.f,wtable);
for(...) {
sig = zing.Osc(); // 440 Hz 6dB sound
}
FM synthesis
Osc mod(ind*fm, fm, sintab);
Osc car(amp, fc, sintab);
48
0.18
Chapter 0
Classes
In C, a version of struct exists in which all members are private by default. It is called a
class:
49
class Osc {
// dataspace is private by default
float *table; // ftable
(...)
// methodspace needs to be accessible
public:
Osc(float *tab, float ph=0.f, int len=def_len,
int vsize=def_vsize, int sr=def_sr);
(...)
};
Classes and structures are basically the same thing, although the former is more commonly
used. They provide the support for a programming style called object-oriented programming
(OOP), in which the ideas discussed above in relation to data abstraction and data hiding
play an important part. In OOP parlance, we call the instances of a data type objects.
0.18.2
In this subsection, we will present a version of our serial analysis program using the concept
of data hiding and encapsulation. We will start by modeling the concept of a twelve-tone
series and the basic operations that we can apply to it:
class Dodecaphonic {
protected:
int series[12]; /* the tone row, hidden from outside */
int mod12(int note) { /* the modulus as an internal method */
while(note >= 12) note -= 12;
while(note < 0) note += 12;
return note;
}
public:
Dodecaphonic() { /* default constructor */
for(int i=0; i < 12; i++) series[i] = 0;
}
Dodecaphonic(int *notes) { /* constructor from an array */
for(int i=0; i < 12; i++) series[i] = mod12(notes[i]);
}
int get(int index){ return series[mod12(index)]; /* get & set
notes */
void set(int note, int index) {
series[mod12(index)] = mod12(note); }
50
Chapter 0
51
b = a.invert().transpose(1);
Here is a program that uses this class (the ellipsis is a placeholder for the class code dened
above):
#include <stdio.h>
#include <stdlib.h>
. . .
int main(int argc, char** argv)
{
Dodecaphonic row, res;
int interval, n;
if(argc != 14 || argv[1][0] != '-'){
printf("usage: %s [-oN | -rN | -iN | -irN] "
"note1 note2 ... note12\n",
argv[0]);
return -1;
}
for(n = 0; n < 12; n++) /* initialize the row object */
row.set(atoi(argv[n+2]), n);
switch(argv[1][1]){
case 'o': /* original transposed */
interval = atoi(argv[1]+2);
res = row.transpose(interval);
break;
case 'r': /* retrograde */
interval = atoi(argv[1]+2);
res = row.retrograde().transpose(interval);
break;
case 'i': /* inverted */
if(argv[1][3] != 'r'){
interval = atoi(argv[1]+2);
res = row.invert().transpose(interval);
}
else { /* inverted retrograde */
interval = atoi(argv[1]+3);
res = row.invert().retrograde().transpose(interval);
}
break;
default:
printf("unrecognized option \n");
return -1;
}
52
Chapter 0
Inheritance
Classes (and structures) can also be created from other classes by inheritance. This allows the
re-use of code ( rather than rewriting it) and the denition of a common interface for various
interrelated objects (all derived classes will share some code, especially functions). The syntax for deriving a class from an existing one looks like this:
class Osci : public Osc {
(...)
};
This makes Osci a derived class from Osc. All public members in Osc are also made public
in Osci. Thus Osci can access all the public members in Osc, but not the private ones. In
order to make private members accessible to derived classes, we must use the keyword
protected, which means private to the class and all its subclasses.
0.18.4
Virtual Functions
C has also a mechanism that allows functions to be specialized in subclasses. These are
called virtual functions. By using the keyword virtual for a function on the superclass, we can
make these functions overridable; that is, we can supply new code for their implementation:
class Osc {
(...)
virtual float *Proc(float amp,float freq);
};
The Proc() function can be then re-declared and re-implemented in a derived class (Osci,
say) to implement an interpolated lookup (instead of truncation):
class Osci : public Osc {
(...)
virtual float *Proc(float amp,float freq);
};
This allows a system to provide an interface for processing functions, which can be implemented by derived classes. The OOP term for this is polymorphism. An example of the use of
53
such a mechanism is found in the application programming interface for VST plug-ins,
which is written in C and is based on the derivation of classes from a base class.
0.18.5
Overloaded Operators
Similarly to overloaded functions, it is possible to dene overloaded forms of specic operators for a certain class denition. The operators =, +, -, *, /, <<, and >> can be overloaded.
For instance, we could set up the overloading of operator + for our Osc class as follows:
class Osc {
(...)
float *operator+(float val) {
// adds val to every sample in the output block
for(int i=0; i < vecsize; i++) output[i] += val;
// returns the audio block
return output;
}
};
Now using + with an Osc object and a float has a denite meaning: add that number to
every sample in the output audio block of the Osc object (and return a block of samples).
Here is an example of how it can be used:
Osc oscil(...);
float a = 1000.f;
float *buffer;
(...)
for(...) {
(...)
buffer = oscil + a;
(...)
}
0.19
Final Words
In this chapter we have focused on the aspects of C that should be useful for audio programming, and on almost the whole of the C language. We hope the chapter has served as a
quick refresher course if you are an experienced programmer and as an inspiring appetizer if
you are a beginner.
Clearly, C is much more extensive than C. C has its own powerful and robust standard library, the C Standard Template Library, which is beyond the scope of this chapter
but is well worth exploring.
54
Chapter 0
In the chapters that follow, you will nd both slow-cooked and fast-food audio
recipes in C and C that will sustain, nourish, and inspire the development of your own
unique audio and musical applications regardless of your level of expertise. Along the way,
you will learn to program in both C and C; at the same time, you will learn the fundamentals and secrets of digital signal processing, software synthesis, computer music, algorithmic composition, and digital audio.
Programming in C
Richard Dobson
1.1
Why C?
This section concentrates, as any rst section on programming must, on the essential features of the language used. However, unlike general books on programming, this book has a
specic focus, from the outset, on music and audio applications. A useful advantage of this is
that it gives an opportunity to address in very practical ways the question of why you need
to know this, or do that. We have all seen instruction manuals with statements such as to
decouple the bifurcated sprocket ange, use the long-handled dingbatwithout explaining
why, and in what circumstances, you would want or need to do it.
In each of the examples of C code that follow, some audio or music aspect is incorporated
or developed. This gives a clear focus, and I hope it will give at least partial answers to the
why question, but a pure presentation of the language runs the risk of concealing issues
that a more thorough theoretical approach might consider important. So the approach here
is based on the desire to get results, rather than on a foundation of theory; theoretical principles will arise rather as an emergent feature, on which a stronger emphasis is placed in later
sections. Nevertheless, the question Why C? is worth addressing at the outset.
The most obvious problem we face in programming audio processes is speed of computation. Many techniques, such as spectral transformation, have been known about for decades,
but only with computer processing speeds at the level they have reached in the last few years
has it been possible to use them in real time. Even now, ensuring that the code is as efcient
as possible is still of great importance, especially for real-time performance. It translates into
more of everything (more oscillators, more lters, more channels) and higher quality (better
lters, better reverbs, and so on). Fortunately for the purposes of this book, computers are
fast enough now that we can in many cases afford the luxury of code that is arguably inefcient to a degree, but demonstrates a process clearly. Nevertheless, throughout the book,
hints and tips on optimizing working code for maximum performance will be given; and
as your understanding develops, these techniques will gradually become part of your stock in
trade. They are typically based on knowledge of how compilers work, or how computers
work (e.g. how data is moved around in memory), or both. C programming does tend to
demand that, sooner or later, this knowledge will be essential.
56
Chapter 1
The C language has often been described as close to the hardware. Expressions in the
language can be compiled to very efcient machine code, exploiting the facilities of each
processor. Some idioms in C, such as the auto-increment and decrement operators and the
address-of and pointer operators, match machine-level instructions exactly. As a result, C
programs may run as fast as programs written in low-level assembler code. The complexity
of modern processors, coupled with a powerful code optimizer (an essential component of
any C compiler), can even mean that a compiled C program may be more efcient than a
skilled programmer can achieve working in assembler. However, these idioms presuppose
an understanding of how the processor handles instructions and data, and even of exactly
how that data is stored in memory. C does little to hide such things from the programmer,
and does even less to prevent you from writing code that can crash the program (and maybe
even the computer). Thus, C is a permissive languageit allows you to do things that it
would be a very good idea not to do.
One aspect of C that facilitates closeness to the hardware is, curiously enough, the complete absence, within the language itself, of anything to do with input and output. Text
input from a keyboard and output to a screen or printer depend on hardware, and usually
on the facilities of a supporting operating system. However, an embedded processor may
have neitherperhaps it is connected to transducers and a set of LEDs. All such facilities
are external to the language and have to be supplied by libraries, which now are highly
standardized and which a complete compiler package will always include.
This closeness to the hardware also has made the C language pre-eminent for the writing
of operating systems. C is associated primarily with UNIX and its variants. Many people
today maintain that the ideal platform for the development of audio applications is one or
other avors of UNIX ( including Linux and now Macintosh OS X) and its native C compiler.
That compiler will include a set of libraries supporting all aspects of the operating system,
including support for input and output devices of all kinds.
In view of the importance of speed for audio programming (and the need to be able to talk
directly to hardware such as A to D and D to A converters, MIDI ports, and so on), it is easy
to understand why C has become popular. As a result of its widespread use, a lot of expertise
and a lot of help are available, and there is a great mass of published source code (covering
the range from simple tools to advanced large-scale applications) to draw upon.
Despite being close to the hardware, C also has the characteristics of a high-level language. (High-level means close to the user; low-level means close to the hardware.) It
provides a variety of powerful and exible facilities for creating and managing large applications, while making it easy to write simple yet useful tools. Much that is very useful can be
done without the need for advanced programming skills. This power comes only partly
from the language itself, which supports a building-block approach to programming. The
rest comes from the development environment supporting the compiler itselfcopious messages from the compiler to help track down errors, and a powerful text pre-processor that,
though in principle external to the language, is in practice inseparable from it.
57
Programming in C
58
Chapter 1
1.1.1
Before proceeding, it is worth looking at appendix A (which deals with the compiler and
command-line tools) so that you have an idea of the programming environment we will be
using. These early examples assume a traditional command-line environment. While all the
example programs presented here are available on the DVD as complete projects, it is preferable that you practice typing in the code by hand, especially if you are unpracticed at typing.
The text editor, used to write and edit programs, is of supreme importance, so the sooner you
get familiar with it the better.
Listing 1.1 is a faintly musical version of the classic Hello World program that everyone
traditionally writes rst when learning a new language. Although it is extremely short, it
illustrates many of the fundamental aspects of C described above. Listing 1.1 includes line
numbers (starting from 1) in the left margin for convenience. These are not actually part of
the program codeall text editors used for writing programs will display, somewhere on the
screen, the line containing the typing cursor.
Listing 1.1
1
2
3
4
5
6
7
8
9
10
int main()
{
printf("What a Wonderful World!\n");
return 0;
}
The rst thing to notice about this example is that there is a lot of what is called white
space: there are empty lines, and some lines are indented. As far as a C program is concerned, all this white space is almost always unimportant. There are situations where the
presence or absence of white space is meaningful (syntactically signicant), but a lot of
the time we can use it freely, to lay out the code as we wish.
Line 1 comprises a text comment. You could delete this whole line and the program would
still compile. Comments are ignored by the compiler; they are there purely for the benet of
the human reader. This comment simply identies the name of the le and its purpose. A
comment in C is anything placed between the /* and */ character pairs (there must be no
space between the * and the /). These are characters that cannot legally occur together as C
code. There is no requirement for the comment to t on a single line. The lines below
equally form a legal comment:
59
Programming in C
/* This is a comment,
running over
several lines.
*/
Apart from their primary use to add annotations to source code, comments are often used
to prevent lines of code from being compiled, without having to delete them. There are
other, arguably better ways of doing this, but the practice is widespread. Depending on
your text editor, text inside a comment may be drawn in a special color, to make it easy to
see where the comment starts and ends. If this is the case, try deleting the closing */ and see
what happens.
Some programmers like to write elaborate titles and descriptions at the head of a source
le, as in the following example:
/********************* WONDER.C
/*
*
* Version 1.0 RWD 10/1/2002.
***************/
60
Chapter 1
denition of a function must allow for the possibility of either or both input and output. In
this case, the main function outputs a single integer number, indicated by the type specier
word int in line 5.
In the code, int is an example of a C keyword, a word that belongs to the language, and is
therefore reserved (you cant use it as the name for anything else). It is one of a collection of
keywords that dene basic numeric types (see section 1.2). Another keyword in this program
is return, used in line 8.
After the function name main there is a pair of parentheses. These provide a space for any
input data. In this case there is no input, so we can just write the parentheses by themselves.
Later, we will see alternative ways of dening the main function to enable it to receive input
from the user.
So far, the main function looks as follows:
int main()
But this is incompleteit does not dene what the function does. This is the job of the
functions body. The body of a function is dened by code placed between braces, as in this
example:
{
return 0;
}
Now the complete structure of the main function looks as follows:
int main()
{
return 0;
}
You can, in fact, replace all of lines 59 with the above, and the program will compile. It just
wont appear to do anything. However, it is actually performing one task. The main function
is dened as producing an integer result; and the one line of code shown above does this: it
causes main to output the value 0. To do this we are using the C keyword return, followed
by the required value. In the jargon of C programming, the return value of main() is 0.
Overall, this line of code forms a C statement. As was shown above, a C statement is terminated by a semicolon. Where to put a semicolon is one of the aspects that most often confuse beginning C programmers. As you study the code examples in this book, take careful
note of where a semicolon is required and where it is not. You will see that the code inside
the main function comprises one or more statements, each ending in a semicolon (look back
at listing 1.1), but the function body (dened by braces) is not followed by a semicolon.
What happens to the value returned by main()? In the majority of cases it is just ignored;
however, where programs are run remotely, from a UNIX shell script or from a DOS batch
le, it is possible to read the return value, and to take action accordingly. Typically, a return
61
Programming in C
value of 0 means that the program terminated without errors, and anything else means
that some error occurred. For example, if a program requires a le to be opened, but the
name given is incorrect (e.g. the le doesnt exist), you can trap this in the program and
have main() return some other value, such as 1. You will see as you work through later sections of this chapter that this principle is entirely generalthe return value from a function
may be something you use elsewhere, or it may be an indication of success or failure.
The substance of listing 1.1 is found in line 7:
printf("What a Wonderful World!\n");
This is the line of code (a single C statement, ending in a semicolon) that does something
useful: it writes a message to the console. The presence of a matching pair of parentheses
looks very like the format of the main() function just describedand indeed this statement
does contain a function, whose name is printf, which is shorthand for print with formatting. Inside the parentheses is the text to be displayed (referred to by programmers as a
string, or sometimes a character string), within double quotation marks. In the jargon of programming, this string is the argument to the function printf, and overall the statement constitutes a function call.
The nal two characters in the message string may be unfamiliar. They relate to the formatting aspect of the printf function. The rst of the two characters is a backslash. In the
context of a character string, this is called the escape character. It signies that the following
character is not to be written literally to the console, but is instead a control instruction to
arrange the text in a particular way. In this case the control character is n, which signies
new line (sometimes also called carriage return and line feed). To see how this works,
you can delete these two characters, re-compile and run the program, and see what happens.
In line 7, the control characters are at the end of the line. However, they could be placed
anywhere in the string. You can even write a string that contains only the control characters,
and you can write them as many times as you like. For example:
printf("\nThe Hills are alive,\nWith the Sound of Music!");
printf("\n");
We will see in later sections that printf is even more versatile than this. It can print numbers too, and control characters are available to organize numbers with respect to spacing,
number of digits, and even to write them in technical formats such as hexadecimal.
1.1.2
Two questions remain unanswered. Previously, it was stated that the C language does not
contain a print command, yet here we are using one, called printf. Where does it come
from? And what is the meaning of line 2, which we so casually skipped over earlier?
The printf function is an important example of a C library function, in this case supporting text output to the console. Most important, it is a standard library function that you
62
Chapter 1
can expect any C compiler to include. But as it is not part of the C language itself. The compiler has to be given information about itits name, its inputs, and its outputs. This information takes the form of a declaration. We will discover how a declaration is written in a
later section. The important thing to grasp at this stage is that the declaration of printf is
contained in a text le called stdio.h (the le extension .h is used by convention to show
that the le is a C header le), and is incorporated into your program by a preprocessor
command (directive). This is the purpose of line 2 in listing 1.1. Your code le is passed
to this preprocessor before being passed to the compiler itself. The preprocessor strips out
all the comments, and can also be asked to perform text substitutions, or to add text from
another le, using the preprocessor directive include. All preprocessor directives are indicated by the initial #, followed by the name of the commande.g. #include. The whole
directive is then followed by the name of the required le, enclosed in angle brackets:
#include <stdio.h>
Although by convention the command immediately follows the character # without an
intervening space, this is not mandatory. The spacing
# include <stdio.h>
is legal.
When the compiler is installed on your system, a large number of header les are stored in
a special include directory. The compiler thus knows about this directory, and will automatically search it to nd the requested le. The use of the angle brackets, as shown above,
ensures that this directory is searched rst for les. An alternative notation using double quotation marks asks the preprocessor to search the directory containing the current le, before
searching further:
#include "myfuncs.h"
This would be used typically for local include les that are specic, and usually private, to
a project. You will see plenty of examples of this in later sections, especially in projects
involving multiple source les.
When your program is compiled, the compiler also looks for the code for printf in a
library le in a lib directory, and links that code with yours to create the nal executable. In
subsequent sections, you will be introduced to many more standard C functions that are
required to be supported by all C compilers.
1.1.3
Exercises
Exercise 1.1.1
Create a new program, perhaps called music.c (if you are using an IDE, you will also have
to create a project for it called music), and write a new message of your choice. Add some
informative text inside a comment block at the top of the le.
63
Programming in C
Exercise 1.1.2
Find out what happens (i.e. what messages the compiler reports) if you
(a) leave out #include <stdio.h> (you can do this without deleting text, by commenting
out the line using /* and */)
(b) leave out the angle brackets
(c) swap the braces and the parentheses
(d) leave out one or both of the semicolons
(e) replace the rst semicolon with a comma
(f) leave out the return statement
(g) leave out the nal brace.
1.2
Basic Arithmetic in C
In this section we will see how to perform basic arithmetical calculations in C. To do this, we
need a means of representing numerical values, an idea of how the computer stores them,
and a means of performing arithmetical operations on them. This may already sound complicated, but fortunately the way arithmetical expressions are written in C is very similar to
how they might be written down on paper. Whether using paper and pencil or writing a
computer program, in order to work anything out we need to be able to represent numbers
by name. For example, you probably will recognize this simple calculation for nding the
area of a rectangle:
area = width * height
By convention, an asterisk is used to signify multiplied by or times. We replace width
and height with numbers and perform the calculation, the result of which is area. The line
above is a simple arithmetical statement. To make it a complete C statement, we must add a
semicolon:
area = width * height;
Note the use of appropriate words, rather than arbitrary letters or symbols. The names make
it completely clear what the task is, and thus the code is self-documenting.
1.2.2
64
Chapter 1
Table 1.1
Binary operators.
Operator
Example
Description
Assign the value of b to c.
Assignment
c = b
Addition
c = b + a
Subtraction
c = b - a
Multiplication *
Division
/
c = b * a
c = b / a
Modulo
c = 12%11
example, the right-hand side of the calculation above, here enclosed in parentheses to make
the point clearer:
(width * height)
Expressions can contain expressions, for example
(100 + (width * height))
Ultimately, even a single number is an expression, evaluating to itself. A statement is any
legal expression, or combination of expressions, possibly combined with one or more C keywords, and terminated by a semicolon. Indeed, the semicolon by itself is a legal C statement,
and this fact commonly gives rise to errors when a semicolon is used by mistake. Normally, a
statement changes something; typically, a variable acquires a new value, or the ow of control through the program is changed.
An expression can also be a statementfor example,
width * height;
This is, however, useless, as the value of the expression is ignored.
1.2.3
Operators
65
Programming in C
1.2.4
Numeric Types
We are already remarkably close to performing a calculation in C, but some information the
compiler needs is missing. It needs to know what sorts of numbers we are using so it can
store them correctly and so it can perform the correct kind of processing on them. The two
most important classes of numbers are integers (1, 0, 1, 2, 3, . . .) and oating-point numbers
(e.g., 3.14159, 0.707, 1.414).
C includes a wide range of integer types (described in subsection 1.2.5), but the most frequently used has the keyword int. There are two oating-point types: a float occupies four
bytes and is described as a single-precision number, and a double usually occupies eight bytes
and, as its name suggests, is a double-precision number.
To complete the statement above in C code, we must dene each of our variables in terms
of a numeric type:
double area;
double width;
double height;
These declarations tell the compiler to set aside some memory to hold each variable, and
dene what type each one is. In the next line, we put some numbers into the variables:
width = 10.0;
height = 21.5;
The semicolon is ubiquitous: a variable declaration is a statement, as is an assignment
a statement using the operator =, properly called the assignment operator. In programming
jargon, we would say that the value 10.0 is assigned to the variable width. It is also common
to refer to the right-hand side of an assignment statement (the right-hand side has to be
an expression, therefore) signifying a possibly elaborate calculation that produces a result
that can be stored on the left-hand side.
Why call area, width, height, and so on variables? We can assign a value as often as
we like to a variable, like so:
width = 10.0;
width = 0.0;
width = 1000.3;
However, all that is remembered is the last assignment. So literally, the contents of the variable width can vary over time. We can also assign the value of one variable to another:
height = 21.5;
width = height;
/* a square! */
Most important, we can perform the full calculation, which is where we started1:
area = width * height;
66
Chapter 1
1.2.5
The examples above used double variables, which ensure a precise result for our calculation. It would also be possible to dene all three variables above as integers instead, by using
the type keyword int:
int area;
int width;
int height;
However, we cannot store fractional (oating-point) numbers in such variables, only whole
numbers:
width = 20;
height = 7;
area = 21.5;
The particular quality of integer calculations emerges when we try to perform division:
area = 26;
height = 10;
width = area / height;
Here width receives the value 2, whereas the use of oating-point variables would result in a
more accurate fractional value of 2.6.
Historically, the integer is the oldest computer data type, and oating-point calculations
had to be performed by means of much arcane code and were therefore extremely slow.
Even today, many digital signal processors used for audio use integer calculations, which
can be performed very rapidly and which offer the highest precision for a given storage size.
1.2.6
One other important aspect of integers that the programmer has to consider is that there is a
limit to the size of the number that can be represented, depending on the number of bits
used. A 16-bit integer can store numbers from 0 to 65,535 (65,536 numbers in all). Therefore,
no negative numbers can be represented at all. However, a long-established system called
twos complement enables signed numbers to be stored by treating the most signicant
(leftmost) bit as a sign bit. In the case of a 16-bit integer, the signed range is from
32,768 to 32,767; this is how 16-bit audio samples are stored.
A 32-bit integer can similarly be signed or unsigned. The numbers are, of course,
much larger: the signed 32-bit range is from 2,147,483,648 to 2,147,483,647.
So important are integers that a C compiler supports several type (shown in table 1.2,
which assumes a typical 32-bit platform). Note that both signed and unsigned forms
67
Programming in C
Table 1.2
Integer types in C. *The int type is dependent on the CPU integer size.
Integer
Number
type
Size
of values
char
1 byte
28
Minimum
Signed
Unsigned
short
2 bytes
16
long
4 bytes
2 32
int
Maximum
128
127
256
32,768
32,767
Unsigned
65,536
Signed
Unsigned
2,147,483,648
0
2,147,483,647
4,294,967,265
Signed
Signed
Unsigned
*
0
are explicitly supported. Signed is the default for all numeric types, so it is only necessary
to use the unsigned qualier when such a form is required. For 16-bit DOS and Windows
(8086 CPU family), an int is 2 bytes (equivalent to short), whereas on 32-bit architecture
(MC68000, PPC, Pentium) an int is 4 bytes (equivalent to long). Thus, using the int type
is unsafe if a specic numeric range is required. In this case long or short should be used.
On 64-bit platforms, int will similarly be 64 bits, and the same caveat applies. Use of the
name long long is already widely established to indicate a 64-bit integer type, though not
yet standard. Strictly speaking, the C standard allows considerable latitude in dening the
sizes of numeric types (as they will have to reect the nature of the hardware). For example,
C only mandates that a short will not be larger than an int and that a long will not be
smaller than an int. Thus, it is theoretically possible (though very unlikely these days) for a
short, an int, and a long all to be the same size.
1.2.7
Sometimes it is important to be able to nd the size of a type, such as a numeric type, programmatically. This is especially important where code is intended to run on multiple platforms. C provides the sizeof() operator for this purpose. It has the appearance of a
function call (where the name of the type is the argument), but in fact it is a C keyword. It
returns the size of the type in bytes:
int intsize = sizeof(int);
/* may return 2 or 4, depending on the machine */
We will introduce an important use of the sizeof() operator in section 1.5, in connection
to user-dened data types.2
68
Chapter 1
1.2.8
A Musical Computation
The task here is fairly simple but useful, and just complicated enough to justify a computer
program: to convert a MIDI Note number to the equal-tempered frequency in hertz. We will
develop the task on paper rst, so to speak, and then see how it would appear as a C program.
First we need some background information. MIDI Note data values use an unsigned
seven-bit integer range, i.e. from 0 to 127 inclusive. These numbers have to embrace key
numbers, various controllers (including Pitchbend and Modulation), Key Velocity and Pressure, and so on. So converting MIDI values into other things is going to be a very common
operation.
Middle C on the piano keyboard is dened to be MIDI Note 60. Given the international
tuning standard, in which Concert A 440, and assuming equal temperament (E.T.), middle
C has a frequency of approximately 262.626 Hz. An octave contains 12 semitones, so that C
an octave below middle C would correspond to MIDI Note 48. The full theoretical MIDI
range 0127 (not known to be covered by any keyboard yet made) extends ve octaves
below middle C (to MIDI Note 0) and almost six octaves above it.
An octave jump corresponds to a frequency change by a factor (or ratio) of 2: doubled for
an octave rise, and halved for an octave fall. So an octave below Concert A would have the
frequency 220 Hz. A further octave fall halves the frequency again, to 110 Hz. A single semitone rise corresponds to the (approximate) value 1.0594631, which is (approximately) the
twelfth root of 2 (a semi-tone only in E.T. 12 tuning). That is to say, multiply 1.0594631
by itself twelve times and you should get the value 2.0. This operation, known as raising to a
power, is notated mathematically as 1.059463112 .
To ensure the best possibly accuracy, we can use the computer to calculate the semitone
ratio itself. Recall the rules of indices, according to which a power of 1/2 corresponds to the
square root and a power of 1/12 corresponds to the twelfth root. So we might notate this as
p
(1)
semitone_ratio 12 2.
The two numbers 2.0 and 1.0594631 (or better, its computed value semitone_ratio) are
all we really need to calculate any equal-tempered 12-tone interval, given a starting base
frequency3.
For example, calculate the frequency of the C sharp above Concert A in Equal Temperament. Concert A is 440, and by denition C sharp is four semitones above A (major third),
so the calculation is simply
Csharp 440 (1.05946314 ) Q 554.365.
The one other piece of numerical information we need is the frequency of the lowest note,
MIDI Note 0. This is ve octaves below middle Cwell below the audible range. An octave
drop is a factor of 0.5, so a ve-octave drop requires the value 0.5 to be multiplied by itself
ve times: 0.55 . This gives 0.03125. Multiply this by the frequency of middle C and we get
the frequency of the MIDI Note 0, or C0. As shown above, each rise of a semitone involves a
69
Programming in C
multiplication by 1.0594631. So to nd the interval between MIDI Note 0 and MIDI note 73
(which happens to be the Csharp calculated above), we rst multiply 1.0594631 by itself
73 times: 1.059463273 . We then have to multiply the base frequency C0 by the result of
this calculation to obtain the required result. It is now time to look at listing 1.2, where the
full computation is performed, and the result printed to the console. If you type this in and
compile it, you could call it midi2freq.c, and the program itself as midi2freq.
Listing 1.2
Calculate the frequency of a MIDI note:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
semitone_ratio;
c0; /* for frequency of MIDI Note 0 */
c5; /* for frequency of Middle C */
frequency; /* . . . which we want to find, */
midinote;
/* . . . given this note.
*/
You can test this program by changing the number assigned to midinote, re-compiling,
and running it. (Sections 1.3 and 1.4 explain how to avoid this tiresome process.)
As expected, there is more code here than in listing 1.1. Nevertheless, the layout of the
program follows the same structure. First, in line 3, a new header le, <math.h>, is included.
This is required to supply the denition of the C library function pow() that is used in the
70
Chapter 1
program. This is the function that raises a number to a power. While you study the details of
the code, notice the comments (inside the /* */ pairs), which describe both the purpose of
the program and what the various lines of code do.
Lines 711 contain declarations of variables, using names that help describe what the code
is doing. Two numeric types are used: the double and the int. These declarations must
appear before any other lines of C code appear.4 The compiler reads the code from the top
down, and needs to know about all these variables, and set aside storage for them, before it
can process code that uses them. You will nd that C++ is more exible, and that it allows
you to declare a variable anywhere, but in C they must all be declared rst, as here.5 Notice
that each declaration is terminated, as usual, with a semicolon.
Because the variables are declared inside the main() function, they are described as local
to that function, or simply as local variables. The alternative is to declare variables outside
main(), after the preprocessor #include statements. Such variables are then global
accessible anywhere in a program. This is generally regarded as a bad idea, as it can be very
difcult to control how they are modied. (For more on this, see section 1.3.)
Each variable is identied by a unique name. There is a lot of exibility allowed in creating
names. Essentially any printable character can be used, as long as it is not otherwise used by
C. This excludes characters such as *, /, -, %, &, and ? (in fact most of the pictorial characters), but it does include the underscore, which is very useful for creating multi-word names,
given that a name cannot incorporate any white space. Also, numeric characters are allowed,
but not as the rst character of a name. So the name 5c would be illegal. It is also illegal to
use a C keyword as a variable name:
int return; /* illegal name! */
You might experiment by deliberately writing illegal names and re-compiling to see what
error messages the compiler gives you. Sometimes error messages are a bit obscure, and it is
very useful to know what they most likely refer to.
Some programmers like to create extremely long names more akin to descriptive commentsfor example,
int an_extremely_long_variable_name_representing_a_MIDI_note;
This can get out of hand very quickly, however, and a good rule of thumb is to try to avoid
names longer than 32 characters or so. It should still be practicable to use a range of expressive names.
Lines 1519 perform the essential intermediate calculation steps described above. They
demonstrate three slightly different ways of using (calling) the pow() function, and in so
doing they reveal a lot about how expressions are handled in C. Inside the parentheses, this
function requires two oating-point numbers: the base number and the number to which
it is to be raised. In the header le <math.h> this function would be declared as follows:
double pow(double base, double power);
71
Programming in C
This shows that the function takes two arguments (dened inside the parentheses, and separated by commas) of type double, and returns a value also of type double. The names
shown above (base and power) are optional (and ignored by the compiler), but are
clearly useful for documenting what the function does. The minimum version of this declaration would therefore be
double pow(double, double);
You can add this line to the list of variable declarations (say into line 11), and then remove
line 3, where <math.h> is included, and the program will compile as before. It is worth looking at the use of this function in listing 1.2 in more detail. Line 15 calls the pow() function
in almost the simplest way possible:
semitone_ratio = pow(2, 1/12.0);
The result or output from the function is assigned to the variable semitone_ratio using
the assignment operator. As we know, everything on the right-hand side of the equals
sign must be an expression that evaluates to a value. That value must be in the form of one
of the legal C types (here, a double). It follows that the type on the left-hand side of the
equals sign must be the same, as it is here. Thus the statement
semitone_ratio = "twelve";
would be illegal, because it tries to make a double equal to a character string, which is obviously impossible. It would be agged as an error by the compiler, probably using the term
type mismatch.
Inside the function call, the rst argument is a simple number, 2. To C this is actually an
integer (int type), but as the required argument is a double, which is another number type,
C is happy to make a conversion automatically. (For more about converting numeric types,
see subsection 1.2.10.) This conversion does take a little time to do, and to eliminate that
you could add a decimal point and a zero to 2:
semitone_ratio = pow(2.0, 1.0/12.0);
C will recognize 2.0 as a oating-point number, and will automatically treat it as a double.
The second argument in this call to pow() is not a simple number but a simple arithmetic
expression, 1/12.0. Because no variables are used, only explicit numbers, the compiler will
usually make this calculation itself, rather than creating code for it. The use of at least one
decimal point ensures that the calculation is performed in oating-point fashion. Writing
1.0/12 would be just as good. However, if you had typed the line as
semitone_ratio = pow(2.0, 1/12);
the numbers 1 and 12 would be treated as integers, and the expression 1/12 would evaluate
to 0 (the result of integer division of 1 by 12), which is clearly not going to work as intended.
However, it is legal code, and the compiler probably will not warn you about it. All the
72
Chapter 1
compiler can assume is that that is what you meant in the rst place. You will not be aware
that something is wrong until you run the program.
In line 17 the rst argument is not an explicit number, but a variable to which we have
previously assigned a value:
c5 = 220.0 * pow(semitone_ratio, 3);
In this case, the call to pow() is itself part of a more lengthy expression, the value of which is
assigned to the variable c5. Line 19 shows pow() called with two numeric arguments (the
second integer again converted by C into a double), and line 23 shows it called with two
variables.
Line 25 is the same printf function introduced in listing 1.1, but here demonstrating one
of its primary uses: to write formatted numeric data to the console. Formatting means
many things; in this context it simply means embedding numbers of various types inside a
text string, the numbers being obtained from variables within the program.
In addition to the message string, two further arguments are given: midinote and frequency. Inside the message string, you will see two occurrences of %. This has the special
meaning for printf of signifying the start of a formatted conversion. For each argument
following the message string, we insert % into the message at the required position, followed
in each case by a letter indicating the type of the argument. A d or an i indicates an integer
(d stands for decimal or denary), and an f indicates a oating-point number. There are
many other possibilities. The rst % conversion uses the rst argument (following the initial
message string), the second conversion uses the second argument, and so on:
printf("%d o'clock %d o'clock %d o'clock rock,\n," 1, 2, 3);
1.2.9
In listing 1.2 each variable is declared on a separate line. For just four variables this does not
really matter, but a larger program may involve dozens or even hundreds of variables, making
the code very long. C allows variables of the same type to be listed together, separated by
commas:
double semitone_ratio,c0,c5,frequency;
You lose the scope for adding comments about each variable doing this; the use of expressive
names can compensate to some extent, but most programmers tend to mix multiple declaration lines like the above with individual lines where comments are needed.
It is also possible to give a variable an initial value when it is declared:
int midinote = 73;
Not surprisingly, this is called initialization. It can be applied to some or all variables in a
multiple declaration statement:
73
Programming in C
As a C expression can combine several calculation steps, C denes strict rules for deciding
in what order to carry them out. These rules are based on the principle of operator precedence. Operations with high precedence are performed before operations with lower precedence. Multiplication and division have higher precedence than addition and subtraction
and thus will be performed before addition and subtraction. The statement
float val = 15 + 440 * 2.5;
will evaluate to 1115.0, as if the expression on the right-hand side were written as
float val = 15 + (440 * 2.5);
Where two operations have equal precedence level, C has to have a rule to decide which to
perform rst. This rule is that operations are performed from left to right. In programming
terminology, the operation associates left to right. In the statement
frequency = 210.0 / 3 * 4.0;
the result will be 280, not 17.5, as the division is performed rst.
74
Chapter 1
In the case of addition, the order of evaluation will not matter: 200 + (10 + 20) is the
same as (200 + 10) + 20. But this is not the case for subtraction:
midinote = 200 - 10 - 20;
Depending on which subtraction is performed rst, midinote will equal 170 or 210.
Again, the operations are performed in order from left to right. The statement above will
accordingly evaluate to 170, as if written as
midinote = (200 - 10) - 20;
If in doubt, use parentheses to make it clear in what order you want the operations carried
out. Parentheses effectively have the highest precedence of all. Many expert programmers
routinely write complex expressions of this kind, without parentheses, as they know what
the evaluation order will be. On the one hand this is entirely reasonable, especially for simple expressions, but on the other hand adding parentheses even where technically they are
not necessary can help to document the code (e.g. to indicate that certain groups of numbers
or expressions relate to each other) and to prevent mistakes.
While all the arithmetic operators associate left to right, the equals (assignment) operator associates right to left. This makes it possible to write statements such as
int
a =
b =
c =
a =
a,b,c;
0;
1;
10;
b = c;
In listing 1.2 and in the description that followed it, we have seen two examples in which a
number of one type (e.g. an int) was automatically converted to another (e.g. to a double).
The most explicit example occurs in line 23:
frequency = c0 * pow(semitone_ratio, midinote);
How do we know a type conversion is happening here? First, the function pow() is dened
as having two arguments of type double. However, in the function call above, the second
parameter is in fact midinote, declared as an int. In this case the compiler is automatically
inserting a conversion process known as a cast. It is also possible to specify the cast explicitly
in the code:
75
Programming in C
Going the Other Way: Converting a Frequency to the Nearest MIDI Note
The code fragment below illustrates the use of the cast operator to obtain a required integer
result; it also introduces another important function from the standard C math library. The
fragment can directly replace lines 2125 of listing 1.2. You also need to add a new variable
fracmidi of type double to the variable list. If you do this, you may want to change to program name, for example to freq2midi. This example uses the C math library function
double log(double arg);
which nds the natural logarithm (to base e) of the argument:
76
Chapter 1
Exercises
Exercise 1.2.1
Using the sizeof() operator and printf statements, write a small program to report the
size in bytes of each of the following:
int
char
short
long
float
double
This will be of particular interest and importance if you happen to be using a 64-bit machine.
Exercise 1.2.2
The statement
samp = (short) fracsamp * 32767;
77
Programming in C
78
Chapter 1
What is involved in receiving input from the user? In the context of a command-line program, the user will be typing something at the keyboard, which the program reads and presumably processes in some way. The challenge here is a basic onewe dont know in
advance, and we have fairly limited means for controlling just what, or how much, the user
is going to type. The user may not even type anything at all. Therefore, some of the code we
write deals not with the core task, but with handling user inputmaking sure we read all of
it, and checking for the unexpected.
Thus an interactive program has to do two things: provide some space (memory), which
we hope is sufcient for anything the user will likely type, and test that data to make sure it
is what we need.
1.3.1
In the previous programs, we relied on the printf() function to print variables to the console. We now have the opposite taskthe user will (we trust) type numbers as a string of
numeric characters, and the program will have to convert that into a C variable. It is time
to understand a little more about exactly how a string is represented in the computer. Consider the string
"one two three four"
This string is stored in memory one character at a time in successive ascending memory
locations. It is the task of the compiler to decide exactly where it is stored. The above string
will occupy eighteen successive bytes of memory (remember, the space is also a character),
starting at some location N determined by the compiler.
However, an additional null byte (the character \0) is added at the end, so the string
occupies nineteen bytes. Thus, any function that is required to access the string need only
know where the rst character is, and it can completely trust that all following bytes also
belong to this string, as far as that nal null byte. The location in memory of this rst byte
is its address, and that address is accordingly the address of the whole string. The full technical term for this is a null-terminated string; this is the way all strings are implemented in C.6
Thus, in the statement
printf("one two three four");
the printf() function is given the address of the start of the string.
So the address is itself an object, a variable that can be passed around. As it is a variable, it
has to have a typein this case, it is a pointer type, because it points to a location in memory. As it points to a row of characters (which may of course be just one character long), its
full type denition is pointer to char. It declared as
char* message;
which could also incorporate an initialization to a string:
79
Programming in C
The Array
To receive text input from the user, we have to set aside some memory to hold the text. This
requires a character array. An array is simply a row of objects of a single type. This requires
a different notation, one that incorporates information on how much space we want:
char message[256];
The brackets (subscript notation) indicate the denition of an array, and the number
inside the brackets indicates how many items of the stated type to provide space for. The
result of the statement above is that a xed block of 256 bytes of memory is set aside (since
a char is one byte in size), and the address of the start of it is stored in the variable message.
The forms
char message[]; /* unspecified size */
and
char message[0];
are illegal. Either of them will trigger an error message from the compiler. Needless to say,
negative sizes will also be rejected. Subscript notation is also used to read and write individual elements of an array:
char Beatle[5] = "John";
char letter;
letter = Beatle[0]; /*
Beatle[2] = 'a';
letter = 'J' */
/* Beatle now = "Joan" */
80
Chapter 1
Here the number in brackets is termed the array index or subscript and, as can be seen,
counts from 0, not (as one might intuitively expect) from 1. In binary arithmetic, a single
bit holds two values, 0 and 1, and so can be used to count two items. 0 therefore has to signify the rst item, and 1 the second.
The power of the subscript notation becomes more apparent when we realize that an integer variable (of any integral type) can be used for the index:
int position;
position = 3;
Beatle[position] = 'l';
position = 0;
Beatle[position] = 'P';
position = 2;
Beatle[position] = 'u';
position = 1;
Beatle[position] = 'a';
/* Beatle now contains "Paul" */
As well as enabling programmatic changing of the index value, this also supports selfdocumenting code, as the index name can become descriptive.
Arrays can be formed using any type. Of course, the string initialization system can be applied only to arrays of char. In declaring the size of an array to contain a string, it is essential
to count the terminating null byte as part of the string. C provides a generic notation using
braces (note the nal semicolon here), which enables arrays of any type to be initialized in a
single statement:
int FullChord[4] = {60,64,67,72};
/* MIDI notes for C Major */
int root = 0, third = 1, rootnote;
FullChord[third] = 63;
/* change chord to C minor */
rootnote = FullChord[root];
/* Middle C */
Figure 1.1 shows the construction of a string array element by element using this notation,
including the all-important terminating null character.
Figure 1.1
A string as a null-terminated array of characters.
81
Programming in C
If there are too many elements in the initialization block, the compiler will ag an error.
However, if there are too few, the remaining elements in the array will be initialized to 0:
int LargeChord[32] = {60}; /* first element = 60, all others set to
zero */
In section 1.5 we will learn how C enables the programmer to step through arrays programmatically, so that possibly very large arrays or other data structures can be initialized
and modied using a small amount of code.
1.3.3
NULL
In addition to the initializations described above, it is possible, and often a very good idea, to
initialize a pointer (of any type) to the special value NULL (dened in stdlib.h). It is always
written in upper case. Exactly how this is dened is discussed in a later section; sufce it to
say here that it is a form of zero for pointers. Many functions that return pointers will use
NULL to indicate errors.
1.3.4
It is probable that more bugs in C programs are caused by misuse of pointers and arrays than
by any other single programming error. There are two bad things one can do with an array
index. One is the following:
double buffer[32];
buffer[-2] = 1.0594631; /* ERROR! Index below start of array */
buffer[33] = 2.0; /* ERROR! Index beyond end of array */
The problem here is that the compiler will very likely not warn you about these errors, and if
the index is in fact a variable, it will in any case have no way of knowing. C does very little
hand-holding of programmers, and if you over-run the bounds of an array it will do nothing to stop you. Sometimes the program will seem to run correctly, but it may suddenly
crash on some machines and not on others, and the point of the crash may be in some seemingly unrelated part of the program.
Indexing errors of the kind discussed above are especially damaging when you are modifying data in memory, because in most compilers the memory spaces below and above an
array may belong to the system, holding information on other data used elsewhere. Maybe
you will be affecting other variables in your program, so it will appear that those variables are
causing the crash.
There are nevertheless good reasons why even an explicit negative index is not automatically treated as an error by the compiler. We will see in later sections that a pointer can
be assigned the address of any element, e.g. one in the middle of an array. In this case, a
82
Chapter 1
negative index relative to that new pointer is perfectly valid. The danger arises only if the
bounds of the overall array itself are exceeded.
A subtle problem arises using indexing on strings used to initialize pointers:
char* name = "John";
name[2] = 'a';
/* DANGER: may crash! */
This will compile, but the program may crash with an access violation, because an attempt
is being made to modify a string that is constant and which the compiler has stored in a
protected area of memory. This may be to stop programmers trying to rename Windows
Windoze, which would never do. However, if you declare name as an array, all will be
well:
char name[] = "John";
name[2] = 'a';
/* NB: sizeof(name) = 5 */
/* safe: name now = "Joan" */
This demonstrates probably the easiest way to initialize a char arrayit is not necessary in
such cases to supply a size for the array inside the brackets as that is now available from the
initialization string. Usefully, with this form of initialization, the sizeof operator can also
be used to nd the size of the string programmatically.
As you work through the chapters of this book, you will discover a range of techniques
that programmers use to keep such coding errors to a minimum. Together these form a repertoire of techniques for defensive programming. We have already met some of these techniques: the use of informative variable names, explanatory comments, and the robust testing
of user input. As our programs grow in complexity, we will need to pay ever greater attention
to defensive programming. We hope we will get it right rst time, but the more our programs depend on input from outside, and the larger our programs become, the more we
must be on the lookout for code with unintended side effects.
1.3.5
We have used the printf() function to write a numeric variable to the console in text
form. In order to get a number interactively from the user, we need to be able to do the
reverse. The standard C library offers three conversion functions: atof(), atoi(), and
atol(), which convert into double, int, and long, respectively. They are declared in the
header le stdlib.h, which therefore must be #included in any source le that uses them.
They all have the same format, differing only by return type:
double atof(const char*);
int atoi(const char*);
long atol(const char*);
Their use is straightforward:
83
Programming in C
double dval;
int ival;
long lval;
dval = atof("1.0594631"); /* dval = 1.0594631 */
ival = atoi("440"); /* ival = 440 */
lval = atol("65536"); /* lval = 65536 */
The const keyword is often used in such declarations. It indicates that the supplied argument is treated as constant or read only; it will not be modied. That is to say, the
pointer is a pointer to a constant char. When a plain (non-pointer) type is used as a function argument, it is copied into the functions private memory space, so that the variable is
not modied. When the argument is a pointer, the pointer is copied, but the memory it
points to could be modied. Some functions are expressly designed to do this, and would
be declared without the const keyword. An important example is the function gets(),
which obtains text typed at the console:
char* gets( char *buffer );
Here the buffer argument receives the string from the user, and must therefore be a pointer
to a char array of adequate size, while the return value will duplicate the buffer or will be
NULL to indicate an error.
Conversely, the presence of const in a function declaration is a guarantee that the function will not modify your data. It therefore becomes another element in our repertoire of
defensive programming techniques. It is also a signal to the compiler that some sorts of optimizations can be performed.
The use of these functions is easy:
double frequency;
char* message = "1.0594631";
frequency = atof(message);
/* frequency now has the value 1.0594631 */
The only issue to remember is what happens if the message string does not in fact contain
the expected numeric charactersin this case, these functions return 0, in the appropriate
type:
message = "one thousand";
frequency = atof(message);
/* frequency now has the value 0.0 */
However, this will produce a plausible result:
message = "1000pointfive";
frequency = atof(message);
/* frequency = 1000.0 */
84
Chapter 1
Thus you cannot know with certainty whether the user has actually typed a 0 or whether
it has typed some other incorrect text. Of course, this is an issue only if 0 is a reasonable
number for the user to give. The standard C library offers more elaborate functions, such as
strtod(), that both trap incorrect text, and identify it for you by returning any offending
text via a pointer to a char array, that you provide.
The const keyword can be used to trap the problem mentioned at the end of the previous
section. By declaring the pointer as a pointer to const char:
const char *name = "John";
name[2] = 'a';
The second line will now trigger a warning from the compiler that an attempt is being made
to modify const or read-only memory.
1.3.6
Listing 1.2 does very little that cannot be handled by a pocket calculator. In order to write a
reasonably practical interactive program, we need to be able to check user input for correctness, and be able to quit the program if input is incorrect. The ability to make simple
yes-no or true-false decisions (very quickly) lies at the heart of computing. Such a decision is, of course, binarythere are only two possible answers. At the level of binary logic,
yes corresponds to true and is associated with the value 1, while no equates to false
and has the value 0. We will see later that literally every C expression can be evaluated in
terms of true or false. At this stage, all we need is the ability to make a comparison between two expressions (e.g. two numbers) and to take alternative actions based on the result.
A frequent programming requirement is to nd out if two variables are equal. This is one
of many comparison tests for which C provides special operators. These are listed in table
1.3. As they are binary operators (like the arithmetic operators), they are placed between the
two values being compared. For example, the equality operator is used to nd if two variables are equal. The expression
Table 1.3
The C comparison operators.
Operator
Example
Description
a is equal to b
Equal to
= =
a = = b
Not equal to
!=
a ! = b
a is not equal to b
Bigger than
>
a > b
a is bigger than b
Smaller than
<
a < b
a is smaller than b
>=
a >= b
a <= c
85
Programming in C
(width == height)
(an example of a C conditional expression or conditional test) will evaluate to 1 if the
values are identical, or to 0 otherwise.
The C language includes the keyword if, which is placed in front of the expression to be
tested:
if(width == height)
As it stands, this is incompleteit is not yet either an expression or a statement. To form a
complete if statement, it must be immediately followed by a C statement, to be executed if
the test is successful (i.e. if the expression evaluates to true):
if(width == height)
printf("it's a square!");
By convention, the body of the statement is indented if it appears in the next line, showing
that it depends on the outcome of the preceding if expression. If you need to write more
than one statement depending on the same if expression, the statements must be enclosed
by braces, which serve to indicate a single code block:
if(width==height)
{
found_square = 1;
printf("it's a square!");
}
One of the traps for the unwary with if statements is adding a nal semicolon. This can
easily occur by accident in the process of editing code, and it is often difcult to spot:
if(width == height);
This is in fact legal C, because the semicolon by itself is a legal statement that does nothing
(a null statement). The true meaning is clearer when we use appropriate layout:
if(width == height)
;
/* do nothing! */
C is fairly exible with respect to comparisons between different numeric types. However,
comparisons between plain variables and pointers will be agged as errors by the compiler,
as will be comparisons between pointers to different types (e.g. a float pointer with an int
pointer). The one important exception is NULL, which can be compared with any pointer
type. One of the more idiosyncratic styles I have come across is
if(a == b)
{
do_something();
}
86
Chapter 1
This works perfectly well so long as braces are always used (as is often recommended). However, in
if(a == b)
do_something();
if(c == d)
{
do_something_else();
}
the dependent statements have different degrees of indentation but are in fact at the same
level.
The amount of indentation also attracts different opinions. Indents of two, four, and eight
spaces are common. A subtler issue is whether to use hard spaces or a tab. (Most text editors
can be set to use a requested tab size.) So long as one approach or the other is used consistently throughout, there should be few problems with layout if the text is transferred between
platforms. If in doubt, remember that the use of hard spaces will always be portable.8
1.3.7
Each time the program is run, it asks the user to type in a MIDI Note number. If it is recognized as a reasonable number, the corresponding frequency is displayed. Otherwise, an error
message is displayed and the program nishes. You can either create a new le (or project)
for this or just edit listing 1.2, keeping the same program name. Apart from the fact that
this version is a little more useable, the latter approach reects more closely how programs
are usually developedincrementally, adding features step by step. The program uses the
function gets(), described above, to read a line of text typed by the user in response to a
prompt.
When gets() is called, it waits for user input from the console (which must include a
nal return keypress), then places whatever was typed in the char array supplied as the
function argument. That array must be large enough to accommodate whatever we suspect
the user might try to type. The return value, also a char pointer, is simply a copy of the
input argument. The function can also signal an error in special circumstances, by returning
NULL. While those circumstances are not likely to apply here, this possibility is nevertheless
tested for in the program. Comparing pointers with NULL is a very common programming
procedure, and is very important, as trying to read from or write to memory pointed to by
NULL can be guaranteed to crash the program. To detect errors, listing 1.3 uses four if statements with three different comparison operators. In each case, the return keyword is used
to exit the program if an error is detected, with 1 as the error value.
87
Programming in C
Listing 1.3
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main()
{
double c5,c0,semitone_ratio,frequency;
int midinote;
char message[256];
char* result;
semitone_ratio = pow(2, 1.0/12);
c5 = 220.0 * pow(semitone_ratio, 3);
c0 = c5 * pow(0.5, 5);
printf("Enter MIDI note (0 - 127): ");
result = gets(message);
if(result == NULL){
printf("There was an error reading the input.\n");
return 1;
}
if(message[0] == '\0') {
printf("Have a nice day!\n");
return 1;
}
midinote = atoi(message);
if(midinote < 0){
printf("Sorry - %s is a bad MIDI note number\n",message);
return 1;
}
if(midinote > 127){
printf("Sorry - %s is beyond the MIDI range!\n",message);
return 1;
}
frequency = c0 * pow(semitone_ratio, midinote);
printf("frequency of MIDI note %d = %f\n", midinote, frequency);
return 0;
}
The use of compound expressions was demonstrated in subsection 1.2.8. As any C expression can be evaluated to a value, it follows that one or both sides of a comparison operator
could also be a compound expression; for example, incorporating a function returning a
88
Chapter 1
value. In listing 1.3, the return value from gets() is tested only once, so the code could reasonably be compacted as follows, eliminating a variable:
printf("Enter MIDI note (0 - 127): ");
if(gets(message) == NULL){
printf("There was an error reading the input.\n");
return 1;
}
The compiler will make sure that the function call is executed rst (it has the highest precedence), and the result then passed to the conditional test.
1.3.8
Exercises
Exercise 1.3.1
Listing 1.3 has no comments. How self-documenting is the code? Add some comments
wherever you think they would be useful.
Exercise 1.3.2
(a) Make a new interactive version of freq2midi from the previous section.
(b) Implement rounding of the printed pitch deviation percentage value, so that positive
and negative values are symmetrically rounded. For example, 38.7 should be rounded to 38,
and 38.7 should be rounded to 39.
Exercise 1.3.3
(a) Add a further test on the number supplied from the user in listing 1.3, to detect and
reject oating-point values (since MIDI notes are integers).
(b) Try to detect whatever erroneous input the user might make, and respond with an appropriate error message.
Exercise 1.3.4
What messages does the compiler write if
(a) you omit the nal double quotation marks in a printf statement?
(b) you omit the variable name associated with a format specier in printf?
1.4
As its name suggests, the command line is a means of communicating with a computer using
text commands typed from the keyboard, and using the medium of a command shell or
89
Programming in C
a console programfor example, the Bash shell in UNIX, the DOS console in Windows, or
the Terminal in Macintosh OS X. While it is not the oldest way of sending commands to a
computer, it is by far the longest established, and despite the inexorable progress of WIMPbased systems (Window Icon Menu Pointer), usually referred to simply as a GUI (Graphic
User Interface), it refuses to go away. Signicantly, the Apple Macintosh, having eliminated
the command-line interface as both unnecessary and user-unfriendly, has recently reacquired it with the introduction of OS X, which is based on the UNIX-like BSD kernel.
Thus the three most widely used operating systems all now support a console-based command-line interface, which is ideal for developing and running simple programs such as
those presented in this section. Indeed, for many advanced users (and especially on Linux
and other UNIX-like platforms) it remains the ideal environment in which to work, even
on complex projects.
A typical text command consists of a program name (e.g. copy), followed by one or more
argumentswords separated by spaces. Let us suppose there is a program sfmix that takes
two or more soundles and adds them together (i.e. mixes them) to create a new soundle.
The documentation for the program tells us that the rst argument should be the name of
the new output le, and all following arguments should be the names of input soundles to
add together. The command line might look like this:
sfmix backing.wav drums.wav guitars.wav
This runs the program sfmix, creating the output le backing.wav from the input les
drums.wav and guitars.wav. This works because the operating system supports a standard
method for both running a command-line program and giving it the arguments from the
user. The operating system has no idea what these arguments meanthey are nothing
more than text strings separated by spaces. It does one very useful thing, however: it separates each string into a list, and gives that list to the program to use as it wishes. Thus, rather
than having to ask the user interactively for each lename, the program is given all the
names automatically from the command line.
The command-line environment is most strongly associated with the UNIX operating
system and its variants, including Linux and OS X. However, the principles are much the
same on Windows using the DOS console. Programs can be run just as illustrated above.
The only immediately relevant difference from a classic UNIX-style environment is that the
command to list the contents of a directory is ls on UNIX-style systems, whereas it is dir in
DOS. By using command-line arguments, a program can be designed to handle a range of
inputs from the user. The usual environment for command lines is a shell application or
terminal window such as the Windows DOS Console, Terminal in OS X, or an Xterm in
Linux. Table 1.6 shows some typical shell commands. (N.B: To run a program in the current
directorye.g. in which the program has been builtin a UNIX-like environment (Linux,
OS X Terminal), the program name must be prexed by the current directory shorthand
./ as follows:
90
Chapter 1
> ./cpsmidi 72
Other commands, such as cd to change directory, are the same.
If you have not already done so, you may want to try out some of the pre-compiled command-line programs supplied on the DVD as a way to develop uency in using such tools.
Most of them are projects from later chapters in this book. Although they involve much
more elaborate C programming than we can do at the moment, there is no reason not to
use them as prefabricated tools with which useful creative work can be done. The chances
are high that you will very soon think of some new facility that is not provided by a given
program. There can be no better incentive to develop your programming skills than the prospect of crafting exactly the tools you want for the work you want to do.
1.4.2
In the programs demonstrated so far, we have used the main() function with no arguments.
There is an alternative extended form, which is required whenever we want information
from the user at the beginning of the program:
int main(int argc, char* argv[]);
The rst argument, argc, is simple enough. It is an int, and the name means argument
count, the number of space-separated strings supplied in the command line. This number
will always be at least 1, as the rst argument (counting from 0, as is always the case in C) is
the name of the program itself. The second argument is a little more complicated and needs
some detailed explanation. You have already seen declarations of pointers (such as char*
name;) and of arrays (such as char name[32];), but argv combines both the pointer and
array notations. The name argv is shorthand for argument vector, and vector here is a
synonym for array. So we have an array argv[]of pointer to char: char*.
The key aspect to remember here is that a pointer to char can itself signify an array, in the
sense of a character string (i.e. null-terminated), which indeed is the case here. Thus, taking
the example above as our example command line, this array of strings would be laid out as
follows:
argv[0]points
argv[1]points
argv[2]points
argv[3]points
to
to
to
to
"sfmix"
"backing.wav"
"drums.wav"
"guitars.wav"
91
Programming in C
Figure 1.2
Command-line arguments as pointers to strings.
found. However, as argc is always available (and a more convenient basis for a loop), such
usage is rare. Note that the rst argument (argv[0]) contains the name of the program. This
may seem superuous (after all, we have written the code, so we know what we have called
the program), but it can be surprisingly useful. If nothing else, we will not be caught out if
the user decides to rename the program.
In a program, you might use argv to print a message to the users, perhaps about some
error:
printf("Cannot find the file %s\n," argv[2]);
Since the expression argv[2] evaluates to a char*, it can be used as the associated variable
for the string format specier %s. Similarly, it can be passed as the parameter to any function
expecting a char* :
/* read number of oscillators requested */
int num_oscs = atoi(argv[3]);
if(num_oscs > 100)
printf("too many oscillators!\n");
Just as in the previous program examples, we will have to check each argument for reasonableness. The simplest and most important test, however, is the one applied to argc. We
92
Chapter 1
know how many arguments we need, and we must check for this before trying to read any of
them:
if(argc < 4){
printf("insufficient arguments.\n") ;
printf("usage: sfmix outfile infile1 \
infile2 [infile3 . . ..]\n");
return 1;
/* exit the program */
}
For command-line programs, this usage message is a very common procedure, which users
come to expect and rely on. If they type just the name of the program, they should be given
a full usage message listing the purpose of the program and the name and purpose of each
argument. You will see this incorporated in all the programs that follow.10
1.4.3
93
Programming in C
/* could be a char */
semitoneratio = pow(2,1.0/12);
c5 = 220.0 * pow(semitoneratio,3);
c0 = c5 * pow(0.5,5);
/* if the program is not called cpsmidi,
either change the lines below,
or use the argv[0] system, shown commented out below */
if(argc != 2){
printf("cpsmidi : converts MIDI note to frequency.\n"
/*,argv[0]*/);
printf("usage: cpsmidi MIDInote\n" /* ,argv[0]*/);
printf(" range: 0 <= MIDInote <= 127 \n");
return 1;
}
midinote = atoi(argv[1]);
/* use argv[1] to echo a bad argument string to the user */
if(midinote < 0){
printf("Bad MIDI note value: %s\n",argv[1]);
return 1;
}
if(midinote > 127){
printf("%s is beyond the MIDI range!\n",argv[1]);
return 1;
}
frequency = c0 * pow(semitoneratio,midinote);
printf("frequency of MIDI note %d = %f\n",midinote,frequency);
return 0;
}
Finally, if you think that command-line arguments exist only for command-line programs,
think again: a Windows GUI program also reads command-line arguments, as will any UNIX
(or Linux or Apple OS X) program. In Windows, for example, it is possible to invoke the
Notepad application from the DOS console, giving it the name of a le to edit:
notepad freq2midi.c
The procedure on Apple OS X is only slightly different. To run the soundle editor Audacity
from the Terminal, you can type
open -a Audacity.app myfile.aiff
94
Chapter 1
The command-line handling skills you develop here will stand you in good stead when
developing more advanced GUI applications.
1.4.4
Exercises
Exercise 1.4.1
Convert all the programs developed so far to use argc and argv. Dont forget to incorporate
the extensions suggested in previous exercises.
Exercise 1.4.2
Add a usage message to each program.
Exercise 1.4.3 (experimental)
(a) Extend the program with a further command-line argument that denes the root A
tuning. For example, Baroque pitch denes A 415 Hz. In fact, A 440 Hz is itself a relatively recent standardespecially in the United States, where the pitch of A 435 Hz was
recently in widespread use.
(b) (experimental) Add a further argument that denes the division of the octave. For example, a popular alternative temperament uses a 19-note equal-temperament division. A value
of 24 would dene a quarter-tone ET scale. In each case, trap inappropriate user input with
suitable error messages.
1.5
95
Programming in C
apply the same process to each element just as described abovefor example, to change the
amplitude of an array of audio samples. We can use the power of the C language to make
such tasks easy. With a few lines of code, we will be able to read and process every element
of an array of any size. The tools for this are two cornerstones of all programming: iteration
(counting) and looping (repeating instructions). These are especially important tasks in audio
programming, as we will be working all the time with blocks of samples, such as tables containing waveforms, memory buffers containing delay lines or audio streamed from a disk or
from a soundcard, or tables of frequencies and amplitudes for oscillators. In a typical application we have to step through the block, making a change to each sample as we go. On a
larger scale, when we need to read a soundle, we usually need to read it in blocks (the whole
le may be far too large to t in memory all at once), so we have to use iteration and looping
to read each block and process it. This is, then, a process of mass production. We can use
the power of the language to repeat steps automatically, at very high speed, on blocks of data
of any size. In this section, we move from writing calculations, to writing algorithmsmethods for performing a concretely dened task, usually involving many repeated steps. In a
sense, this is the rst section in which we embark on true computer programming. We will
learn about how to create loops, and also more about the use of Cs auto-increment and autodecrement operators, which are designed to support the highly efcient mass production we
need. Finally, we will apply all this new knowledge to build on the programs we have already
built, and to produce perhaps the rst truly useful program so far.
1.5.1
As its name suggests, a loop is a block of code (it need not be a single instruction) that
repeats over and over. However, a code block that simply repeated foreveran endless
loopwould not be very useful. If you have used a sampler, you will be familiar with the
way a sound is looped (to maintain a sustaining sound) while a key is held down. This is a
form of endless loopit is broken out of only when you release they key. Should it fail to do
that, you have a real problem.
So, the most important aspect of using loops is controlmaking sure that they stop when
we want them to. The simplest way to do this is to count the number of times the loop
repeats, and stop when a limit is reached.
The simplest loop construct in C is the while loop. Listing 1.5.1 shows an example of its
use.
Listing 1.5.1
1
2
3
4
#include <stdio.h>
int main(){
int i;
96
Chapter 1
5
6
7
8
9
10
11
i = 0;
while(i < 10) {
printf("%d ,"i);
i = i + 1;
}
return 0;
}
Figure 1.3
Comparing if . . . while and do . . . while.
97
Programming in C
There are three essential elements of loop control illustrated in this example:
1. Line 6 is the conditional test, associated with the while keyword itself,: (i < 10). At the
end of each iteration, program control will automatically return to the conditional test, and
evaluate it again. So long as the expression evaluates to true, the code inside the dependent
block { }will execute repeatedly.
2. Line 8 is the update of the control expression, i = i + 1; In the control expression (i < 10),
i is a variable which is declared outside the block, and modied at each repeat (iteration).
The effect is simply that at each repeat i is increased by 1, as shown by the output of
printf().
3. Line 5 is the initializer statement, i = 0 ;This sets the starting value of the controlling variable. This is placed before, and outside, the while statement. In this case it is set to 0, but it
could be anything. For example, in this variation the numbers count downward from 9
to 0 11:
i = 9;
while(i >= 0){
printf("%d," i);
i = i - 1;
}
1.5.2
/* equivalent to i = i +1; */
/* equivalent to i = i - 1; */
Since this form can be used as part of a more complex expression, it enables a loop to be
expressed in a very concise form. The example below is exactly equivalent to the code in listing 1.5.1 (note that we now can omit the braces):
i = 0; /* initializer */
while(i < 10) /* conditional expression */
98
Chapter 1
While the increment and decrement operators are intended for use with integer variables
(and also with pointers, as well see later), they will also work with oating-point variables:
double var = 1.5;
var++; /* var now = 2.5 */
This is not idiomatic C programming, however, and it is not recommended12 any C programmer reading the expression var++ will assume that var is an integer variable. A more
general update expression that can be used with oating-point types uses one of the arithmetic operators (+, -, *, /) together with the = sign:
float a =
a += 2.5;
a -= 0.5;
a /= 2.0;
a *= 3.0;
1.0;
/* equivalent to a = a + 2.5; a has the value 3.5 */
/* a now = 3.0 */
/* a now = 1.5 */
/* a now = 4.5 */
The % (modulus) operator can also be used in this way, but only for integral types:
int a = 10;
a %= 3;
/* a now = 1 */
99
Programming in C
Note that it is illegal to have a space between the arithmetic operator and the = sign:
a + = 5;
/* syntax error */
In the example below, update notation is used inside a loop to calculate the intervals of
the equal-tempered scale, and write them to an array. Post-increment is also used, applied
to the counter i in the array index expression:
double ratio = 1.0, semitone_ratio = pow(2,1/12.0);
double intervals[12];
int i;
i = 0;
while(i < 12){
intervals[i++] = ratio; /* uses auto-increment*/
ratio *= semitone_ratio; /* uses update operator */
}
The examples in this subsection demonstrate the use of the primary arithmetic conditional testsfor equality (=), less than (<), and greater than (>), together with their
inverses, not equal to (!=), greater or equal (>=), and less or equal (<=). As we shall
see, these already offer the programmer considerable exibility in designing conditional tests
associated with loops. C also supports a group of logical operators that make it practicable
to test multiple conditions together.
1.5.4
As was shown above, a complete looping construct involves three control elements: an initializer statement, a conditional expression, and an update statement (e.g. a counter) that
usually updates a parameter in that expression. C offers a compact looping construct based
on the keyword for that places all three elements together; it is by far the most popular
and widely used of all the looping constructs in C. It is best demonstrated by example:
int i;
for(i = 0; i < 12; i++){
intervals[i] = ratio;
ratio *= semitoneratio;
}
This functions identically to the while example in the previous section. The for statement
has the structure
for(initializer_statement; conditional_expression; update_expression)
This must be followed, as in the case of the while construct, by either a single statement or a
block of statements enclosed by braces, as shown above. The three elements are demarcated
100
Chapter 1
Figure 1.4
The for loop.
by the two required semicolonsa semicolon should not be used to terminate the update
expression.
The for loop runs as follows (see gure 1.4), assuming that the conditional test evaluates
to true:
(1) The initializer statement is executed: i = 0;
(2) The conditional expression is evaluated: i < 12;
(3) The result is true, so . . .
(4) The dependent code inside the braces is executed.
(5) Control returns to the top, and the update expression is executed: i++
(6) The loop continues from step (2) until the condition evaluates to false.
(7) When the condition evalua