User Tools

Site Tools


cs19f22as08

cs19f22as08: Graphing a PCM Waveform

Goals

  • Work with explicitly binary data in C++.
  • Learn a bit about digital audio representation.

Prerequisites

This assignment requires familiarity with the lecture materials presented in class through week 08.


Background

Review the discussion in lecture regarding PCM (pulse-code modulation) and its use in representing digital audio.


Assignment

You shall write a C++ program that accepts two command-line arguments specifying the width and height of an image and interprets the contents of standard input as a stream of raw (binary) 16-bit integer PCM audio samples (such as those emitted by week 08's pcm_generator example) and emits on standard output a sequence of 8-bit grayscale pixel values representing a width×height-pixel image (similar to week 08's grayscale_pixels example) on which the PCM waveform has been plotted as white pixels against a black background.

Assume any amount of input samples on stdin in native byte order1), and make sure that the image consists of a plot of the entire audio waveform:

  1. On the x axis, ensure that the left end of the image represents the first audio sample and the right end of the image represents the last audio sample, interpolating the horizontal position of all other samples by rounding to the nearest pixel along the image width.
  2. On the y axis, ensure that the top of the image represents a 16-bit sample with the maximum possible value (32767), and the bottom of the image represents a sample with the minimum possible value (-32768), interpolating all other possible sample values by rounding to the nearest pixel along the image height.
  3. Always round any calculations to the nearest pixel using double precision.

Ensure that the waveform is visible by drawing a “bounding box” around each plotted sample, i.e. color the pixels a certain distance around the plotted sample as well as the sample itself. The distance shall be:

