diff --git a/README b/README index d8dbff0..1d6cacd 100644 --- a/README +++ b/README @@ -10,12 +10,11 @@ Disclaimers: enough to get students started in the right direction, I stopped working on it. I accept no blame for any faults in the labs. -- I provided some input to the syllabus, but the final call was - always the lecturer, not me. These labs include some stuff - that I don't think is useful; they even include some stuff that - I think is actively harmful to students learning to program. - (e.g. pointers, excessive amounts of sine waves) - Again, I accept no fault for these. +"The Lecturer" has now rewritten a lot of the course with +Python and PyQt, although it's now been replaced by +a completely new course and back to C++ again, as it's for +electronics engineers who will be doing embedded stuff. +This can stay here in case it's of any use to anyone. This material is dual-licensed under: diff --git a/python/index.html b/python/index.html index 89eb5ad..3fdf911 100644 --- a/python/index.html +++ b/python/index.html @@ -25,7 +25,7 @@

Lab Exercises

+ + +
+
+

... Write up and hand in your work. It's worth 2 normal exercises.

+
+

Back to main

+ + + +

+Unless otherwise noted, all materials on these pages +are licenced under a Creative +Commons Licence. +

+ + + + diff --git a/python/labs/lab-swig.html b/python/labs/lab-swig.html new file mode 100644 index 0000000..f9465ef --- /dev/null +++ b/python/labs/lab-swig.html @@ -0,0 +1,628 @@ + + + +Getting Ready I - SWIG + + + + +Back to main + +

+ Getting Ready for the Grand Challenge I
+ Calling C(++) from Python +

+ +
+Exercises: + + +
+ + +
+ +

Exercise 18: A Very Simple Example

+ +
+

Background

+

+Python isn't the fastest language. It's certainly great +for rapid prototyping and cuts development time by a +great deal, but at run time it's slow, and can't easily make +use of multiple cores you find even in telephones, let +alone desktop workstations. C(++) on the other hand is +more difficult to develop, but offers potentially huge +speed advantages by being "close to the metal". +There will very probably come a time when at least the speed-critical +part of your Python needs to be faster, or you might just +want to call one of the function libraries developed in +C(++) over the last 4 decades. +

+ +

+Python is written in C, so of course it's possible to +extend it by writing new functionality in C(++). +However, calling a C(++) function from Python or using +a C++ class as a Python one requires a lot of boiler-plate +code because of the differences between the languages. +Just think about them: +

+ + + + + +
PythonC(++)
TypesDynamicStatic, Strict
AllocationOn demandExplict
Memory ManagementGarbage collectionRAII, explicit or end-of-scope deletion
+

+Every call to a C(++) function from Python needs to take +these differences into consideration, and that makes for +a lot of tedious, error prone work to connect the two +together. Who allocates memory for the variables passed +between the Python and C(++) contexts? Who frees that memory? +How do we check that the right version of the C(++) function +is being called and that the types agree? +

+ +

+Fortunately, Python is designed to make things as painless +as possible, to the extent that almost all of the boiler-plate +can be generated automatically by a Simple Wrapper Interface Generator +program. One of these programs is called (guess...) SWIG. +

+ +

+Our game plan is as follows: +

    +
  1. Write a function in C++.
  2. +
  3. Write a test harness for it in C++; + compile and link them; + make sure the function works.
  4. +
  5. Write an interface specification file + explaining to SWIG what the function does.
  6. +
  7. Parse the i-file with SWIG to generate the wrapper code.
  8. +
  9. Link the wrapper and your original C++ object files + to produce a shared library.
  10. +
  11. Start Python, import the shared library + just like any regualar Python module, and enjoy.
  12. +

    +
+ +
+

Command Reminders and References

+ +

+You'll aready know that you can compile a C++ program using the +GNU compiler with the following command +

+
+g++ -o output_file_name \
+    input_file_names \
+    -Iinclude_file_path \
+    -Llibrary_path -llibrary
+
+

+and that unless you specify otherwise with the -o flag, +the program is written into a file called a.out +(assembler output). Of course, you can leave the flags you don't +need out, and the include file and library search paths will have +sensible defaults if the compiler has been properly installed. +

+ +

+To work with SWIG you'll need to know +about these other compiler options: + + + +
‑cCompile only: don't link. + Produces an output file with the same name + but a .o extension (by default).
‑fPICProduce Position Independent Code, + i.e. code which may be loaded at any memory address + without modification, and is therefore suitable to + be used in a shared library.
‑sharedLink the named object files + resolving as many references as possibe producing + a shared object library (extension .so). +
+


+

