CS 312 - Week 8.2 Class
CS 312 Audio Programming Winter 2020

Table of Contents

8.2 Class

Harmonic Series

When you press a key on a piano, blow into a wind or brass instrument, pluck a guitar string, or sing you generate a note at a certain frequency. That note is actually composed of several frequencies related by the harmonic series.

Harmonic Series Definition

Mathematics

A series whose terms are reciprocals of an arithmetic progression \[1{\text{ }} + {\text{ }}\frac{1}{2}{\text{ }} + {\text{ }}\frac{1}{3}{\text{ }} + {\text{ }}\frac{1}{4} \cdots \frac{1}{n}\]

Music

A series of tones consisting of a fundamental tone and consecutive integer multiples (harmonics) of the fundamental. The fundamental is designated as harmonic 1.

\[{f} + 2{f} + 3{f} + 4{f} \cdots n{f}\]

Alternatively a series of tones consisting of a fundamental tone and and its harmonics whose periods follow the harmonic series.

Name Period Frequency 100 Hz Fundamental
First Harmonic 1 f 100
2nd Harmonic 1/2 2f 200
3rd Harmonic 1/3 3f 300
4th Harmonic 1/4 4f 400
5th Harmonic 1/5 5f 500
6th Harmonic 1/6 6f 600
7th Harmonic 1/7 7f 700
8th Harmonic 1/8 8f 800
9th Harmonic 1/9 9f 900
10th Harmonic 1/10 10f 1000
11th Harmonic 1/11 11f 1100
12th Harmonic 1/12 12f 1200
13th Harmonic 1/13 13f 1300
14th Harmonic 1/14 14f 1400
15th Harmonic 1/15 15f 1500
16th Harmonic 1/16 16f 1600

Harmonics, Overtones, And Partials

These terms are often used misused when referring to notes in the harmonic series.

Harmonics
Harmonics refer to integer multiples of a fundamental frequency.
Harmonic 1 is the fundamental frequency.
Overtones
Overtones also refer to integer multiples of a fundamental frequency.
Overtone 1 is harmonic 2.
Partials
A partial is a non integer multiple of the the fundamental frequency and does not occur in the harmonic series.

Harmonic Series In Musical Notation

The first 16 harmonics of C2 (MIDI 36) are shown below. Harmonics 2f, 4f, 8f, and 16f (the octaves above C2) precisely match the notes on the piano. Because the piano is tuned in equal temperament (hw825) all octaves are in perfectly in tune. All other the notes are slightly out of tune.

harmonicsPiano.png

Historical tuning systems

Pythagoras is credited with discovering that dividing a string in half produced a tone one octave higher and dividing a string into thirds produced a tone an octave and a fith higher. Medieval and Renaissance musicians used tuning systems where all musical intervals were related by simple fractions 1/2 and 2/3 (octave and fith). Length and frequency are inversely related.

  Length Frequency
Octave lower x2 1/2
Octave higher 1/2 x2
Fifth higher x2/3 3/2
Fifth lower 3/2 x2/3

For example, given string length 1, call it C1, then length C1*2/3 would produce the note a musical fifth higher, G1. A fifth higher than G1 would be length C1*2/3*2/3, D2. If D2 was lowered an octave the length would be D2*2 or D1, the scale note after C1. Using this process the string lengths for all notes of the scale CDEFGABC can be calculated. This scale could then be extended into higher or lower octave by multiplying each note by 1/2 for the octave higher or by 2 for the octave lower, etc.

Musical compositions restricted to this relatively small number of notes produced mathematically pure harmonies that were very pleasing to the ear. However, when composers tried to introduce notes outside of this range like a C# or F# the music sounded harsh or dissonant when certain intervals were used. Experimentation with adjusting the ratios between notes and dividing the octave into 12 parts so that music could be played in different keys eventually led to the system called equal temperament that we use today. The most famous example is Bach's Well Tempered Clavier published in 1722.

Reconciling the tuning problem

We'll use the piano as an example. Start on the lowest C on the piano and continue up for 7 octaves until you reach the highest C on the piano. If you call the lowest frequency f, then the highest frequency is 2^7*f = 128*f.

