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.
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.
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
).
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.
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.
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 2then 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.
[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