+The python3-config command is your friend. +If you want to know which include files to use compile +your SWIG wrapper, python3-config ‑‑includes +will tell you. +Type it at the prompt and see what it says. +Try ldflags instead of includes. +Remember you can get output of a command +such as this included in the command you type using either +` cmd ` or $( cmd ). +

+
+

+The SWIG documentation is very comprehensive, +although aimed at expert users. Check out the +Executive Summary. +SWIG works with many languages, not just Python, so you'll +find the tutorial useful +as well as the more detailed information about +SWIG and Python. +

+
+ +
+ +
+

Your task...

+
+ In this elementary example, I'm going to give you + all the code. You will have to understand it thoroughly. + You won't be able to write your own unless you do. + That means every single operator, statement and + preprocessor directive in what follows must be absolutely + clear to you. +
+

+Write two C++ files. In one, write a function which prints +"Hello, " followed by its argument. Then write a test file +with a main function in it to make sure your C function behaves +as you wish it to behave. For example, here's the header file: +

+
+// File: simple_hello.h
+//
+// Print a greeting.
+//
+
+#include <string>
+
+void hw(std::string who = std::string("World"));
+
+

+here's the implementation: +

+
+// File: simple_hello.cxx
+//
+// Implementation of the Hello function suite in C++
+
+#include <iostream>
+#include <string>
+
+#include "simple_hello.h"
+
+void hw(std::string who)
+{
+        std::cout << "Hello, " << who << std::endl;
+}
+
+

+and here's the evaluation program: +

+
+// File: simple_hello_test.cxx
+
+#include <iostream>
+
+#include "simple_hello.h"
+
+using namespace std;
+
+int main(int argc, char *argv[])
+{
+        cout << "Calling hw()\n";
+        hw();
+        cout << "Calling hw(argv[0])\n";
+        hw(argv[0]);
+	return 0;
+}
+
+

+ +
+ If you are using a machine which has its Python environment + and numpy installations in non-standard places, you'll need + to do a bit of extra spade work. For example, the machines + in the lab have anaconda installed, which you'll want to use + in preference to their standard Python environment. + Make sure the computer looks there first for executables + with
+ export PATH=/local/anaconda/32/bin/:$PATH
+ You only have to issue this command once per session. +
+ +

+Compile this and test it. Maybe say
+g++ -o simple_hello simple_hello_test.cxx simple_hello.cxx
+./simple_hello
+

+ +

+Now we have to bind this to python. First write an interface file. +In very simple cases like this, you can instruct SWIG simply to +parse the C(++) header file and work out what's going on for itself. +Let's call our python module simple_hello. A suitable +interface file might contain: +

+
+/* File: simple_hello.i */
+
+/* Name our python module */
+%module simple_hello
+
+/* We'll be using C++ standard library strings */
+%include <std_string.i>
+
+/* Put the literal code needed at the top of the output file */
+%{
+#define SWIG_FILE_WITH_INIT
+#include "simple_hello.h"
+%}
+
+/* Parse the c++ header file and generate the output file */
+%include "simple_hello.h"
+
+

Note that the awfully nice SWIG people have already provided +interface files for much of the C++ standard library, including +the C++ string type. We take the opportunity to put two +C preprocessor directives at the top of the wrapper file; +as we get more advanced, we can add extra python-specific +functionality to the wrapper, but for now these two lines are +all you need. They are simply copied into the output file. +We then parse our "library" header file and produce the +C++ code. +

+ +

+To generate the wrapper code for our simple_hello module +you need to say
+swig -c++ -python simple_hello.i
+which tells swig to enable C++ processing, use Python as the +target language, and to perform the actions required by the +interface file. This will generate a file called simple_hello_wrap.cxx +which you should feel free to look at. You should never have to edit +this file (which is just as well!): you can change the interface +file instead to modify it's behaviour. +

+ +

+We're almost there now. We have to make position independent object +files and link them into a shared library, then import that into a +Python session. Here are the appropriate incantations: +
g++ -fPIC -c simple_hello.cxx
+g++ -fPIC -c simple_hello_wrap.cxx $( python3-config ‑‑includes )
+g++ -shared simple_hello_wrap.o simple_hello.o -o _simple_hello.so
+Note the leading underscore character on the filename of the +shared object, and that I told the C++ compiler to look in +Python's include directory for the necessary header files +to connect with the Python interpreter. The include directory +might be different on the machine you're using. +

+ +

+Sometimes you might need extra linkage flags. For example, +MacOS requires a whole bunch of stuff to do with frameworks. +You might try appending $( python3-config --ldflags ) +to the last command above if you get linkage errors. On a Mac, you'd +also normally use the .dylib extension instead +of .so, and say -dynamiclib instead +of -shared. +

