DEPARTMENT OF COMPUTING

CS 3005: Programming in C++

Waveform Classes

Introduction

Sound is sent through the air as compressions and expansions of the density of the air molecules. A single pitch sound comes from a cyclic compression/decompression at a specific frequency. For example, Middle C on a piano keyboard produces sound at approximately 261.6258 Hz (Hz = cycles per second).

When working with digital representations of sound, we record the increase or decrease in the air density, as the signal. For example, the following image shows a cycle of a sine wave. We allow the amplitude to range between 1.0 and -1.0, indicating the most compression and the most decompression.

sine-args-waveform.png

Notice that the amplitude increases and decreases smoothly. You can hear the smooth sound produced by this waveform in the audio track. If we reduce the amplitude of the signal, the sound becomes more quiet. Increasing the frequency of the sine wave will produce a higher pitched sound, and decreasing the frequency will produce a lower pitched sound.

We can also change the shape of the signal to produce a different sound. This is a square wave. It abruptly changes between the maximum and minimum amplitude, and has a harsher sound.

square-args-waveform.png

Audio designers will try many different shapes to create different sounds. The amplitude is used to control the volume of the sound, and the frequency is used to control the pitch of the sound.

Assignment

In this assignment, you will create a class hierarchy to represent the functionality of a waveform, including specialization for the sine and square waves.

The Waveform class will be a base class. SineWaveform and SquareWaveform will inherit from it. The base class will be responsible for storing the amplitude of the wave. It will also provide the functionality to compute the wave’s position in a cycle, given the time since the beginning of the wave. It will also provide the functionality of generating all samples for a waveform over a given period of time, storing the samples in an AudioTrack object. The derived classes will provide the functionality capable of producing the specific shape of the waveform.

You will also create a simple program that writes a WAV file after the user selects the waveform, amplitude and frequency, and the duration of the audio track.

A sample interaction with the program may look like this:

$ ./program-waveform-test/waveform_test 
Samples/Second: 44100
Seconds: 2.5
Bits/Sample[8,16,24,32]: 16
Left Channel
Waveform style: sine
Amplitude: 1.0
Frequency: 265
Right Channel
Waveform style: square
Amplitude: 0.5
Frequency: 300
WAV filename: foo.wav 
$ ls -l foo.wav 
-rw-rw-r-- 1 cgl cgl 441044 Oct 10 14:41 foo.wav

This example demonstrates a user giving an invalid waveform style.

$ ./program-waveform-test/waveform_test 
Samples/Second: 8000
Seconds: 1.2
Bits/Sample[8,16,24,32]: 24
Left Channel
Waveform style: sawtooth
Amplitude: 1.0
Frequency: 400
Waveform style 'sawtooth' is not known.
Right Channel
Waveform style: sine
Amplitude: 1.0
Frequency: 300
WAV filename: bar.wav
$ ls -l bar.wav 
-rw-rw-r-- 1 cgl cgl 57644 Oct 10 14:45 bar.wav

Computing Position in Waveform (Angle from Sample Number)

The units for frequency are Hertz (Hz) which is the same as (1/Seconds). The units for samples_per_second are Samples/Seconds. The units for sample_number are Samples. The units of two_pi are Radians. The value of two_pi is approximately 6.283185307179586476925286766559.

To compute the angle from the sample number we need to use this formula:

angle = two_pi * sample_number * frequency / samples_per_second

Note the units are:

Radians = Radians * Samples * (1/Seconds) / (Samples/Seconds)
Radians = Radians * Samples * (1/Seconds) * (Seconds/Samples)
Radians = Radians * (Samples/Samples) * (Seconds/Seconds)
Radians = Radians

This checks out.

Computing Cycle Position

The cycle position is the fractional part of a cycle within the full position. We want to make this be a number in the range [0, 1).

This is calculated by getting the angle of the full position, then repeatedly subtracting of the angle of a full cycle, until the remaining angle is less than a full cycle. The angle of a full cycle is two_pi. Note that this repeated subtraction is the same as taking the modulus of the angle by two_pi.

The problem is that the modulus operator (%) only works on integers. We are working with floating point numbers. We need to use std::fmod() (look in the documentation to find the correct header file) to compute the remaining angle.

After getting the remaining angle, we get the position in the cycle by dividing by two_pi.

Programming Requirements

Create library-waveform/Waveform.{h,cpp}

Waveform Class

Data Members:

The Waveform class should contain data members to track the following information. These data members should be protected or private. They are not allowed to be public.

public Methods:

Special Note

It is recommended that you define a constant in the Waveform.cpp file to use the precise same value for two_pi everywhere it is needed.

const double two_pi = 6.283185307179586476925286766559;

Create library-waveform/SineWaveform.{h,cpp}

SineWaveform Class

Publicly inherits from Waveform.

Data Members:

The SineWaveform class does not define any new data members.

public Methods:

Create library-waveform/SquareWaveform.{h,cpp}

SquareWaveform Class

Publicly inherits from Waveform.

Data Members:

The SquareWaveform class does not define any new data members.

public Methods:

Create library-waveform/Makefile

This file must contain rules such that any of the following commands will build the libwaveform.a library:

This file must contain rules such that the following command will install the libwaveform.a library into the lib directory, and all .h files to the include directory:

Create library-commands/waveform_test_aux.{h,cpp}

Functions:

Update library-commands/Makefile

Add waveform_test_aux.{h,cpp} in the appropriate places to add them to the library and install the header file.

Create program-waveform-test/waveform_test.cpp

Functions:

Create program-waveform-test/Makefile

This file must contain rules such that any of the following commands will build the waveform_test program:

Create program-waveform-test/.gitignore

The file program-waveform-test/.gitignore needs to store one line of text:

waveform_test

Update Makefile

Update the project-level Makefile so that make and make all in the project directory will call make install in the library-waveform directory, and make in the program-waveform-test directory.

Additional Documentation

TBA

Grading Instructions

To receive credit for this assignment:

Extra Challenges (Not Required)

TBA

Last Updated 01/27/2025