piano8ves.png

Now do the same thing tuning by fifths, a 3:2 ratio. After 12 fifths you'll reach the B#7 that lands on the same piano key as C8. If the starting frequency is f, then the ending frequency is (3/2)^12*f = 129.75*f. According to the math B#7 is higher in frequency than C8.

piano5ths.png

The Comma Of Pythagoras

The difference by which (3/2)^12 exceeds 2^7 is known as the comma of Pythagoras.

\[{\text{ comma of Pythagoras }} = {\text{ }}\frac{{{{\left( {\frac{3}{2}} \right)}^{12}}f}}{{{2^7}f}}{\text{ = }}\frac{{129.75}}{{128}}{\text{ = 1.0136}}\]

Assume f0 is 1Hz. Then

12 Fifths 129.746
7 Octaves 128.0
Difference 1.746
Comma of Pythagoras 1.0136
B#7 4243.12
C8 4186.00
Difference 57.12
B#7/C8 ratio 1.0136
B#7 * 1/comma 4186.0

Equal Temperament

The Equal Temperament system we use today is one of many tuning systems that have tried to reconcile the pure harmonic series ratios of the Octave (2:1) and the Fifth (3:2). Every octave is contains twelve equal half steps. Every fifth contains seven equal half steps. All octaves remain perfectly in tune. All other notes are calculated using the frequency ratio between half step as done in hw735.

\[{2^{\frac{1}{{12}}}}\]

Equal Temperament in practice

One method of tuning the piano is to tune all octaves perfectly and then flatten each fifth in the cycle of fifths shown above by 1/11 of a comma. That way the cycle of fifths will end on the same frequency as the cycle of octaves. Piano tuners found that the when the interval of a fifth is lowered so that it beats 3 times every 5 seconds, that was the right amount.

On the guitar, every fret is positioned in equal temperament half steps with the 12th fret being the Octave and the 7th fret the fifth. Many guitar players use a method of tuning in harmonics where they play the harmonic on the fifth fret of a lower string and compare it to the harmonic on the seventh fret of the next higher string. If the harmonics match they think it's in tune. Mathematically it's not. The 5th fret harmonic is two octaves above the the open string and the 7th fret harmonic is one octave plus a fifth above the open string. The harmonics produce the pure Pythagorean ratios, the frets produce Equal Temperament ratios. Errors compound themselves when tuning all six strings using harmonics.

String Ensembles and Choral groups are not bound by equal temperament and often use pure ratios in their performances. Violinists and singers trained to produce pure intervals sometimes have trouble adjusting their intonation when accompanied by a piano.

Cents

The audio unit used for measuring small differences in frequency is called a cent. By definition there are 1200 cents in one octave. A half step is 100 cents. This is the general formula to find the cents difference between any two frequencies.

\[{\text{centDifference = 1200 }} \bullet {\text{ lo}}{{\text{g}}_{\text{2}}}\left( {\frac{{f1}}{{f2}}} \right)\]

Trained musicians can detect around a 3 cents difference between two tones.

The Fourier Theorem

Jean Baptiste Joseph Fourier (1768-1830) developed the Fourier Theorem, Fourier Series, and Fourier Transform during his study of heat conduction. Hundreds of books, treatises, college and graduate school mathematics, and engineering courses are devoted to the study of Fourier analysis. The basic premise of Fourier's theorem states that:

Any continuous periodic waveform can be transformed into a sum of sinusoids at integer multiples of the fundamental frequency having well chosen amplitudes and phases.

\[anywave = \sum\limits_{k = 1}^\infty {{A_k}} \sin (k\omega nT + {\theta _k});{\text{ }}\omega = 2\pi {f_0}\]

This formula says that a sawtooth wave, a square wave, a triangle wave, a piano sound, a violin sound, or any periodic sound can be computed by carefully choosing the amplitude and phase values and summing the Fourier series with a fundamental frequency of \({f_0}\).

Fourier Series Harmonics

