-


> > > > Programming Assignments > Exercise A - Processing Audio Data

Objectives

Introduction

Sound is created by air (or whatever) pressure changing over time. If you graph pressure versus time for a given sound, you may come up with a graph that looks something like this:

periodic waveform for beep

sampled waveform

To represent a continuous curve digitally, we can sample it at regular intervals. At each sample point, we measure the pressure to the desired resolution and record the result as an integer. As we increase the sample frequency and resolution, we will digitize the sound more accurately. In second picture, the blue curve represents the original sound. The tick marks along the bottom show the times at which we take samples. The tick marks along the side reflect the resolution of our samples; the value recorded will be that represented by the closest tick to the actual curve. The red dots represent the samples and the white curve represents the approximation of the original curve that our samples give us.

Suppose we have an old cassette tape. A cassette tape records the pressure wave magnetically on the tape as a continuous signal. There are devices that digitize cassette tapes as described above. But unless the user stops and starts the tape manually, the device needs some help from software to determine how to split the output into separate tracks so that we can put Basketball by Kurtis Blow on endless repeat.

Assignment

Write a program called SplitAudio that reads audio data from standard input (for example, by using scanf) and outputs the starting and ending points of each separate track in the input. The divisions between the tracks are determined by finding at least 4 low values in a row, where "low" means between -5 and 5 inclusive. (1 low value is just the transition between positive and negative in the waveform, and 2 or 3 consecutive low values is just a pause in the song – the low length threshold is unrealistic, but we don't want the files to get too long for this exercise; maybe we should set the program up so that the thresholds are easy to change later). The low values between tracks are not considered part of the track before or the track after, and any number of consecutive low values at the beginning or end of input are not considered part of any track.

The input will contain one integer per line. The output should consist of the starting and ending point of each track, one per line, in increasing order by starting point, and with the endpoints converted from sample numbers to seconds, assuming a 44100Hz sample rate (so that the value on line 44100 (counting from 0) corresponds to time 1.00000s). Each line of output should be of the following form.

[0.000000-0.000045]

The program can simulate a state machine. A state machine is a device for processing input that can be in one of several states that determine what it is doing – what it has already seen and what it is looking for. Upon reading each input, the state machine determines what to do next – what state to switch to – based on its current state and the value of the input.

State Machine

Our state machine will have three states: one for when it is in the middle of reading consecutive low values that are a gap between tracks (GAP); one for when it is reading a track (TRACK); and one for when it has read some low values but not enough to decide to end the current track (ZEROS). state machine for splitting tracks

The state machine starts in the GAP state and then loops over all the input. In the GAP state, if it read a high value, it then remembers the current position in the input as the start of the current track and switches to the TRACK state. Otherwise, when it reads a low value, it remains in the GAP state.

When the state machine is in the TRACK state and it read a high value, it remains in the TRACK state. If it read a low value, it remembers the current position in case it determines later that it is the end of a track, and enters the ZEROS state.

In the ZEROS state, if the input was a high value, it returns to the TRACK state (and can forget the possible end value it remembered upon switching into the ZEROS state). If it read a low value, it determines if the difference between the current position and the possible end position is large enough to define a gap between tracks, and, if so, it outputs the startng and ending positions and enters the GAP state. Otherwise, it remains in the ZEROS state.

Upon finding the end of input, if the state machine is in the TRACK state or the ZEROS state, it outputs the starting and ending positions of the last track.

Code

To simulate the state machine, we need a variable to keep track of which state the machine is in. This is one of three possible values: GAP, TRACK, or ZEROS. We can define a new enumeration type to represent those possible values:
    enum state {GAP, TRACK, ZEROS};
  
before the definition of main declares a new type called enum state that we can use as the type in variable declarations just like any other type. Declare a variable in main of that type and initialize it to GAP:
    enum state curr = GAP;
  
Also declare (and initialize where appropriate) variables to count lines of input, to remember the starting and ending points of the current track, and to hold the current input value.

Write a loop that uses scanf to read one integer at a time from standard input until there are no more integers to read. The format specifier for an int is %d. Remember that scanf takes its parameter by reference and to #include the appropriate header file.

Inside the loop, you will have a multi-way conditional (if/else if/else or switch) based on the value of curr to implement the transitions as described above. You will find the abs from the stdlib library helpful in the curr == GAP case. Note that when you handle a low value in the curr == TRACK case that the end of the track was the value on the line before the one you're currently on.

Remember to increment the input counter at the end of the loop, and to output the starting and ending points of the last track after the loop if the loop ends with the state machine in the TRACK or ZEROS states.

There are likely two different places where you output the starting and ending points: once in the curr == ZEROS case when you see that you've seen enough consecutive low values to determine that the previous track is over, and once after the loop to handle cases where the input ended and the state machine wasn't in the GAP state already. That would be a good place for a function, which you can declare and define in a separate header (.h) and implementation (.c) file if you wish.

You should also create a makefile with the executable SplitAudio as the default target.

Example

If the input is
0
0
1
-1
80
-80
26
17
14
0
0
-1
0
0
10
-90
-32
40
-12
0
100
-80
0
0
0
0
0
0
0
13
19
24
-90
-2
2
then the output must be
[0.000091-0.000181]
[0.000317-0.000476]
[0.000658-0.000726]
  
Since the tracks are in lines 4-8, 14-21, and 29-32 (starting with line 0) and we multiply each endpoint by 1/44100.

Submissions

Submit all of your code and your makefile as submission number 8 (replace the filenames below with your filenames).
[jrg94@hawk cpsc223_split]$ /c/cs223/bin/submit 8 split.c output.h output.c makefile log
[jrg94@hawk cpsc223_split]$ /c/cs223/bin/testit 8 SplitAudio
/home/classes/cs223/Hwk8/test.SplitAudio
Executing /home/classes/cs223/Hwk8/test.SplitAudio

Public test script for SplitAudio (09/05/18)

***** Checking for warning messages *****
Making ./SplitAudio
gcc -std=c99 -pedantic -Wall -g3   -c -o split.o split.c
gcc -std=c99 -pedantic -Wall -g3   -c -o output.o output.c
gcc -std=c99 -pedantic -Wall -g3 -o SplitAudio split.o output.o

Each test is either passed or failed; there is no partial credit.

To execute the test labelled IJ, type the command:
     /c/cs223/hw8/Tests/tIJ
     ./SplitAudio < /c/cs223/hw8/Tests/tIJ
The answer expected is in /c/cs223/hw8/Tests/tIJ.out.


           Basic Execution
  1 point  001. No low values
  1 point  002. Two zeros at start
  1 point  003. Four zeros at start
  1 point  004. Single zero in middle
  1 point  005. Single zero at end
  1 point  006. Three zeros in middle
  1 point  007. Four zeros in middle
  1 point  008. Four low values in middle
  1 point  009. Three low values and one just barely high value in middle
  1 point  010. Non-zero low values all over

           Basic Execution: 10 points

End of Public Script

 10 points Total for SplitAudio

***** Checking log file *****
Estimate: 2:00 ESTIMATE
Total: 3:00 TOTAL
  

Valid HTML 4.01 Transitional