CSE 466 Lab 6:
Sound on the iMote 2 and SuperBird
Introduction
In this lab, you will explore the sound generation capabilities of the SuperBord board. You will begin by writing a program to read and play back sampled audio files. Then, you will implement a simple synthesizer using a sine table and ADSR amplitude envelopes.
The last part of this lab is left open-ended to allow you to come up with a creative application for your synthesizer.
WARNING: NEVER attach or detach the iMote2 from the debug board or the sensor board when power is attached. ALWAYS make sure you've unplugged ALL of the USB cables when connecting or disconnecting the sensor board, debug board, or SuperBird.
Objectives
In this lab, you will learn:
- How to write programs that use the Linux Open Sound System
- How to read and play uncompressed digital audio files
- How to generate sounds from a sine table
- How to apply ADSR envelopes
- Optionally, how to use frequency modulation to generate more complex sounds
Suggested Reading and Resources
Part 1: Getting started with the Superbird
Obtain a Superbird board from a T.A. The Superbird board adds several peripherals to the iMote2 via the advanced connector on the iMote2. One of these peripherals is an audio codec with an on-board speaker.
- Make sure your iMote2 and Superbird are powered off, and connect the SuperBird to your iMote2. Plug the SuperBird into the AC adapter. Turn on the SuperBird first by pressing SW4 (near the power connector), then connect the iMote2 to the computer with the USB cable.
- When the iMote2 starts up, the Superbird should display the CSE and Intel logos on the LCD and play a chime sound. Hearing the chime confirms to you that the audio drivers have been properly loaded and that everything is working.
- To turn off the Superbird, you can hold down SW4 for 5 seconds, then release SW4. The Superbird and iMote2 will turn off about 5 seconds after you release the button.
- If there is not enough room to plug the USB cable into the iMote2 with the SuperBird attached, inserting the debug board in between the SuperBird and iMote2 should give you enough space for the USB plug. If you do this, however, note that the accelerometer's data ready line and the UART share a pin, so you should change the debug board to use I2C to avoid a conflict with the accelerometer.
Part 2: Sampled audio playback
For this part of the lab, you will implement playback of sampled audio files in .au format.
- Download the auplay starter code. The zip file contains:
- auplay.c, a skeleton that contains a struct that defines the format of an .au file header and a few useful constants
- several sample .au files, with different encodings, sample rates, and numbers of channels
- sndoutctl, a program which lets you switch the output between the on-board speaker and the line out connector (J5, the jack farthest from the edge of the board). You'll need to create a node for the superbird-audio device at /dev/sa-test for it to work.
- Add code to auplay.c that accepts the name of a .au file as an argument, reads the header from the audio file, and prints out the encoding (format), sample rate, and number of channels for the recording.
- Use the sample files provided to test that your program is able to accurately recognize different types of .au files. Your program should be able to handle 8- and 16-bit signed linear PCM files, in addition to 8-bit µlaw. It should also work for any sample rate that the codec will support, with either 1 or 2 channels.
- Note that all of the data fields in the header, in addition to the sample data, are stored in network (big-endian) byte order. The PXA271 is running in little-endian mode. You will likely find the ntohl() (convert from network to host order, long (i.e. 32-bit integer)) function in arpa/inet.h to be useful in reading the header.
- Refer to the OSS API documentation section on the ioctl() system calls. Modify your program to open /dev/dsp as a write-only device and use ioctl() calls to set up the sound device for an encoding (format), sample rate, and number of channels that matches the information in the header of the audio file you're reading.
- Make sure you read the documentation for the OSS ioctl() calls carefully. Note that you cannot pass constants as arguments.
- Once your program has successfully opened the audio device and set the appropriate format parameters, read the rest of the data in the audio file and copy it to the sound device using the write() call. The OSS API does not specify the buffer size you should use when writing to the audio device. You should choose a size that is reasonable; i.e. you should be copying more than a single sample on each write, but you should not attempt to copy the entire audio file at once.
Part 3: Sine table synthesis
In this part of the lab, you will design a synthesizer that uses a table-based oscillator.
- The sin() function in math.h implements the sine function for angles in radians with floating-point arithmetic. Since the support for floating point numbers is limited on the iMote2, you should precalculate the sine table on attu and compile it into your synthesizer. You should write a C program that you can run to produce the table for you.
- Your program should produce as output a valid .c file that defines your table as an array of type int16_t that you can include in your synthesizer program.
- You can think of each sample as a 16-bit signed fixed-point number, where an integer value of 32767 represents the maximum value of 1, and -32767 represents -1. If you exceed this range, the numbers will "wrap around" and create crackly artifacts in the sound.
- It is up to you to determine the size of your table. You may want to make the table size an argument to your program so you can easily create tables of different sizes as you are designing your synthesizer.
- Now that you have your fixed-point sine table, write a program that uses it to generate continuous tones.
- Set up and open the sound device (/dev/dsp) the same way you did with your au playback program. You should configure it to use 16-bit samples at 44100 Hz.
- Your program should use a fixed-point counter that is incremented on each sample to track the current position in your sine table. To attain higher frequencies, you increment by a larger amount on each sample, and to attain lower frequencies, you increment by a smaller amount.
- You should not write each sample to the audio device immediately as it is ready; rather, your program should copy samples to a buffer of about the same size you used for your au playback program, and then when the buffer is full, you write it to the audio device.
- Next, write another program to run on attu that will generate another table containing an amplitude envelope. Like your sine table generator, this program should produce a valid .c file containing the table in an array that you can include in your synthesizer.
- Once again, the size of the table is up to you. It doesn't necessarily have to be the same size as your sine table; you'll be incrementing through it with a different counter at a different speed. Whether you use 8- or 16-bit fixed-point numbers is up to you as well.
- Your program should take arguments that let you customize the table size, as well as the attack, sustain, decay, and release parameters of the envelope. (Refer back to the lecture notes on sound for definitions of these parameters.)
- Modify your synthesizer to apply your amplitude envelope to the generated sound. You should use a separate counter for the envelope table, and make sure that you stop producing sound when you reach the end of your envelope.
- Remember the rules for multiplying two numbers with a fixed number of decimal places. The same rules apply to binary fixed-point numbers. If you multiply a number with 16 bits of fraction by a number with 8 bits of fraction, the resulting value will have 24 bits of fraction. Use the right shift operation to move the "binary point" back where you want it to be.
- Be careful not to exceed the maximum value that you can represent in a 16-bit signed number when writing samples to the audio codec. If you do, you'll hear it in the form of pops and clicks in the audio.
Part 4: Do something interesting with your synthesizer
At this point, you should have a working synthesizer that generates sounds from a sine table and applies an amplitude envelope. Now's your chance to be creative and extend your synthesizer program to do something with the sounds it can generate.
- One option is to create a simple sequencer and have your synthesizer play a song. Calculate the increment values for your table that produce different notes on the chromatic scale, and add constants to your program that define the names of the notes ('C4', etc) as their numeric increment values. Write a function that takes the increment value and a duration as arguments, then call this function with a sequence of notes that play a song.
- You can obtain the frequencies of the chromatic scale by taking the frequency of your base note and multiplying by the twelfth root of two. Each time you do this, you increase the pitch by a half step. You can go up an octave at a time by doubling the frequency.
- Alternately, come up with a different project idea for your synthesizer. There are several sensors on the boards available to you that could make interesting control inputs for a synthesizer. Run your idea by a T.A. prior to starting.
- Make sure you make a back-up copy of your working synthesizer code from Part 3 before modifying it.
- Extra credit may be awarded for particularly creative and well-implemented ideas.
Extra Credit: Implement FM synthesis
For extra credit, you can add a second oscillator and generate sounds via frequency modulation synthesis. See this PDF for some examples.
Deliverables
Turn in all required documents via Catalyst, or as an e-mail attachment to cse466-tas@cs. For all files turned in, the comments at the top of the file should contain:
- Both partners' full name, login and student number.
- The lab number and the part of the lab (e.g. "Lab4, Part2").
- Demonstrate your .au player and your synthesizer to a T.A. You can either do this during lab, or during the first 1/2 hour of the next lab.
- At the end of the second week, you should turn in source files for:
- your .au player,
- your table generation utilities, and
- your synthesizer application.
You should create a .zip or .tar archive of all of the files you are turning in.