Given a fundamental frequency \({f_0}\), the Fourier Series consists of sine wave harmonics that are integer multiples of the fundamental frequency. The fundamental frequency is harmonic number one. For example a square wave can be constructed from odd numbered harmonics whose amplitude is the reciprocal of the harmonic number.

\[SquareWave = \frac{4}{\pi }\sum\limits_{n = 1,3,5,7...}^\infty {\frac{1}{n}(\sin (2\pi } n{f_0}))\]

Aliasing

Any frequency in the signal that exceeds the Nyquist Limit (FS/2) will fold back into the output as an unwanted alias frequency. All commercial digital audio software and hardware products aggressively guard against aliasing.

The alias frequency of a square wave exceeding the Nyquist limit is shown in this short code example.

c821_SquareWaveAliasing.cpp
Try this at home.

// calcSquareWaveAliasing.cpp
#include <iostream>
#include <vector>
// Calculate alias frequency if > Nyquist Limit.
int main()
{
    const int FS = 44100;
    const int NYQUIST = FS/2;
    const int f = 1000;
    int val;
    std::vector<float> v;
    for ( int i = 1; i < 48; ++i )
    {
        val = f*i;
        if (val <= NYQUIST)
          std::cout << "f" << i << " = " << val << std::endl;
        else
        {
            val = std::abs( val - FS );
            std::cout << "f" << i << " = " << f*i << " aliased to " << val << std::endl;
        }
    }
}

Output
Note the cycle starts ascends to the Nyquist limit (22050 Hz) then descends to the sampling rate (44100 Hz) and then begins ascending again with a different starting value.

f1 = 1000
f2 = 2000
f3 = 3000
f4 = 4000
f5 = 5000
f6 = 6000
f7 = 7000
f8 = 8000
f9 = 9000
f10 = 10000
f11 = 11000
f12 = 12000
f13 = 13000
f14 = 14000
f15 = 15000
f16 = 16000
f17 = 17000
f18 = 18000
f19 = 19000
f20 = 20000
f21 = 21000
f22 = 22000
f23 = 23000 aliased to 21100
f24 = 24000 aliased to 20100
f25 = 25000 aliased to 19100
f26 = 26000 aliased to 18100
f27 = 27000 aliased to 17100
f28 = 28000 aliased to 16100
f29 = 29000 aliased to 15100
f30 = 30000 aliased to 14100
f31 = 31000 aliased to 13100
f32 = 32000 aliased to 12100
f33 = 33000 aliased to 11100
f34 = 34000 aliased to 10100
f35 = 35000 aliased to 9100
f36 = 36000 aliased to 8100
f37 = 37000 aliased to 7100
f38 = 38000 aliased to 6100
f39 = 39000 aliased to 5100
f40 = 40000 aliased to 4100
f41 = 41000 aliased to 3100
f42 = 42000 aliased to 2100
f43 = 43000 aliased to 1100
f44 = 44000 aliased to 100
// cycle starts ascending again at 900Hz
f45 = 45000 aliased to 900
f46 = 46000 aliased to 1900
f47 = 47000 aliased to 2900

We'll demonstrate aliasing effects in hw821_gensquare_phasor using a square wave that has only two values +1.0 and -1.0.

Homework 8.2

hw821_gensquare_phasor

This assignment will construct a 1000Hz square wave using a phasor. As the phasor rotates counterclockwise from 0 to π, the square wave will have a value of +1.0. As it rotates past π to 2π the phasor will have a value of -1.0. When it rotates past 2π you subtract 2π to keep it within the 0 to 2π range.

WARNING:
Turn down the volume before you run the program.
Take off headphones/earbuds.
THIS IS VERY LOUD.

Setup
mkdir $HOME312/cs312/hw82
cd $HOME312/cs312/hw82
mkdir hw821_gensquare_phasor
cd hw821_gensquare_phasor
touch hw821_gensquare_phasor.cpp
touch CMakeLists.txt
mkdir build

Open hw821_gensquare_phasor folder in vsCode

hw821_gensquare_phasor.cpp
Copy/paste

#include "sndfile.hh"
#include <iostream>
#include <vector>
#include <cmath> // for M_PI

