C++
About C++
C++ (pronounced cee plus plus) is a general purpose programming language developed by Bjarne Stroustrup starting in 1979 at Bell Labs. It is immensely popular, particularly for applications that require speed and/or access to some low-level features. It is considered to be an intermediate level language, as it encapsulates both high and low level language features. Uses include Vst and VCVRack plugin development.
C is what we call a compiled language. This is because programs, written with C code in a text file, are generated by a package of programs called the compiler. These programs transform 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 inefficient for intensive tasks (such as calculating the output of a synthesis routine, in the case of a music system).
Overview[edit]
Compiling[edit]
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 file. (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 file called a.out, which you can rename (using mv) if you want. This file will sit in the directory you are working on.
For more practical uses, you can output the binary executable to a different file, using
‘-o . . . ’ (just as in Csound ‘-o’ means ‘‘output to’’): cc -o myprogr mysource.c
On Windows machines your program filename will have the extension .exe. On other systems no extensions are used.
Source Files[edit]
Source files are text (ASCII) files 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.
Header Files[edit]
Header files (usually with extension .h) are files with definitions and parts of code which are included in (i.e. they will be copied to) the source files. The compiling process will use the code in these header files in the compilation process.
Libraries[edit]
C is a very lean language. It has a number of built-in definitions, rules, etc.; the rest is done by elements (functions, data definitions, 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.)
The Build Process[edit]
The ‘‘program build’’ has three main phases:
1. Pre-processing. This expands all include header files (by copying them into the source code) and other preprocessor statements (which usually start with the character #) found in the source file(s).
2. Compiling. This reads the source code and transforms it into binary code for the machine you are targeting (i.e. the machine for which you’re building the program).
3. Linking. This links all references to binary code that is not in the source code (i.e. in libra- ries), as well as putting together all source binaries (if there are multiple source files). These three stages can be done in one go, or if you like, separately. Usually, when building large pieces of software it is common to do steps one and two at one time and then leave the linkage process for later. The source files are compiled into binary files called object files (extension .o), which can later be combined (linked) to make the program. This is usually the case when there are many source files in the project. This procedure enables the programmer to compile the different source files only when they have been modified, saving time and resources. Utilities such as make or scons can be used to maintain a build project and will automatically call the compiler and the linker to build from sources that have been updated.
Program Format and Layout[edit]
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 finishes. By default this function is called main(), but this can be changed by a compiler flag (although we will not normally do it). This function can take two forms:
int main( );
and
int main(int argc, char* argv);
The first form does not take any arguments and, being the simplest, is the one we will use first. Both forms are supposed to return (i.e. ‘‘answer with’’) an integer number, which is passed as a code to the operating system (generally 0 means OK). The second form will be discussed later in section 0.15. In addition to main(), the program can also have other func- tions, although they are not absolutely required.
Functions[edit]
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 defined, you can use it:
int main() { message(); return 0; }
As the function does not return anything, it is defined 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.
Arguments and Return Types[edit]
Passing values to a function is done via its arguments. These are defined within the paren- theses, 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 func- tion provides, we use the keyword return:
int sum(int a, int b) { int result; result=a + b; return result; }
This defines 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 }
Notic e 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.
Prototypes[edit]
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 first have to declare it. We need not completely define (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 defined above. The function definition can then go elsewhere in the source code—after main(), or even in a different source file. By default, if a function is not declared before use, it is assumed to be an int function—which is not always correct.
Pointers[edit]
As we mentioned earlier, a variable is a memory slot that has been given a name. For exam- ple, 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 defined 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.
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 */</nowiki>
In summary:
- A pointer is declared by a ‘*’ in front of its name.
- The address of a variable is given by a ‘&’ in front of its name.
- To obtain or store the value of a variable (pointed at), use ‘*’ in front of a pointer.
Pointers and Arrays[edit]
Pointers and arrays are closely connected. If you declare an array as int a[10]; you are also declaring a pointer a to the first element in the array. Here, a is equivalent to &a[0]. The only difference between a and a pointer variable is that the array name is a constant pointer and cannot be used as a variable. In this sense, a[i] and *(a+i) are equivalent, which makes possible what is called pointer arithmetic, adding integers to pointers to step through a memory block. The compiler will know how many bytes to move forward when you add a certain number of memory slots to it. If it is an int, it will jump four bytes (system-dependent of course) each step, if it is a double, then it will jump eight bytes. This is one of the reasons why the compiler needs to know the type of a variable when you declare it.
Finally, when you pass an entire array to a function then by default a pointer is passed. This allows you to write functions that process entire arrays without having to pass every single value stored in the array, just a pointer to the first element: int randarray(int *pa, int n)
{ int i; for (i=0; i < n; i++) { *pa = rand()%n + 1; pa++; } }
The loop can in fact be reduced to
for(i=0; i<n; i++) *(pa+i)=rand()%n+1;
31 An Overview of the C Language or
for(i=0; i<n; i++) pa[i]=rand()%n+1;
If you define pa as a pointer, you can use array indexing notation with it as well as pointer arithmetic.
Strings, Arrays, and Pointers[edit]
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 first 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 com- pile). What we need is strcopy(a,b), which copies every character in a into the array b up to the first 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";
As with all C parameters, structures are passed by value; thus, if you want to allow a function to alter a parameter you have to remember to pass a pointer to a struct.
Command-Line Arguments[edit]
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 first 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
Moving to C++[edit]
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 file 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 ++).
Variable Declaration Anywhere in the Code[edit]
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.
Default Value for Arguments in Functions[edit]
In Cþþit is possible to declare functions with default arguments:
float func(int a=0, int b=1, float f=0.5f); float func(int a, int b, float f){ return a+b*f;
} When called as in
a = func();
this function will return 0.5.
In order to use this facility, in the declaration, we define which arguments are to have default values by initializing them. In the function definition, we just implement the code as before.
Memory Management: new and delete[edit]
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-defined variable type, as in
// memory allocation for float pointer variable a float *a = new float; // ditto for int pointer variable b int *b = new int; // ditto for struct Mystruct pointer variable s MyStruct *s = new MyStruct;
Memory can be allocated for arrays, as in
int size = 512; float *array = new float[size]; // 512 floats.
As this is a dynamic memory allocation, a variable can be used to determine how much memory will be allocated. Only one dimension of a multi-dimensional array can be allocated at a time. To free the memory, we use delete:
delete a;
For arrays, we use a slightly different form:
delete[] array;
Structures and Data Types[edit]
In C++, when defining a structure, we are also creating a new data type, so we do not need the typedef keyword to define 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 sufficient to know that we can write struct MyStruct { int a, b; float f; }; // no need for typedef... MyStruct obj; obj.a = 1; // etc...
Line Comments[edit]
C++ also supports a single-line comment, using ‘//’ as the first 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).
Data Abstraction[edit]
Data abstraction is another important element of programming that is enabled in a more consistent way by Cþþthan by C. It consists of defining new data types that would model
something—perhaps signal-processing components or unit generators. We will define a data type by deciding what it is made of (its member variables) and what it can do (its operations). Cþþstructures provide good support for this type of programming. For example, we can define a structure to hold an oscillator’s data (the dataspace) and functions or methods (the methodspace) as follows:
struct Osc { // dataspace 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 *tab, float ph=0.f, int len=def_len, int vsize=def_vsize, int sr=def_sr); ~Osc() { delete[] output; } float *Proc(float amp, float freq); };
The structure holds all the data members that make up the oscillator, the data that needs to be persistent (i.e. present in memory) between invocations of the process function. The methodspace is made up of the bare minimum necessary: an initializing function (the constructor), the deallocating function (the destructor), and the oscillator function proper. The constructor will always have the same name as the structure, and the destructor will have a ‘~’ before the structure name. This is called automatically when the instance of the structure is deallocated. In order to use this data type, all we need do is create one (using all the default parameters):
Osc bing(table);
and the process function will generate audio, as in
output(fp_out, bing.Proc(0.5f, 440.f));
Since Proc() is a member of osc, it is accessed means of a ‘.’. Notice that in the present design the function returns the audio block, which has been allocated inside the data type by the constructor. Member functions such as those declared above can be defined inside the struct definition (as in ~osc()) or externally.
The syntax for the latter is struct_name::member_func() For example, the constructor will be defined 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.
Function Overloading[edit]
Function overloading is another feature of C++. It is possible to have functions with same name in a class, as long as their arguments differ in type or in number. Function selection, then, depends on argument types at the function call. It is possible to supply more than one processing method to the structure, say to take in audio vectors for the amplitude and/ or frequency arguments (for audio rate modulation of these parameters). Because of overloading, it is possible to use the same name (Proc()) for these functions:
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); };
Usage Examples[edit]
fixed 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);
Data Hiding and Encapsulation[edit]
Another feature of Cþþthat can be useful for systems design is the support for data hiding and encapsulation. This allows us to make certain bits of the data structure available only for member functions, and not for functions that are external to the data structure. One reason this might be useful is that we can keep things in a structure completely separate from the rest of the program. We can then provide an interface for accessing only certain elements of our data types, the ones we want to grant access to. This will provide a more robust data type, which will be able to behave only in ways defined by us. In a structure, all members are accessible from the outside: they are public by default. If we use the keyword ‘private’, we can keep them from being accessed by external code:
struct Osc { // dataspace is now private private: 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 needs to be accessible public: Osc(float *tab, float ph=0.f, int len=def_len, int vsize=def_vsize, int sr=def_sr); ~Osc() { delete[] output; } float *Proc(float amp,float freq); };
We can still create an instance of our data type, because the constructor is public:
Osc ping(table);
But we cannot read or write to the member variables:
ping.ndx = 1; // not allowed, ndx is private
Classes[edit]
In C++, a version of struct exists in which all members are private by default. It is called a class:
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.
Inheritance[edit]
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 definition 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.
Virtual Functions[edit]
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 52 Chapter 0 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.
Overloaded Operators[edit]
Similarly to overloaded functions, it is possible to define overloaded forms of specific operators for a certain class definition. 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 definite 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; (...) }
IDEs & Tools[edit]
Cross Platform[edit]
GCC[edit]
The GNU Compiler Collection includes front ends for C, C++, Objective-C, Fortran, Ada, Go, and D, as well as libraries for these languages (libstdc++,...). GCC was originally written as the compiler for the GNU operating system. The GNU system was developed to be 100% free software, free in the sense that it respects the user's freedom.
Make[edit]
In software development, Make is a build automation tool that automatically builds executable programs and libraries from source code by reading files called Makefiles which specify how to derive the target program. Though integrated development environments and language-specific compiler features can also be used to manage a build process, Make remains widely used, especially in Unix and Unix-like operating systems. Besides building programs, Make can be used to manage any project where some files must be updated automatically from others whenever the others change.
Cmake - Cross Platform compiler-independant builder[edit]
Make is an open-source, cross-platform family of tools designed to build, test and package software. CMake is used to control the software compilation process using simple platform and compiler independent configuration files, and generate native makefiles and workspaces that can be used in the compiler environment of your choice. Allows for the configuration of multiple build environments across different platforms, especially useful for teams that use different OS
Windows[edit]
Visual Code[edit]
Plugin Based IDE Works well with MingGW64 & GCC. Similar to PhpStorm and other intellisense based IDEs
MSYS2 - Windows Package Manager[edit]
MSYS2 is a collection of tools and libraries providing you with an easy-to-use environment for building, installing and running native Windows software.
It consists of a command line terminal called mintty, bash, version control systems like git and subversion, tools like tar and awk and even build systems like autotools, all based on a modified version of Cygwin. Despite some of these central parts being based on Cygwin, the main focus of MSYS2 is to provide a build environment for native Windows software and the Cygwin-using parts are kept at a minimum. MSYS2 provides up-to-date native builds for GCC, mingw-w64, CPython, CMake, Meson, OpenSSL, FFmpeg, Rust, Ruby, just to name a few.
To provide easy installation of packages and a way to keep them updated it features a package management system called Pacman, which should be familiar to Arch Linux users. It brings many powerful features such as dependency resolution and simple complete system upgrades, as well as straight-forward and reproducible package building. Our package repository contains more than 2000 pre-built packages ready to install.
For more details see 'What is MSYS2?' which also compares MSYS2 to other software distributions and development environments like Cygwin, WSL, Chocolatey, Scoop, ... and 'Who Is Using MSYS2?' to see which projects are using MSYS2 and what for.
Mingw-w64 - GCC runtime environment[edit]
The mingw-w64 project is a complete runtime environment for gcc to support binaries native to Windows 64-bit and 32-bit operating systems. Mingw-w64 is an advancement of the original mingw.org project, created to support the GCC compiler on Windows systems. It has forked it in 2007 in order to provide support for 64 bits and new APIs. It has since then gained widespread use and distribution.
Code Blocks(open source)[edit]
Visual Studio (Closed Source)[edit]
MAC[edit]
Linux[edit]
Hardware[edit]
Books[edit]
The Audio Programming Book
Audio Tutorials[edit]
- Exercism.org Massive Computer Language Tutorial System
- VCVRack
- Vult
- Pulse Audio
Audio Frameworks & Libraries Supported by EMC23[edit]
PortAudio is a free, cross-platform, open-source, audio I/O library. It lets you write audio programs in 'C' or C++ that will compile and run on many platforms including Windows, Macintosh OS X, and Unix (OSS/ALSA). It is intended to promote the exchange of audio software between developers on different platforms. It provides a very simple API for recording and/or playing sound using a simple callback function.
RtAudio is a set of C++ classes that provide a common API (Application Programming Interface) for realtime audio input/output across Linux, Macintosh OS-X and Windows operating systems. RtAudio significantly simplifies the process of interacting with computer audio hardware. It was designed with the following objectives:
- object-oriented C++ design
- simple, common API across all supported platforms
- only one source and one header file for easy inclusion in programming projects
- allow simultaneous multi-api support
- support dynamic connection of devices
- provide extensive audio device parameter control
- allow audio device capability probing
- automatic internal conversion for data format, channel number compensation, (de)interleaving, and byte-swapping
- RtAudio incorporates the concept of audio streams, which represent audio output (playback) and/or input (recording). Available audio devices and their capabilities can be enumerated and then specified when opening a stream. Where applicable, multiple API support can be compiled and a particular API specified when creating an RtAudio instance. See the API Notes section for information specific to each of the supported audio APIs.
JUCE has hundreds of classes covering a vast range of tasks from high-level user-interface handling right down to low-level collections, networking, strings, etc. Supported platforms are OSX, Windows, Linux, iOS and Android, and the Introjucer project management tool makes it a breeze to create and maintain cross-platform projects.