$$ 2 \cdot \frac{\text{image width}}{\text{total # of samples}} $$

Constrain the bounding-box distance such that it is no fewer than 2 pixels and no greater than $\frac{\text{image height}}{16}$ pixels.

Tips

Since iostream (and C++ generally) uses char arrays to represent raw binary data (a char is always 1 byte), you will need to read and write char arrays. To interpret the input as 16-bit signed integer PCM samples, you can simply reinterpret a char array as an array of signed 16-bit integers:

[cling]$ #include <cstdint>
[cling]$ char raw_bytes[] = {'\xff', '\x7f', '\x01', '\x00'};
[cling]$ int16_t *samples = reinterpret_cast<int16_t *>(raw_bytes);
[cling]$ samples[0]
(short) 32767
[cling]$ samples[1]
(short) 1

Since you will be calling read() on std::cin, you may find function std::istream::gcount() useful, since it will tell you how many bytes were actually extracted:

[cling]$ char buf[1024];
[cling]$ std::cin.read(buf, 1024);
Hello!
[cling]$ std::cin.gcount()
(long) 7

Demonstration and Sample Executable

Sample executable cs19_pcm_waveform demonstrates the expected behavior of your program, and executable cs19_pcm_generator is a slightly fancier and configurable version of week 08's pcm_generator example (though by default it behaves the same). You can combine these with the convert command to generate actual image files. pcm_waveform also prints to stderr the total number of samples received and the resulting size of the bounding box, for testing purposes (you don't have to include this in your program).

Image depicting sine, square, triangle and sawtooth waveforms

cs19_pcm_generator accepts two optional command-line arguments:

  1. A maximum positive amplitude for the wave, i.e. an integer expected to be < 32767 (defaults to 25000 if unspecified)
  2. A type of waveform, specifically either sine (the default if unspecified), square, triangle or sawtooth (see image on the right)

For example, the following command will generate a 1 second of a 4 Hz sine wave, so if we graph it we should see pretty much exactly 4 wave periods spread across the entire image:

cs19_pcm_generator <<< '4 1' |
  cs19_pcm_waveform 8192 512 |
  convert -depth 8 -size 8192x512 gray:- ~/public_html/as08.png

If you run the above command, you should see the following image at URL jeff.cis.cabrillo.edu/~/as08.png:

Generated image of a 8 Hz waveform with a duration of 1 second.

Here are some other commands and their expected images:

cs19_pcm_generator <<< '20 .1 200 .1 400 .1' |
  cs19_pcm_waveform 17280 1080 |
  convert -depth 8 -size 17280x1080 gray:- ~/public_html/as08.png

Generated image of the frequencies and durations 20 .1 200 .1 400 .1

cs19_pcm_generator <<< '440 .02' |
  cs19_pcm_waveform 800 800 |
  convert -depth 8 -size 800x800 gray:- ~/public_html/as08.png

Generated image of a 440 Hz waveform with a duration of .02 seconds.

<html> <audio controls>

<source src="/classes/portfolio/_media/assignments/pcm_waveform_nbc_sine.flac" type="audio/flac">

</audio> </html>

cs19_pcm_generator 10000 sine <<< '196 .25 392.63 .25 261.63 .25' |
  cs19_pcm_waveform 8192 512 |
  convert -depth 8 -size 8192x512 gray:- ~/public_html/as08.png

Generated image of a sine wave of frequencies and durations 196 .25 392.63 .25 261.63 .25

<html> <audio controls>

<source src="/classes/portfolio/_media/assignments/pcm_waveform_nbc_square.flac" type="audio/flac">

</audio> </html>

cs19_pcm_generator 10000 square <<< '196 .25 392.63 .25 261.63 .25' |
  cs19_pcm_waveform 8192 512 |
  convert -depth 8 -size 8192x512 gray:- ~/public_html/as08.png

Generated image of a square wave of frequencies and durations 196 .25 392.63 .25 261.63 .25

<html> <audio controls>

<source src="/classes/portfolio/_media/assignments/pcm_waveform_nbc_triangle.flac" type="audio/flac">

</audio> </html>

cs19_pcm_generator 10000 triangle <<< '196 .25 392.63 .25 261.63 .25' |
  cs19_pcm_waveform 8192 512 |
  convert -depth 8 -size 8192x512 gray:- ~/public_html/as08.png

Generated image of a triangle wave of frequencies and durations 196 .25 392.63 .25 261.63 .25

<html> <audio controls>

<source src="/classes/portfolio/_media/assignments/pcm_waveform_nbc_sawtooth.flac" type="audio/flac">

</audio> </html>

cs19_pcm_generator 10000 sawtooth <<< '196 .25 392.63 .25 261.63 .25' |
  cs19_pcm_waveform 8192 512 |
  convert -depth 8 -size 8192x512 gray:- ~/public_html/as08.png

Generated image of a sawtooth wave of frequencies and durations 196 .25 392.63 .25 261.63 .25

Leaderboard

As submissions are received, this leaderboard will be updated with the top-performing fully/nearly functional solutions, with regard to execution speed.

<html>

<table> <thead> <tr><th>Rank</th><th>Test Time (s)</th><th>Memory Usage (kB)</th><th>SLOC (lines)</th><th>User</th></tr> </thead> <tbody id=“leaderboard-table”> </tbody> </table> <script> function updateLeaderboard() { window.fetch(`/~turnin/leaderboard-cs19f22as08.txt?t=${new Date().getTime()}`, {

method: 'get'

}).then(response ⇒

response.text()

).then(text ⇒ {

let updated = document.getElementById('leaderboard-updated');
updated.innerText = `(Last updated: ${new Date()})`;
let lines = text.split('\n');
let table = document.getElementById('leaderboard-table');
while (table.firstChild)
  table.removeChild(table.firstChild);
for (let i = 0; i < lines.length; ++i) {
  let tokens = lines[i].split(' ').filter(token => token.length > 0);
  if (tokens.length < 2)
    continue;
  let tdRank = document.createElement('td');
  tdRank.textContent = i + 1;
  let tdTime = document.createElement('td');
  tdTime.textContent = Number(tokens[0]).toFixed(4);
  let tdMemUsage = document.createElement('td');
  tdMemUsage.textContent = tokens[1];
  let tdSloc = document.createElement('td');
  tdSloc.textContent = tokens[2];
  let tdUser = document.createElement('td');
  let userLink = document.createElement('a');
  userLink.href = `/~${tokens[3]}/`;
  userLink.target = '_blank';
  userLink.textContent = tokens[3];
  tdUser.appendChild(userLink);
  let tr = document.createElement('tr');
  tr.appendChild(tdRank);
  tr.appendChild(tdTime);
  tr.appendChild(tdMemUsage);
  tr.appendChild(tdSloc);
  tr.appendChild(tdUser);
  table.appendChild(tr);
}

}).catch(err ⇒ {

console.log('Something bad happened: ' + err);

});

window.setTimeout(updateLeaderboard, 60000);

} updateLeaderboard(); </script> </html>


Submission

Submit your source-code file(s) via turnin. If you submit multiple files, make sure there is only one main() function defined.

Feedback Robot

This project has a feedback robot that will run some tests on your submission and provide you with a feedback report via email within roughly one minute.

Please read the feedback carefully.

Due Date and Point Value

Due at 23:59:59 on the date listed on the syllabus.

Assignment 08 is worth 60 points.

Possible point values per category:
---------------------------------------
Precision of waveform                60
Possible deductions:
  Style and practices            10–20%
Possible extra credit:
  Submission via Git                 5%
---------------------------------------
1)
Don't worry about endianness.
cs19f22as08.txt · Last modified: 2023-03-27 17:22 by 127.0.0.1