// EITHER
typedef float MY_TYPE;
#define LIB_SF_FORMAT SF_FORMAT_FLOAT; // SF_FORMAT_MY_TYPE defiend in "sndfile.h"
// OR
// typedef double MY_TYPE;
// #define LIB_SF_FORMAT SF_FORMAT_DOUBLE; // SF_FORMAT_MY_TYPE defiend in "sndfile.h"
// END

const int FS = 44100;       // CD sample rate
const MY_TYPE T = 1.0 / FS; // sample period
const MY_TYPE k2PI = M_PI * 2.0;
const MY_TYPE k2PIT = k2PI * T;

std::vector<MY_TYPE> gen_square_phasor(MY_TYPE freq, MY_TYPE ampl, MY_TYPE secs)
{
  std::vector<MY_TYPE> v;
  MY_TYPE phzNow = 0;
  MY_TYPE phzIncr = k2PIT * freq;
  for (int n = 0; n < secs * FS; n++)
  {
    /*----------------------------------------------------
 Square wave = 1.0 if phznow <= pi
 Square wave = -1.0 if phzow > pi
 add sample to v
 increment phase now
 wrap around 2pi
 ----------------------------------------------------*/
  }
  std::cout << "You need to implement gen_square_phasor(...)\n";
  return v;
}

bool samples2wavfile(const char *fname, const std::vector<MY_TYPE> &v)
{
  // code borrowed and modified from libsndfile example sndfilehandle.cc
  // mostly from create_file(...) and format from main()
  SndfileHandle file;
  const int channels = 1;
  const int srate = FS;
  const int format = SF_FORMAT_WAV | LIB_SF_FORMAT;

  file = SndfileHandle(fname, SFM_WRITE, format, channels, srate);

  // using vector instead of buffer array
  // C++ vector elements are stored in contiguous memory, same as array
  // &v[0] is pointer to first element of vector
  file.write(&v[0], v.size());
  return true;
}

int main(int argc, char *argv[])
{
  std::vector<MY_TYPE> vsamps = gen_square_phasor(1000.0, 1.0, 1.5);
  samples2wavfile("hw821_gensquare_phasor_1000Hz.wav", vsamps);
  return 0;
}

CMakeLists.txt
Copy/paste

cmake_minimum_required(VERSION 3.5)

set(PROJECT_NAME hw821_gensquare_phasor)
project(${PROJECT_NAME})

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -Wall")

# CHANGE THE PATH TO YOUR COMMON FOLDER
set(COMMON "/Users/je/cs312/_cs312/common")
set(LSF "${COMMON}/libsndfile")
set(ULL "/usr/local/lib")

add_executable(
  ${PROJECT_NAME}
  ${LSF}/sndfile.hh
  ${PROJECT_NAME}.cpp
  )

target_include_directories(${PROJECT_NAME} PRIVATE ${COMMON} ${LSF})
target_link_libraries(${PROJECT_NAME} ${ULL}/libsndfile.1.dylib)

Build and run
A hw821_gensquare_phasor_1000Hz.wav will be created in your build folder.

WARNING:
Turn down the volume before you play this file in Audacity.
Take off headphones/earbuds.
THIS IS VERY LOUD.

Play hw821_gensquare_phasor_1000Hz.wav in Audacity.

Plot the spectrum
Sharp corners (abrupt slope changes) are produced by high frequencies. Those high frequencies show up in the spectrum.

squareSharpCorners.png

I had to widen the window to show frequencies up to the Nyquist limit, 22050 Hz. Frequencies higher than the Nyquist limit are aliased to lower frequencies and show up as intermediate peaks between the harmonic peaks. I've drawn red lines indicating where aliased frequencies are present. Aliasing occurs both above and below the fundamental frequency of 1000 Hz.

squareNonBL1000.png

Ideally we'd like the spectrum to look like this with no intermediate peaks between the harmonic peaks.

squareBL1000.png

Bandlimited waveforms

