This tarball contains all the files needed to build and run this assignment.
To build just type 'make'.
To clean all temporary files and executable type 'make clean'.
To run use the following syntax:
sig-zag [type] [frequency] [width] [input]
[type]: --sine | --saw | --pulse | --noise | --impulse
[frequency]: a floating-point number > 0. Not applicable to 'noise' signal.
[width]: pulse width (only applicable types saw and pulse)
[input]: --input (optional)
It's okay to drop the frequency and/or width argument for signal types that don't need them.
The program builds for Linux/JACK. Make sure you have the Jack server running before trying to run it.
I created one callback per waveform type. It would have been possible to create a single callback for multiple waveforms, since some could be reused. I decided against it since it would be both less readable and involve executing more logic each time a callback is called (e.g. 'if' statements to determine signal type).
Initially it was hard to troubleshoot problems with the waveforms. Finding a problem in a waveform is quite different from debugging a non-audio related program, it's not enough to look at values of specific variables, you need some sense of the overall waveform, over many samples. Also, using my ears wasn't always helpful since I wasn't sure how the sounds were supposed to sound, and whether the strange noises I'd occasionally hear were caused by my program or by problems in the real-time performance problems of the audio system.
As suggested on the mailing list, it turned out to be very helpful to write out the waveform to a file and use Audacity to look at the signal. I'm pretty sure I'll be using that debugging technique in the future too.
Opportunity for improvement
Some of the callbacks could be more made more efficient.
Some calculations inside the loops in the callbacks in could be simplified. If I needed to optimize, I'd start here since these calculations are executed frequently.
Certain calculations could be moved outside the callback so that they are only calculated once per program execution. An example is the slope calculation in the saw callback.
- The command line parsing is flexible. For example, the user may (but is not required to) omit the 'frequency' or 'width' arguments for signal types where they are not required, but still supply arguments that come after that (e.g. '--input').
- Command line parsing is rather lax. E.g if the user typed in more arguments than necessary for a certain wave type, we will accept the command line. However if the input arguments are invalid (i.e. out of the acceptable range), the command line will be rejected.
- To scale all outputs by a constant factor, modify the AMPLITUDE macro (near the start of sig-gen.cpp)
- To scale inputs only (useful for low-gain microphones), modify the INPUT_SCALE_FACTOR macro. This was useful when the signal from the microphone was very weak.
- Output to multiple channels is supported, see the OUT_CHANNELS macro. The implementation should also support multiple input channels (see IN_CHANNELS macro), but that has not been tested.
- To dump all output samples to a binary file, define the WITH_DEBUG macro.
- A signal handler was implemented for SIGINT, SIGABRT and SIGTERM. The signal handler takes care of exiting cleanly. The audio stream is closed, the RtAudio object released, and any open files (used for dumping samples) closed. This fixed a problem I encountered on Windows (and to an extent on Linux), where exiting the program using Ctrl-C used to freeze the system(!).