,,wav。
Introduction
Music synthesizers are programs that take music in notational form and produce the digital signals required to play the sound. Thus, they are similar to a compiler for music. For this milestone the synthesizer will use a very simple notation for the music, a sequence of pure tones with a specified amplitude and duration. Consider a middle C note which has a frequency of 261.63 Hz, lasting 3 seconds and amplitude F. This can be written as a sinusoidal function:
f(t) = F sin( 2*pi*f*t )
with f = 261.63 and t E (0, 3). To represent this continuous function digitally we need to sample the sinusoid in time, using a sampling rate t, and quantize the amplitude into a fixed range by choosing a quantization level, Q in bits. This is referred to as pulse code modulated data. In this milestone we will use a fixed sampling rate of t = 1/44100 and Q = 16 bits, that is each sample is of type int16_t (http://en.cppreference.com/w/cpp/types/integer). The fixed-width integer types (int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, etc.) are defined in the header cstdint (http://en.cppreference.com/w/cpp/types/integer). You should always use them instead of the types int , long , etc. when you need to depend on the memory size of the type.
You will write a program to read a song represented as a sequence of tones with a specified amplitude and duration and produce a sampled signal in WAVE format suitable for playing on a computer. The program should perform strict error checking of input, printing a warning message and skipping any malformed (non-blank) line of input. The number of lines and hence the length of the song is arbitrary.
The program should read the input file and create a sampled signal representing the sound, scaling the peak amplitude to the allowable range of a 16 bit (short) type if required, and save it as a binary file in a simplified WAVE format as specified below.
The input file is a text file in comma separated value format with three columns. Each line contains a single tone with the number of lines being arbitrary and indicating the number of tones in the song. On each line, the first column contains one of seven notes written as one of: C (261.63 Hz), D (293.66 Hz), E (329.63 Hz) , F (349.63 Hz), G (392.0 Hz), A (440.0 Hz), B (493.88 Hz). The second column is a positive floating point number representing the amplitude of the note in arbitrary units, and the third column, a strictly positive floating point number representing the duration in seconds.
For example the following file specifies 3 notes,
C, 10.0, 2.5 G, 20.8, 5.0 C, 10.0, 3.0
Each note should be played-back-to back, with no gaps in between, using the sin function as specified above, where t=0 when the note starts.
Output File Specification
This milestone will use a simplified version of the WAVE format. A WAVE file is a binary file that consists of a header section followed by the sampled signal data. The header consists of 14 fields written consecutively with a bit widths and values as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| int8_t ChunkID[4]; int32_t ChunkSize; int8_t Format[4]; int8_t Subchunk1ID[4]; int32_t Subchunk1Size; int16_t AudioFormat; int16_t NumChannels; int32_t SampleRate; int32_t ByteRate; int16_t BlockAlign; int16_t BitsPerSample; int8_t Subchunk2ID[4]; int32_t Subchunk2Size; int16_t Data[NUMSAMPLES];
|
Each field should be written to the binary output file in little-endian order. The output wav file can be played with almost any music player, for example VLC (http://www.videolan.org/vlc/index.html).
Hints
Recall that to write a file byte-by-byte in C++ it should be opened in binary mode. Reading and writing from/to a binary file is different than when using text files. First, you have to open the file in binary mode, e.g.
1
| std::ofstream outstream("output_binary.file", std::ios::binary);
|
To write a binary value you use the write method, which takes a pointer to a memory location as a std::fstream::char_type * (a pointer to the type fstreams use to hold characters) and the number of bytes to read. The pointer must be cast using a reinterpret cast. For example to write an unsigned 32 bit integer one would do
1 2
| uint32_t value = 0; outstream.write(reinterpret_castlt;std::fstream::char_type*gt;(amp;value), sizeof value);
|
When reading and writing binary files it is often usefull to have a Hex Editor https://en.wikipedia.org/wiki/Hex_editor to aid with debugging. There are many availble for free https://en.wikipedia.org/wiki/Comparison_of_hex_editors for various platforms. The reference environment specified in the Vagrantfile provided includes a command-line tool xxd to dump a hex representation of a file.
Setup
Accept the GitHub invitation above and then clone the git repository to your local machine. Implement your program in a source file named synth.cpp . You should use git to commit versions of your program source (only) as you go along, demonstrating good incremental programming technique.
Correctness
The file names to read and write are specified as command line argument (whatever the user types on the command line after the executable). If you need a primer on how to use command line arguments, see below.
The following program invocation reads the file twinkle.csv and converts it to the file twinkle.wav (Assuming Windows platform, $ as the command line prompt)
$ .\synth.exe twinkle.csv twinkle.wav
If no file names are specified, or the specified files cannot be opened, the program should print an appropriate error message to standard error and return EXIT_FAILURE . If the input file is invalid, then an error message should be printed to standard error and the progeram should return EXIT_FAILURE . Otherwise it should convert the file and return EXIT_SUCCESS.
Testing
The initial git repository has a sub-directory called test which has several examples of input files (ending in .csv) and corresponding expected output (ending in .wav). The included CMakeLists.txt file sets up these tests for you. Just configure the build, run the build, and then run the tests.
Correctness means that the program reads the filename from the command line and write the output per the specification. Code quality will be assessed in this assignment by ensuring your code compiles cleanly, with no warnings, using the g++ flag -Wall -Wextra
in the reference environment, as specified above. That is in the VM typing g++ -std=c++11 -Wall -Wextra /vagrant/synth.cpp
should produce no errors or warnings. You should also have made more than 2 commits to your repository before submission
A Primer on Command Line Arguments
Recall the primary ways to get user input into a program, standard input ( std::cin ) and reading from files. Another very convenient one is to use command line arguments. When your program is run, usually from another program (the operating system or a shell program), it starts executing in the function main . This is called the entry point.
1 2 3 4
| int main() #123; return 0; #125;
|
There is another form of this entry point with two arguments. The first (traditionally named argc ) is the number of string arguments to the program, the second (traditionally named argv ) is an array of “argc” C-style strings (pointers to null-terminated memory) with the arguments themselves.
1 2 3
| int main(int argc, char*argv[]) #123; #125;
|
You can specify the command line arguments when you run the program from a text shell (e.g. powershell or bash), from a graphical shell (like when you click on an icon), or from a script.
It is easy to convert the more C-style arguments to a more modern C++ style as follows
1 2 3 4 5 6 7 8 9
| #include lt;vectorgt; #include lt;stringgt; int main(int argc, char*argv[]) #123; std::vectorlt;std::stringgt; arguments; for(int i = 0; i lt; argc; ++i) arguments.push_back(argv[i]); return 0; #125;
|
A related question to the previous is why does main return an int anyway? Well, when your program is run to completion it can indicate to the running process if it succeeded or if an error occurred by returning this int. Success is defined as zero, which is why most simple examples returns that. The following are defined in the header “cstdlib”: “EXIT_SUCCESS” and “EXIT_FAILURE”, and can be used when returning from main.