A waveform with no frequency content above the Nyquist limit is said to be bandlimited. One method of producing bandlimited waveforms is to use Fourier Series formulas to generate the waveforms and limit the number of harmonics to those below the Nyquist limit.

A 100Hz fundamental could theoretically contain 220 Fourier Series harmonics before exceeding the Nyquist limit. Whether the audible difference in using 50 or 220 harmonics at 100Hz is worth the increased computation time is debatable. Harmonic 50 would equate to 5000 Hz. The highest note on a piano is 4186 Hz. As the fundamental frequency increases past 8000Hz the third harmonic exceeds the Nyquist limit and all waveforms tend to sound like a sine wave. Bandlimited waveforms are the subject of hw822.

hw822_qtplotBLwav

BL is an abbreviation for BandLimited.

The Five Classic Synthesizer Waveforms

The five classic synthesizer waveforms were found on early electronic synthesizers around 1965 and have appeared on synthesizers ever since.

  • Sine
  • Square
  • Sawtooth up (Saw)
  • Sawtooth down
  • Triangle

The five waveforms can all be generated using the Fourier Series. Bandlimited waveforms can be generating by restricting the number of harmonics allowed based on the fundamental frequency.

In the Fourier Series formulas given below the following terms are used:

  • FS is the sampling rate
  • T is the sampling period, the inverse of the sampling rate.
  • n is an integer harmonic number
  • The Greek letter ω (omega) is commonly used to measure frequency in radians per second.
  • ω = \(2 \pi f\) Hz (cycles per second)

    Sine

A pure sine wave has exactly one harmonic, the fundamental frequency. As long as the frequency stays under the Nyquist limit a sine wave is always bandlimited.

\[\sin[n] = \sin (\omega nT + \theta ){\text{; }}\omega = 2\pi {f_0}\]

Equivalent formulas for a sine wave.

\[\sin[n] = sin(\frac{{2\pi n{f_0}}}{{FS}} + \theta ) = sin(2\pi n{f_0}T + \theta ) = sin(\frac{{\omega n}}{{FS}} + \theta ) = sin(\omega nT + \theta )\]

Often the following term is a constant and can be calculated outside of any loops.

\[\frac{{2\pi }}{{FS}} = 2\pi T\]

Saw up

\[\eqalign{ & SawtoothUp[n] = \frac{2}{\pi }\sum\limits_{k = 1}^\infty {\frac{{ - {1^{(k - 1)}}}}{k}\sin (k\omega nT)} {\text{; }}\omega = 2\pi {f_0} \cr & \cr & = \frac{2}{\pi }\left( {\sin (\omega nT) - \frac{1}{2}\sin (2\omega nT) + \frac{1}{3}\sin (3\omega nT) - \frac{1}{4}\sin (4\omega nT) + \frac{1}{5}\sin (5\omega nT)...} \right) \cr} \]

Saw down

\[\eqalign{ & SawtoothDown[n] = \frac{2}{\pi }\sum\limits_{k = 1}^\infty {\frac{1}{k}\sin (k\omega nT)} {\text{; }}\omega = 2\pi {f_0} \cr & \cr & = \frac{2}{\pi }\left( {\sin (\omega nT) + \frac{1}{2}\sin (2\omega nT) + \frac{1}{3}\sin (3\omega nT) + \frac{1}{4}\sin (4\omega nT) + \frac{1}{5}\sin (5\omega nT)...} \right) \cr} \]

Square

\[\eqalign{ & Square[n] = \frac{4}{\pi }\sum\limits_{k = 1}^\infty {\frac{{{1^{(2k - 1)}}}}{{2k - 1}}\sin ((2k - 1)\omega nT)} {\text{; }}\omega = 2\pi {f_0} \cr & \cr & = \frac{4}{\pi }\left( {\sin (\omega nT) + \frac{1}{3}\sin (3\omega nT) + \frac{1}{5}\sin (5\omega nT) + \frac{1}{7}\sin (7\omega nT) + \frac{1}{9}\sin (9\omega nT)...} \right) \cr} \]

Triangle