+ +
+ If you have a very old C++ compiler, you might have to + encourage it to use the modern standard by adding the + flags ‑‑std=c++0x + or ‑‑std=c++11. + The C++ produced by SWIG relies on modern language features. +
+ +

+You should have covered the use of build tools in an earlier +programming class. Once you've understood what's going on, +you might save yourself some time by writing a Makefile +so that you can run all of the necessary steps by simply +typing make. There is an example of how to construct a +make file in the appendix of the +MCM document. +

+ +

... show your work to a demonstrator

+
+ + +
+ +

Exercise 19: SWIG and numpy nd-arrays

+
+

Background

+ +

+The simple Hello World program +passed a value to a C++ function. It didn't return +any value, but returning simple built-in types +(int, float, double +etc) is handled trivially by SWIG and you can call the function +entirely as expected, using the return value in Python as normal. +

+ +

+An issue arises with C(++) types which are semantically +ambiguous. In C (and C++, although it's frowned upon), +strings are represented as null-terminated character arrays, +and passed around the program as pointers to their +first character. Indeed, the declaration +
char* s = "Hi"; +
is really just a shorthand for +
char* s = {'H', 'i', '\0'}; +
The C "string" doesn't even have a method to determine +its length; it's just a pointer, not an object. +So what is SWIG to do with the pointer-to-char type? +There is a possibility that the C programmer really did +mean a pointer-to-character and it isn't to be regarded +as a "string". +

+ +

+The solution that SWIG offers to this is the application +of "Typemaps". A Typemap tells SWIG to generate some specific +code when the variable maps a type template. Most specific +templates are considered first (and they can include the +name of the variable), so it's possible to specify how +a variable is to be treated on a case-by-case basis. +Writing the Typemap requires a fairly intimate knowledge +of SWIG and the Python run-time, but the numpy authors +have provided an interface file for their package, so we +are in the happy position of only ever having to use those +which we're given. That's not to say you shouldn't ever +think of writing your own Typemap. Just be prepared to +read the manual. +

+ +

+Finally, it's worth noting that as well as functions, SWIG +will convert wrappers for C++ classes wholesale if required. +The wrapped class in the C++ appears as a regular Python +class, and all of its public methods are accessible from +Python. +

+ +
+ +
+

Using Supplied numpy nd-array Typemaps

+

+ To make use of the Typemaps provided to support + numpy nd-arrays through SWIG, it's necessary to + include in your interface file the instructions + to read numpy's interface file and initialise + the necessary array-support code. The first two + stanzas of the following code fragment achieve + this. +

+
+%include <numpy.i>
+
+%init %{
+import_array();
+%}
+
+%apply (double* IN_ARRAY1, int DIM1) {
+  (const double* in, std::size_t in_size)
+};
+%apply (double* INPLACE_ARRAY1, int DIM1) {
+  (double* out, std::size_t out_size)
+};
+        
+

+ The final two stanzas apply the numpy array Typemaps to + functions which have the named arguments and types. You + have to understand that numpy.i is designed to pass arrays + using the convention of supplying a pointer to the first + element and the length of the dimension. + The first %apply instructs functions with the + formal parameter list (double* in, int in_size) + to be treated as if described in the Typemaps IN_ARRAY1 + and DIM1. So supplying a single nd-array argument + in the Python call invokes the C function with two + arguments: a pointer to the first element and the size of + the first (only) dimension. To know that, you have to read + the manual. +

+

+ numpy.i provides a choice of signiatures for arrays of up + to 4D. To pass a 2D array, for example, you might use the + signiature + (DATA_TYPE* IN_ARRAY2, int DIM1, int DIM2) + where DATA_TYPE can be one of the supported types (that includes + almost any simple type you can think of). +

+

+ Notice that the IN_ARRAY can't be modified: the C(++) type + which it is applied to is represented by a const pointer. + If you want to modify the Python array with the C function, + you must use a INPLACE_ARRAY1, INPLACE_ARRAY2, etc. + Python often returns more than one value with calls like + a,b=myfunction(x), but C(++) usually achieves + this by having pointers (or references) passed to the function. + If your code already uses numpy, and the return values are all + of the same type, an INPLACE array might be used to return + them. There is also the ARGOUTVIEWM_ARRAYx template to allow + properly memory-managed views of public arrays in your C++: + check out the documentation. However, if you want to return + multiple values, you might ask yourself whether you shouldn't + do the Pythonic thing and return a tuple. +

+ +
+ +

Reference Documents

+ + +
+ +
+ +
+

Your task...

+

+First of all, provide the implementation for the +class described below. Then implement the resonator +you built in python using C++ from scratch. +

+ +
+ If you have your own local copy of numpy, tell the C++ + compiler to use its header files with (e.g.)
+ export CPLUS_INCLUDE_PATH=/local/anaconda/32/lib/python3.5/site‑packages/numpy/core/include +
That's where anaconda's up-to-the-minute numpy resides + on the lab machines. +
+ +

+To illustrate the techniques involved, I've made a couple +of functions which calculate the minimum, maximum and sum +of an 1D numpy array of doubles. The calculation is done +in C++. +

+ +

+The functions are gratuitously wrapped in a class, for no +other reason than to show the calling convention. They are +static functions so they belong to the class and you don't +have to instantiate an object to use them. mms_t +calculates the maximum, minimum and sum and returns them +as a tuple (actually a Python list). mms_a +returns the values in a numpy array which the caller is +responsible for creating. +

+ +

+Here is the header file. +

+ +
+// File: min_max_sum.h
+//
+// Given an array of doubles, return its minimum and
+// maximum values and the sum of its contents in
+// diverse and amusing ways.
+//
+
+#include <cstddef>
+
+class MMS {
+public:
+        // return the min, max and sum of the given array.
+        // SWIG will arrange for this to be returned as a tuple.
+        static void mms_t(const double* in, std::size_t in_size,
+                          double* min, double* max, double* sum);
+
+        // return the min, max and sum of the given numpy array 'in'.
+        // SWIG will write the values into the given numpy array, 'out'.
+        static void mms_a(double* out, std::size_t out_size,
+                          const double* in, std::size_t in_size);
+};
+
+

+And here is the interface specification +

+
+/* File: min_max_sum.i */
+
+/* Name our python module */
+%module min_max_sum
+
+/* Put the literal code needed at the top of the output file */
+%{
+#define SWIG_FILE_WITH_INIT
+#include "min_max_sum.h"
+%}
+
+/* The typemaps interface file lets us use the *OUTPUT Typemap.
+   This will enable us to use pointers to variables as return results.
+   If more than one *OUTPUT is matched, a tuple gets constructed. */
+%include <typemaps.i>
+
+/* Use the numpy interface for ndarrays. See the warning below */
+%include <numpy.i>
+
+%init %{
+import_array();
+%}
+
+/* Match the arguments of our various C++ methods */
+%apply (double* IN_ARRAY1, int DIM1) { (const double* in, std::size_t in_size) };
+%apply double *OUTPUT { double* min, double* max, double* sum };
+%apply (double* INPLACE_ARRAY1, int DIM1) { (double* out, std::size_t out_size) };
+
+/* Parse the c++ header file and generate the output file */
+%include "min_max_sum.h"
+
+
+

+Just to prove it works, here's a session running on Nick's laptop: +

+
+>>> from min_max_sum import MMS
+>>> import numpy as np
+>>> result = np.zeros(3)
+>>> MMS.mms_t(np.array([1,4,3,7]))
+[1.0, 7.0, 15.0]
+>>> MMS.mms_a(result,np.array([1,4,3,7]))
+>>> result
+array([  1.,   7.,  15.])
+
+ +
+ Some Linux distributions of python3-numpy don't + package the numpy.i interface file. + This is a bug. + If your copy of SWIG complains about not being + able to find numpy.i, you'll have to + download + one and put it in the same directory as your code. +
+ +

... show your work to a demonstrator

+
+ + + + + +

Back to main

+ + +
+ +

+Unless otherwise noted, all materials on these pages +are licenced under a Creative +Commons Licence. +

+ + + + diff --git a/python/labs/lab1.html b/python/labs/lab1.html index 0650893..368acfe 100644 --- a/python/labs/lab1.html +++ b/python/labs/lab1.html @@ -9,7 +9,7 @@ Back to main -

Lab 1 for C programming

+

Lab 1 for python programming

@@ -101,7 +101,7 @@

Technical details

-print "hello world!"
+print("hello world!")
 

@@ -126,9 +126,9 @@

Technical details

 # this is a comment
 
-print "hello"
+print("hello")
 
-# print "this will not be printed"
+# print("this will not be printed")
 
@@ -237,24 +237,24 @@

Technical details

 x = 3
-print "x is currently", x
+print("x is currently", x)
 
 y = 4.001
-print "y is currently", y
-print "Make that shorter: %.1f" % (y)
-print "Make that longer: %.8f" % (y)
+print("y is currently", y)
+print("Make that shorter: {:.1f}".format(y))
+print("Make that longer: {:.8f}".format(y))
 
 z = x/y
 z = x + z*y - y
-print "Why does z = %i ?\n" % (z)
+print("Why does z = {} ?\n".format(z))
 
 animal_name = "cat"
 cute_name = "kitty"
 combo = cute_name + animal_name
-print "We can do \"math\" on strings: %s" % (combo)
+print("We can do \"math\" on strings: {}".format(combo))
 
-print "We can print multiple",
-print "variables: %i %f %i %s\n" % (x, y, z, cute_name)
+print("We can print multiple")
+print("variables: {} {} {} {}\n".format(x, y, z, cute_name))
 
@@ -356,7 +356,7 @@

Technical details

 print "Enter an animal name:"
 animal_name = raw_input()
-print "You typed: %s" % (animal_name)
+print("You typed: ", animal_name)
 

@@ -365,7 +365,7 @@

Technical details

-print "Enter your age (as an integer):"
+print("Enter your age (as an integer):")
 user_input = raw_input()
 age = int(user_input)
 
@@ -373,7 +373,7 @@ 

Technical details

user_input = raw_input() weight = float(user_input) -print "You typed: %i, %f" % (age, weight) +print("You typed: {}, {}".format(age, weight))
@@ -525,10 +525,10 @@

Technical details

return a*a + b*b def p(years, c, animal): - print "If I were a %s, I'd be" % (animal), - print "%i years old.\n" % (years) - print "The sum of squares", - print "was %f units.\n" % (c) + print("If I were a {}, I'd be".format(animal), + "{} years old.\n".format(years)) + print("The sum of squares", + "was {} units.\n".format(c)) my_age = 18 x = 3.3 diff --git a/python/labs/lab2.html b/python/labs/lab2.html index 0576a6b..ae7ae13 100644 --- a/python/labs/lab2.html +++ b/python/labs/lab2.html @@ -9,7 +9,7 @@ Back to main -

Lab 2 for C programming

+

Lab 2 for Python programming

Exercises: @@ -77,13 +77,13 @@

Technical details

if x == 4: # this line will not be printed - print "x is equal to 4" + print("x is equal to 4") # one of these two lines will be printed if (x > 10) or (1 != 0): - print "Expression is true" + print("Expression is true") else: - print "Expression is false" + print("Expression is false")

@@ -106,7 +106,7 @@

Technical details

# between 0 and 0.999999999999 number = random.random() -print "Random number is:", number +print("Random number is:", number)
@@ -220,7 +220,7 @@

Technical details

 while True:
-    print "Infinite loop is infinite"
+    print("Infinite loop is infinite")
 

@@ -230,7 +230,7 @@

Technical details

 i = 0
 while i < 10:
-    print "The loop has repeated", i, "times."
+    print("Looped {} times.".format(i))
     i = i + 1
 
@@ -325,7 +325,7 @@

Technical details

 for i in range(10):
-    print "The loop has repeated", i, "times."
+    print("For the {} time.".format(i))
 

@@ -334,10 +334,10 @@

Technical details

 for i in range(5, 15):
-    print "What is this loop doing?  i is", i
+    print("What is this doing?  i is", i)
 
 for i in range(20, 2, -2):
-    print "What is this loop doing?  i is", i
+    print("What is this doing?  i is", i)
 
diff --git a/python/labs/lab3.html b/python/labs/lab3+4.html similarity index 91% rename from python/labs/lab3.html rename to python/labs/lab3+4.html index 170efd9..cda8b87 100644 --- a/python/labs/lab3.html +++ b/python/labs/lab3+4.html @@ -2,14 +2,14 @@ "http://www.w3.org/TR/html4/strict.dtd"> -Lab 3 +Lab 3 and 4 Back to main -

Lab 3 for C programming

+

Lab 3 for audio programming

Exercises: @@ -22,6 +22,7 @@

Lab 3 for C programming

  • 10. Classes and buffer-based audio
  • +
  • 11. Object-oriented Tone Generation
  • @@ -37,7 +38,7 @@

    Background

    An array is an ordered sequence of values; you cannot -rearrange values without changing the meaning). A two-dimensional +rearrange values without changing the meaning. A two-dimensional array is just a normal "table".

    @@ -74,7 +75,7 @@

    Technical details

    import numpy # initialize all positions with 0 arr = numpy.zeros(8) -print arr +print(arr)

    @@ -197,7 +198,7 @@

    Technical details

    sample_rate, samples = scipy.io.wavfile.read( "a440-1second.wav") -print "Data type is:", samples.dtype +print("Data type is:", samples.dtype) # scale to -1.0 -- 1.0 samples = samples / convert_16_bit @@ -208,7 +209,7 @@

    Technical details

    # scale to -32768 -- 32767 samples = numpy.int16( samples * convert_16_bit ) -print "Data type is now:", samples.dtype +print("Data type is now:", samples.dtype) scipy.io.wavfile.write("quieter.wav", sample_rate, samples) @@ -309,7 +310,7 @@

    Technical details

    # note the capitalization (convention) counter = CounterOne() for i in range(8): - print counter.get_next() + print(counter.get_next())

    @@ -345,7 +346,7 @@

    Technical details

    # the code here doesn't know if it's # using CounterOne or CounterTwo! for i in range(8): - print counter.get_next() + print(counter.get_next())

    @@ -367,7 +368,7 @@

    Technical details

    # pass in increment_value counter = CounterOne(4) for i in range(8): - print counter.get_next() + print(counter.get_next())

    @@ -386,13 +387,31 @@

    Technical details

    extra_stuff = numpy.zeros(2) big_buffer = numpy.append(big_buffer, extra_stuff) -print big_buffer +print(big_buffer)

    +

    Lab 4 for Audio Programming

    + +

    Exercise 11: Object-oriented Tone Generation

    + +
    +

    Making a Noise

    +

    +What objects do you need if you want to write a program to make +a tone? +

    +

    +Sometimes you need to generate a sound in small blocks, +for example in real-time response to some input like a +MIDI-keyboard or an event in a video game. Does this change +your design? +

    +
    +

    Your task...

    @@ -428,7 +447,7 @@

    ... show your work to a demonstrator

    -

    Move on to Lab 4

    +

    Move on to Lab 5


    diff --git a/python/labs/lab4.html b/python/labs/lab4.html deleted file mode 100644 index e66a8d9..0000000 --- a/python/labs/lab4.html +++ /dev/null @@ -1,293 +0,0 @@ - - - -Lab 4 - - - - -Back to main - -

    Lab 4 for Audio Programming

    - -
    -Exercises: - -
      -
    • -11. Look-up table (full) -
    • -12. Look-up table (quarter) -
    • -
    - -
    - - -
    - -

    Exercise 11: look-up table (full)

    - -
    - -
    -

    Maths homework

    - -
    -Do this homework on paper. I know that writing stuff on paper by -hand is a drag, but it will save you a lot of time and effort in -the long run. -
    - -

    -Answer these questions, assuming you are creating CD-quality audio -(44100 Hz, 16-bit samples, stereo channels). -

    - -
      -
    • -How large should your look-up table (a numpy array) be for a sine -wave? -
    • -What should the phase increment be to play a sine wave at 1 Hz? -
    • -What should the phase increment be to play a sine wave at 100 Hz? - -
    • - -Make a rough plot of these two functions: -
      -f(x) = sin(x)
      -g(x) = sin(2*x)
      -
      - -Suppose you wanted to create a look-up table for g(x). How large -should that table be? - -
    • - -Write a look-up table for a sine wave with 10 samples, i.e. - -
      - - - -
      Index012...
      Value00.5880.951...
      -
      - -Assume that you are producing audio at 10 hz (yes, ridiculously -low!). - -
        -
      • -Using your table, write the first 10 values you will use for the -audio to produce a sine wave at 2 hz. Make a rough plot of those -values; does the result look like a sine wave? - -
      • -Using your table as much as possible, write the first 5 values you -will use for the audio to produce a sine wave at 0.5 hz. Use any -reasonable approximation to generate the values you have not -pre-calculated. - -
      • - -
      -
    • -
    -
    - -
    - -
    -

    Your task...

    - -
    -Stop! Before you try to write a program, you must finish the -maths homework. -
    - -

    -Let the user specify a frequency, then generate a sine wave at -that frequency -

    - -
      -
    • -You must calculate your look-up table before asking the user for -their desired frequency. (this imitates the table being -implemented in ROM with silicon) - -
    • -You must use a class for your SineOscillator. All data (look-up -table, internal state, etc) must be part of that class. - -
    • -Given the previous point, you should not need to change anything -from your program in exercise 10 other than your SineOscillator -class. - -
    • -
    - -

    ... show your work to a demonstrator

    -
    - - - -
    - -

    Exercise 12: look-up table (quarter)

    - -
    - -
    -

    Maths homework

    - -
    -Do this homework on paper. I know that writing stuff on paper by -hand is a drag, but it will save you a lot of time and effort in -the long run. -
    - -
      -
    • -Plot one full sine wave, then draw vertical lines diving it into -quarters. For each quarter, indicate whether the quarter is -mirrored or flipped. - -
    • -Write a quarter look-up table for a sine wave with 10 -samples, i.e. -

      - -
      - - - -
      Index012...
      Value00.1560.309...
      -
      - -Assume that you are producing audio at 10 hz (yes, ridiculously -low!). - -
        -
      • -Using your table, write the first 10 values you will use for the -audio to produce a sine wave at 2 hz. - -
      • -
      -
    -
    - -
    - -
    -

    Your task...

    - -
    -Stop! Before you try to write a program, you must finish the -maths homework. -
    - -

    -Let the user specify a frequency, then generate a sine wave at -that frequency -

    - -
      -
    • -You must calculate your quarter look-up table before -asking the user for their desired frequency. (this imitates the -table being implemented in ROM with silicon) - -
    • -You must use a class for your SineOscillator. All data (look-up -table, internal state, etc) must be part of that class. - -
    • -Given the previous point, you should not need to change anything -from your program in exercise 11 other than your SineOscillator -class. - -
    • -
    - -

    ... show your work to a demonstrator

    -
    - - -

    Move on to Lab 5

    - - -

    Back to main

    - - -
    - -

    -Unless otherwise noted, all materials on these pages -are licenced under a Creative -Commons Licence. -

    - - - - diff --git a/python/labs/lab5.xhtml b/python/labs/lab5.xhtml index ba70fd8..61d77b7 100644 --- a/python/labs/lab5.xhtml +++ b/python/labs/lab5.xhtml @@ -17,193 +17,14 @@ Exercises:
    -
    - -

    Exercise 13: Small Angle Interpolation

    - -
    -

    Background

    - -

    -As well as programming a large computer for -audio output, it's often useful to be able to -minimise the time and storage spent generating -a sine-wave. Some devices contain very low -power processors but might still need to -implement audio-frequency oscillators; others -may need to generate sine-waves at very high -frequency sample rates. It is now possible to -build FM receivers using software-only IF -processing (at 10.7MHz) on a relatively modest -dsp chip. -

    - -

    -The size of the look-up table, even in its -quarter-cycle version, would be regarded by -hardware designers as a profligate waste of -memory, when a simple piece of trig can cut -the storage requirement dramatically. -

    - -

    -sin(A+B) = sinAcosB - + cosAsinB
    -sin(x) ≈ x; -cos(x) ≈ 1 - x2/2 -(for small x). -

    - -

    -Here's the clever bit: let's store a smaller -number of entries in the look-up table, and use -the identity above to interpolate between them. -So A will be a "big" angle (cos and -sine obtained from the table) -and B will be the difference -between the value which can be obtained by the -table lookup and the angle we need. B is a "small" -angle, so the approximations can be used to -calculate the final value. -

    - -

    -Do the Maths Homework to find out how much of the -table can be thrown away without compromising -the accuracy of the result. -

    - -
    - -
    -

    Maths homework

    - -

    -Before investing effort in coding up an interpolated -solution, we'd better find out exactly how much storage -can be saved from the look-up table. -

    - -
      -
    • -The interpolation described begins to fail as the -angle increases. If we're using 16-bit audio -and A=0, find how the largest B -before the error exceeds 0.5bit (1/65536) by solving -
      - |sin(A+B) - ξ| = 1/216 -
      -  where ξ = - (sinA)(1-B2/2) - + BcosA -
    • - -
    • -How many samples are there between 0 and this B -radians in your original look-up table? -
    • - -
    • -Is the answer different if A = π/2? -
    • - -
    • -How does the situation change if we ignore the -x2 term and simply take -cos(x)≈1 for small x? -
    • - -
    - -

    Reference: Newton's Method

    -

    -You can find the roots of (most) -differentiable polynomials -iteratively using Newton's method. (Hint: it's easy -to code in Python if you get bored using a calculator) -

    - - - - x - - n - + - 1 - - - = - - x - n - - - - - - - - f - - - ( - - x - n - - ) - - - - - - f - ' - - - ( - - x - n - - ) - - - - - - -
    - -
    - -
    -

    Your task...

    - -

    -Modify the oscillator class you have already -written so that it uses a smaller -look-up table and interpolation. -

    - -

    ... show your work to a demonstrator

    -
    - - -
    -

    Exercise 14: A Recursive Oscillator

    +

    Exercise 12: A Recursive Oscillator

    Background

    @@ -211,7 +32,7 @@ look-up table and interpolation.

    As described in Chapter 2 of mcm.pdf +href="http://markov.music.gla.ac.uk/AP_Sandpit/mcm.pdf">mcm.pdf the position of poles and zeros in Laplacian space characterises a system response. Poles on the left half of the s-plane represent a damped @@ -521,7 +342,7 @@ previous two outputs. This all sounds quite daunting, but it isn't hard after you've worked through what's happening once. Read Chapter 2 of mcm.pdf for more detail. +href="http://markov.music.gla.ac.uk/AP_Sandpit/mcm.pdf">mcm.pdf for more detail.

    diff --git a/python/labs/lab6.html b/python/labs/lab6.html index e749656..966fd9f 100644 --- a/python/labs/lab6.html +++ b/python/labs/lab6.html @@ -16,17 +16,17 @@

    Audio Programming Lab 6


    - -

    Exercise 15: Complex Arithmetic

    +
    +

    Exercise 13: Complex Arithmetic

    Background

    @@ -124,9 +124,9 @@

    ... show your work to a demonstrator


    -
    -

    Exercise 16: frequency response

    -
    + +

    Exercise 14: frequency response

    +

    Background

    @@ -144,7 +144,7 @@

    Background

    we can follow the procedure described in the "Subtractive Synthesis" chapter of mcm.pdf. This describes an easily-rememered graphical +href="http://markov.music.gla.ac.uk/AP_Sandpit/mcm.pdf">mcm.pdf. This describes an easily-rememered graphical method to calculate the magnitude and phase of a system's transfer function given the position of the poles and zeros. In summary: diff --git a/python/labs/lab7.xhtml b/python/labs/lab7.xhtml index 3769b59..e7e0e23 100644 --- a/python/labs/lab7.xhtml +++ b/python/labs/lab7.xhtml @@ -15,19 +15,19 @@
    Exercises: -
      +
      • -17. Manipulating buffers. +15. Manipulating buffers.
      • -18. Filtering with pole and zero placement in the z-plane. +16. Filtering with pole and zero placement in the z-plane.
      • -19. Filtering to a specification. +17. Filtering to a specification.

    -

    Exercise 17
    Manipulating buffers.

    +

    Exercise 15
    Manipulating buffers.

    In this short exercise, we'll practice writing classes @@ -84,7 +84,7 @@ comparison.


    -

    Exercise 18
    Filtering with pole and zero placement +

    Exercise 16
    Filtering with pole and zero placement in the z-plane.

    @@ -350,7 +350,8 @@ Use the coefficients of the filter in Example 16 to test it.
    -

    Exercise 19
    +
    +

    Exercise 17
    Filtering to a specification

    @@ -429,7 +430,7 @@ and then to transform it into the digital domain.

    The design process for a digital filter based on one of the "classical" polynomials is covered in detail in the -Making Computer Music PDF file. In (extreme) summary, +Making Computer Music PDF file. In (extreme) summary,

    • Design the desired filter in low-pass form with a cut-off diff --git a/python/labs/lab8.html b/python/labs/lab8.html index 4dc657c..7bcc227 100644 --- a/python/labs/lab8.html +++ b/python/labs/lab8.html @@ -16,15 +16,40 @@

      Audio Programming Lab 8

      • -20. A Grand Challenge +25. Research Seminar!
      • +
      • +26. Your Very Own Grand Challenge
    +
    + +

    Exercise 25: Research Seminar!

    +
    +

    Your task...

    +

    +Research the literature and present a short (15-minute) presentation on a +Music Technology topic of your choice, to be presented at the Science and Music +Research Group Seminar on Issues in Music Technology. +

    +

    +If you have agreed your on topic for Exercise 21 already, you may present +on that. You may not present on any of the given topics for Exercise 21. +

    +

    +You can use moodle, web sites, html files with links in a browser, +demonstrations etc., but the use of +Powerpoint or any similar software product designed to make vacuous +executives look important or knowledgeable will result in instant failure +and possible disciplinary proceedings. +

    +

    Present your work at the SMRGSIMT.

    +

    - -

    Exercise 20. A Grand Challenge

    +
    +

    Exercise 26. A Grand Challenge

    Your task...

    @@ -59,15 +84,16 @@

    Your task...

    speech or music sample without changing its pitch;
  • Using the Autocorrelation Method, plot the pitch of a piece of music sampled in a given file against time
  • -
  • Write a Linear Predictive Coder which given a speech file +
  • Write a Linear Predictive Coding program which given a speech file and a music file, determines the predictor parameters from the speech and imposes the formants on the music sample, thus creating a "talking instrument"
  • -
  • Your own challenge, if agreed in advance with Nick and Graham. You'll +
  • Your own challenge, if agreed in advance with Nick and +the demonstrator. You'll need to support its viability with background research.
  • -

    ... Write up and hand in your work. It's worth 3 normal exercises.

    +

    ... Write up and hand in your work. It's worth 2 normal exercises.

    diff --git a/python/labs/qt_sketch-1.png b/python/labs/qt_sketch-1.png new file mode 100644 index 0000000..97fb6ab Binary files /dev/null and b/python/labs/qt_sketch-1.png differ diff --git a/python/labs/qt_sketch-2.png b/python/labs/qt_sketch-2.png new file mode 100644 index 0000000..63f03db Binary files /dev/null and b/python/labs/qt_sketch-2.png differ diff --git a/python/labs/qt_sketch-3.png b/python/labs/qt_sketch-3.png new file mode 100644 index 0000000..533e717 Binary files /dev/null and b/python/labs/qt_sketch-3.png differ