\[\eqalign{ & Triangle[n] = \frac{8}{{{\pi ^2}}}\sum\limits_{k = 1}^\infty {\frac{{ - {1^{(k + 1)}}}}{{{{(2k - 1)}^2}}}\sin ((2k - 1)\omega nT)} \cr & = \frac{8}{{{\pi ^2}}}\left( {\sin (\omega nT) - \frac{1}{{{3^2}}}\sin (3\omega nT) + \frac{1}{{{5^2}}}\sin (5\omega nT) - \frac{1}{{{7^2}}}\sin (7\omega nT) + \frac{1}{{{9^2}}}\sin (9\omega nT)...} \right) \cr} \]

Setup

Download this zip file

hw822_qtplotBLwav.zip

Unzip it. You should see this content.

hw822_01.png

Copy to your hw82 folder
Copy the hw822_qtplotBLwav folder into your $HOME312/cs312/hw82 folder.

Open the hw822_qtplotBLwav.pro project file in Qt Creator

Change the build folder
Change the Qt build directory to $HOME312/cs312/hw82/hw822_qtplotBLwav/build-debug
Then choose Run qmake from the Build menu.

hw822_02.png

After qmake has finished processing the file you should see this.

hw822_03.png

Run the program. You should see this.

hw822_04.png

The ui-> variable names for the layout widgets are shown below.

hw822_05.png

QCustomPlot
The customPlot widget is already setup in the GUI following the same instructions used in hw813_qtwavio_plot.

Sine and Saw Down
I've implemented the Sine and Saw Down waveform functions you. They're in the bandlimitedwavs.h and bandlimitedwavs.cpp files.

Build and run

Select Sine and click the Play button
Move the slider controls.
You should be able to see and hear the Frequency and Amplitude sliders affect the sound. The number of harmonics slider has no affect because a sine wave has no harmonics.

Select Saw Down
You should be able to see and hear all sliders affect the sound.
If you move the number of harmonics slider to the far left and then slowly move it right you should be able to see and hear the harmonics added to the waveform and the sound one by one.

Select Square
Open the Application tab at the bottom of the Qt Creator window.
You'll immediately see panel fill up with messages.

hw82208.png

The Square, Saw Up, and Triangle waveforms have been left for you to implement

Assignment 8.2

I've written most of the Qt GUI portion of the hw822_qtBLwaves project for you. You can use my code for the Sine and Saw Down waveforms as an example to implement the other three waveforms. You'll need to use the Saw Up, Square, and Triangle waveforms Fourier Series formulas on the web page.

Three functions are needed for each waveform: a RtAudio callback function, a plotting function, and a calculation function that is called by both the callback and the plotting functions. The highest harmonic number used must remain below the Nyquist frequency. The maximum number of harmonics is limited to 50.

The square, sawUp, and triangle waveforms already produce sound, it's just not the correct sound.

  • calcSquareHarmonics()
  • doSquareBLCallback()
  • doPlotSquareBL()
  • calcSawUpHarmonics()
  • doSawUpBLCallback()
  • doPlotSawUpBL()
  • calcTriangleHarmonics()
  • doTriangleBLCallback()
  • doPlotTriangleBL()
Make use of the Qt Creator debugger

Some of this code is tricky to implement. Setting breakpoints and stepping through your code line by line while examining variables is highly recommended. Think of the Debugger as a programmer's best friend.

My SawDown implementation

I've added comments in the code to help you implement the remaining waveforms.

calcSawDownHarmonics
\[{\text{SawDown}[n] = \frac{2}{\pi }\sum\limits_{k = 1}^\infty {\frac{1}{k}\sin (k\omega nT)} {\text{; }}\omega = 2\pi {f_0} }\]

// This function calculates a single sample that is a sum of integer multiples
// of the fundamental frequency using the Fourier Series formula shown above
// where n is the integer harmonic number and  count is the current number of times
// the the doSawDownBLCallback function has been called
// the sin() function always returns values in the range -1.0 to +1.0

MY_TYPE calcSawDownHarmonics( int count )
{
    MY_TYPE samp{0};
    MY_TYPE sum{0};
    std::vector<MY_TYPE> v;

    for ( int n = 1; n <= hSlider_numHarmonics; ++n )
    {
        samp = ( 1.0 / n ) * hSlider_ampl * sin( k2PIT * hSlider_freq * n * count );
        v.push_back( samp );
    }
    // add up all the harmonics and multiply by k2overPI
    sum = k2overPI * std::accumulate( v.begin(), v.end(), 0.0 );
    return sum;
}

Notes:
k2overPI and k2PIT are const declarations at the top of bandlimitedwavs.cpp.
std::accumulate() is part of the C++ <numeric> library.
Look it up online and you'll discover it can be used in several different ways.
As used above it means "sum all items from v.begin() to v.end() and add 0.0 to the result.

/doSawDownBLCallback( unsigned int nBufferFrames, MY_TYPE* buffer )/
This is called by the RtAudio callback function. It is called repeatedly as long as the RtAudio dac stream is running. It also continuously appends samples to the vsamps vector every time the callback is called.

void doSawDownBLCallback( unsigned int nBufferFrames, MY_TYPE* buffer )
{
    // this function is called repeatedly by the rtaudio callback function
    // countSawDownCallbacks is declared static so the next time it's called
    // it can remember its location for the vsamps vector

    static int countSawDownCallbacks = 1;
    MY_TYPE sum{0};

    // nBufferFrames = kRTABUFFER_SZ = numSamples in callback buffer
    unsigned int numSamplesInBuffer = nBufferFrames; // a reminder that for mono files nBufferFrames == numberSamples
    for ( unsigned int n = 0; n < numSamplesInBuffer; ++n )
    {
        // calculate the SawDown Harmonic series for this trip through the callback
        sum = calcSawDownHarmonics( countSawDownCallbacks );

        // buffer is a pointer address
        // *buffer is the value stored at that address
        // buffer++ advances the buffer pointer by the size of the objects it contains (MY_TYPE)
        *buffer = sum;
        buffer++;
        *buffer = sum;
        buffer++;

        // Other C++ functions like vectors better than buffers and pointer arithmetic
        vsamps.push_back( sum );

        // finished one trip through the callback function
        // increment countSawDownCallbacks
        countSawDownCallbacks++;
    }
}

doPlotSawDownBL()

void doPlotSawDownBL()
{
    int count = 0;
    MY_TYPE sum{0};
    for ( unsigned int n = 0; n < kRTABUFFER_SZ; ++n )
    {
        sum = calcSawDownHarmonics( count );
        plot_buffer[ n ] = sum;
        ++count;
    }
}

Notes:
This function calls the calcSawDownHarmonics() function.
It fills the plot_buffer variable instead of the vsamps vector.
It is not part of the callback function.
It only fills the plot_buffer once. The plot_buffer variable is declared in globals.h and holds the Y-axis plot data.

The doPlotSawdownBL() function is called in mainwindow.cpp.

void MainWindow::updatePlot( eWaveType e )
{
    if ( e == esine )
        doPlotSine();
    else if ( e == esquare )
        doPlotSquareBL();
    else if ( e == esawup )
        doPlotSawUpBL();
    else if ( e == esawdown )
        doPlotSawDownBL();
    else if ( e == etriangle )
        doPlotTriangleBL();

    plot();
}

Notes:
The updatePlot() function is called whenever the plot needs to be redrawn.

  • when the waveform changes
  • when the frequency changes
  • when the amplitude changes
  • when the number of harmonics change
  • the updatePlot() function is called in the GUI widget private slots functions.

I used the first code example on this page as a guide to set up the plot.

https://www.qcustomplot.com/index.php/tutorials/basicplotting

Notes for the remaining waveforms

The square and triangle waves use only odd numbered harmonics. This is a common way of testing for odd or even.

for (int i = 1; i< 20; ++i)
{
   odd == 2*i -1;
   even == 2*i;
}
Plot the wave forms

If you implemented the Square, Saw Up, and Triangle waveforms correctly you should see these waveforms using these slider settings:

Frequency 500
Amplitude 0.8
Num Harmonics 10

Sine

hw823_wavSine.png

Square

hw823_wavSquare.png

Saw Up

hw823_wavSawUp.png

Saw Down

hw823_wavSawDown.png

Triangle

hw823_wavTriangle.png

Tasks remaining to implement in mainwindow.cpp
  1. Square and Triangle waveforms only contain odd numbered harmonics.
    Number Harmonics slider should only update the plot and label_value for odd numbers.
  2. Display the amplitude dB value in the amplitude dB label.
  3. Call setNyquistWarning() when numHarmonics exceeds the Nyquist limit at the current frequency value. The Num Harmonics slider has a fixed range from 1-50 harmonics and has no effect on the sine wave. It does affect the other waveforms. Given a square wave with a fundamental frequency of 1000Hz, harmonic 23 will exceed the Nyquist limit at which point the green square next to the Num harmoncis text label must turn red.
  4. You need to implement doSave()
    Use the wavWrite() code you wrote for hw811_qtwavio
    The Save dialog should display the hw82/hw822_qtplotBLwav folder when it opens.
  5. You need to implement askSave()
    Consult the second example at https://doc.qt.io/qt-5/qmessagebox.html.

    hw822_06.png

Submission

Feel free to email jellinge@carleton.edu on any part of the homework that is unclear to you. Chances are if you have questions other students do too. I will answer those questions by email to everyone in the class. The original sender will remain anonymous. I have tried to provide clues to the homework in the web pages, reading assignments, and labs. Sometimes what seems obvious to me is not obvious to students just learning C++.

Create a folder named hwNN_LastnameFirstname_LastnameFirstname.
Substitute your name and your partner's name for LastnameFirstname_LastnameFirstname.

Remember

  • Boilerplate header at top of every file.
  • Make sure your program compiles before submitting.
  • Programs that compile and produce partially correct output will be graded.
  • You can send notes to me in your homework files by enclosing them in block comments.
  • Programs that do not compile get an automatic F (59%).
  • Submit the homework in only one of the partner's folders.
  • Empty your build or build-debug folders using the command emptyBuildFolder.sh
  • Only include .h, .cpp, Makefiles, CMakeLists.txt
  • Do not include these folders/files
    .git
    .vscode
  • Double check for the above two folders using this command

    ls -a hwNN_LastnameFirstname1_LastnameFirstname2
    # if either .git or .vscode exist in the folder you're submitting remove them with
    # rm -fR .git
    # rm -fR .vscode
    

Hand-in folder contents

/common
├── RtAudio
│   ├── RtAudio.cpp
│   └── RtAudio.h
├── RtMidi
│   ├── RtMidi.cpp
│   └── RtMidi.h
├── hw332_CMidiPacket.cpp
├── hw332_CMidiPacket.h
├── hw411_rand_int.cpp
├── hw411_rand_int.h
├── hw421_CDelayMs.cpp
├── hw421_CDelayMs.h
├── hw422_CAppleMidiSynth.cpp
├── hw422_CAppleMidiSynth.h
├── hw423_CMidiTrack.cpp
├── hw423_CMidiTrack.h
├── hw511_CInstrument.cpp
├── hw511_CInstrument.h
├── libsndfile
│   ├── sndfile.h
│   └── sndfile.hh
└── qcustomplot
    ├── qcustomplot.cpp
    └── qcustomplot.h

/hw82
├── hw821_gensquare_phasor
│   ├── CMakeLists.txt
│   ├── build
│   └── hw821_gensquare_phasor.cpp
└── hw822_qtplotBLwav
    ├── bandlimitedwavs.cpp
    ├── bandlimitedwavs.h
    ├── build-debug
    ├── globals.cpp
    ├── globals.h
    ├── hw822_qtplotBLwav.pro
    ├── main.cpp
    ├── mainwindow.cpp
    ├── mainwindow.h
    ├── mainwindow.ui
    ├── rtaudioutils.cpp
    ├── rtaudioutils.h
    ├── sawdown.wav
    ├── wavReadWrite.cpp
    └── wavReadWrite.h

Author: John Ellinger

Created: 2020-02-26 Wed 12:48

Validate