CS 312 - Week 7 Class 2
CS 312 Audio Programming Winter 2020

Table of Contents


ANNOUNCEMENTS
I spent Thursday morning in CMC lab with Mike Tie to resolve the libsndfile link issues with hw72. It's fixed and now working in CMC. I've greatly simplified the steps to install libsndfile and revised the web page.

A new version of Qt has been pushed to WCC along with the same libsndfile changes. WCC is in use until 5:00pm today and I have not been able to test yet. I am pretty confident that the hw72 stuff in WCC will work as described below.
Important for WCC
You must login once, then logout, then login again to have the new changes pushed to your computer.

Follow the libsndfile steps described below. Once the new common/libsndfile folder is in place the changes should only affect the CMakeLists.txt file and not any code you've already written. You will have to replace the hw721 CMakeLists.txt file. All other hw72 CMakeLists.txt files should only require a set(PROJECT_NAME hw721_gensin) change.

7.2 Class

Our coverage of digital audio and digital synthesis begins today. All hw72 homework will be done in VScode terminal. We'll get back to using Qt in hw73.

We'll begin by installing the open source libsndfile library that enables reading and writing digital audio in many formats. We'll use the .wav file format. Why .wav and not .mp3? In short, wav is a lossless format while mp3 is a lossy format. MP3 files have become the common method of delivery over the internet because of their small size. MP3 files can be as much as one tenth the size of the WAV file. However that comes at a loss of quality.
https://www.dawsons.co.uk/blog/how-do-mp3-and-wav-files-differ

libsndfile

Click to download this file. It contains two files: sndfile.h and sndfile.hh. The C version uses .h, the C++ version uses .hh.
libsndfile_files_for_common_folder.zip

Important
If you already have a common/libsndfile folder delete it.
Then execute these commands.

# DOWNLOAD libsndfile above first
cd $HOME312/common
mkdir libsndfile

Unzip the file you downloaded and copy these two files into your common/libsndfile folder.

  • sndfile.h
  • sndfile.hh

Audacity sound editor

Audacity is a free open source audio editor with many tools and effects for modifying/analyzing sound files.
Its installed in both labs. If you want to use it on your personal computer the link is: https://www.audacityteam.org/download/

Open Audacity

AudacityIcon.png

We're going to create two sine wave tones at a frequency of 440Hz, the standard orchestra reference pitch.

Choose Tone from the Generate menu.

aud001.png

Make these settings. The duration is 250 milliseconds.

aud002.png

The waveform is displayed.

aud003.png

Choose Export/Export as WAV from the File menu.

aud006.png

Save it in your $HOME312/cs312 folder using the name shown below.

aud007.png

Close that track by clicking the X in the upper right corner of the track.

aud003a.png

Create a new sine tone at amplitude 0.5.

aud008.png

Export and save it in your $HOME312/cs312 folder using the name shown below.

aud009.png

You can see the amplitude scale is at the 0.5 level.

aud010.png

Close this track.

Open hw721_a440sine_amp1.wav from the Audacity File menu.

Audacity Waveform Tools

aud004.png

  • 1 Zoom in
  • 2 Zoom out
  • 3 Zoom in to selection
  • 4 Fit entire waveform in window
  • 5 Fit entire waveform in left quarter of window

Audacity Play/Edit Tools

aud004a.png

Use the Zoom In tool to view a couple periods of the sine wave.

aud01.png

Zoom in further until you can see the samples for one complete period.
Use the Select too to select one period of samples.
You should see 100/101 samples depending on the start and end points of your selection.

aud005.png

Sample rate and bit depth

You can see on the left in the above picture some information about the file.

  • It's mono
  • The sample rate was 44,100 Hz
  • The bit depth was 32 bit floating point.

That means every second of sound contains 44,100 samples. Each sample has an amplitude range from -1.0 to +1.0 in 32 bit floating point values. The 44100 Hz sample rate was the used as the audio CD standard. For stereo recording you need 88,200 samples per second. Today's recording equipment can use sample rates of 48000 Hz, 96000 Hz, up to 192000 Hz.

It's an amazing process when you think of electronics behind the recording process where air pressure acts on the diaphragm of a microphone creating minute variations in movement that are amplified and turned into one second of digital audio.

Read this for more information.
DigitalAudioFundamentals.pdf

Period and Frequency

The period T (time) is the duration of one complete cycle of the waveform measured in seconds. The frequency F (cycles per second) is measured in Hertz (Hz). 1Hz is one cycle per second. The formula relating the two is shown below.

  • nSamp is the number of samples.
  • FS is the sample rate or Frequency of Samples, 44100 Hz

\[T{\rm{ = }}nSamp{\rm{*}}\frac{1}{{FS}}\]

One period of this sine wave is 100 samples.

\[T = \frac{{100}}{{FS}} = {\rm{0.0022676 sec}}\]

Calculate the frequency

The frequency of for one period of N samples at a sample rate of FS can be calculated using this formula.

\[f = \frac{{FS}}{N} = \frac{{44100}}{{100}} = 441Hz\]

Using an integer number of samples at a fixed sample rate we can only estimate frequency. 100 samples is too high for 440Hz and 101 samples is too low.

\[f = \frac{{44100}}{{100}} = 441Hz\]

\[f = \frac{{44100}}{{101}} = 436.63Hz\]

The frequency of this waveform actually is 440Hz which means its period would have to be 100.23 samples.

\[\frac{{44100}}{{400}}{\rm{ = 100.23}}\]

A common method of computing frequency requires fractional samples and uses a technique known as phase increment. It's based on the idea that a sine wave is equivalent to a phasor rotating counterclockwise around the origin of the unit circle. That's the subject of hw722.

Read this for more information.
Phasors.pdf

Time domain and frequency domain

A time series of waveform samples can be be viewed in two equivalent domains. The mathematical operation that is used to translate between the time and frequency domains is known as the Discrete Fourier Transform. The time domain view is commonly known as a time series. The frequency domain view is known as a spectrum.

Time domain
Amplitude vs. time
Frequency domain
Magnitude vs. frequency

Nyquist theorem

A fundamental theorem of Digital Signal Processing (DSP) known as the Nyquist Theorem or Sampling Theorem states that no information is lost between the time and frequency domains as long as the sample rate is at least twice the frequency of the highest frequency found in the signal. The sampling rate of 44,100Hz was chosen in part because the upper range of human hearing is around 20,000Hz and decreases with age.

Nyquist Rate

The Nyquist rate or Nyquist limit is defined as half the sample rate. All digital recording hardware pre-filters the signal to ensure no frequecies higher than the Nyquist Rate are passed to the analog to digital converter (ADC).

Frequency spectrum

Audacity uses the Fast Fourier Transform algorithm (FFT) to plot the spectrum of a sampled waveform. The FFT is a computationally fast implementation of the mathematical Discrete Fourier Transform (DFT).

View the hw721_a440sine_amp1.wav spectrum

Assuming the the hw721_a440sine_amp1.wav window is open, select the entire waveform using Command-click in the left panel shown by the red rectangle.

aud03a.png

Choose Plot Spectrum from the Analyze menu.

aud03b.png

Position the cursor on the highest peak and read the Peak frequency and amplitude. You may have to modify your settings to those shown in the picture below.

aud04.png

The frequency is 440 Hz and the amplitude is 0.1dB (decibels).

Open the hw721_a440sine_amp1half.wav spectrum

Follow the same steps as above.

  • Select the entire waveform using Command-click
  • Choose Plot Spectrum from the Analyze menu.
  • Position the cursor on the highest peak and read the Peak frequency 440 Hz (A4).
  • The frequency is 440Hz as before but the amplitude is -6.1dB.

    aud04b.png

Decibels and amplitude

A decibel is a base 10 logarithmic ratio between two numbers. Because the two numbers in the ratio have the same units the ratio is dimensionless. In digital audio 0dB is the loudest sound that avoids clipping. All other audio dB values are negative.

The decibel is defined as

\[dB = 20\log \left( {{V \over {{V_{ref}}}}} \right)\]

In digital audio the number 1.0 is chosen as Vref. It's the maximum floating point sample value for amplitude. I used V in the formula because decibels originally referred to the ratio of voltages. In our two example wave files one is at full amplitude and the other at half amplitude. Their decibel ratio of one half is -6.02dB. The B in dB is capitalized for Alexander Graham Bell similar to the H in Hz for Heinrich Hertz.

\[20{\log _{10}}(0.5) = - 6.02\]

A 6dB drop is equivalent to halving the amplitude. That's why hw712_a440sine_half_amp is -6.1dB lower than hw712_a440sine_amp1.

You can use this popup menu in Audacity to view the waveforms on a decibel scale.

aud011.png

Start on homework.

Reading/Viewing

After class
Read and watch these items.

wav or mp3
https://www.dawsons.co.uk/blog/how-do-mp3-and-wav-files-differ

PDF
SixPropertiesOfSound.pdf
Digital Audio Fundamentals
Phasors

Video
You can end at 38:30. Rest of the video is optional.
https://www.youtube.com/watch?v=jNSiZqSQis4

Additional References
This is not required reading but there's more in depth information here if you want to pursue FM synthesis on your own.

Chowning: FM Theory & Applications By Musicians for Musicians.pdf

Also google "fm synthesis algorithm".

Homework 7.2

All homework for 7.2 is done in vsCode.

hw721_gensin

Generate a sine wave with user parameters for frequency, amplitude, and duration.

Setup

cd $HOME312/cs312
mkdir hw72
mkdir  hw72/
cd hw72/hw721_gensin
mkdir build
touch CMakeLists.txt
touch hw721_gensin.cpp
code .

Copy this code into CMakeLists.txt

cmake_minimum_required(VERSION 3.5)

set(PROJECT_NAME hw721_gensin)
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 "<your_full_pathname_to_course_common_folder>")
set(LIBSF "${COMMON}/libsndfile")
set(ULL "/usr/local/lib")

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

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

Copy this code into hw721_gensin.cpp

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

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

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

std::vector<MY_TYPE> gen_sin(MY_TYPE freq, MY_TYPE ampl, MY_TYPE secs)
{
  std::vector<MY_TYPE> v;
  std::cout << "You need to implement gen_sin(...)\n";
  /*----------------------------------------------------
    // FORMULA: samp(n) = A * sin( 2 * pi * freq * n * T);
    // calculute 2*pi*T outside of the for loop for efficiency
    // FORMULA: samp(n) = A * sin( k2PIT* freq * 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[])
{
  // generate a 440 Hz, full 1.0 amplitude sine wave for one second
  std::vector<MY_TYPE> vsamps = gen_sin(440.0, 1.0, 1.0);
  samples2wavfile("hw721_a440sine_amp1.wav", vsamps);

  // generate a 440 Hz, half 0.5 amplitude sine wave for one second
  vsamps.clear();
  vsamps = gen_sin(440.0, 0.5, 1.0);
  samples2wavfile("hw721_a440sine_amp1half.wav", vsamps);

  return 0;
}

Notes
You may get warnings about const variables not used. You'll use them to complete the code.
The typedef and #define statements near the beginning let you choose between using float32 (float) or float64 (double) types.
Most modern computers use 64 bit CPUs and contain 64 bit floating point processors.
High end DSP filters and effects might use float64 for more accurate internal computation but later output float32 audio.
Float32 is slightly faster.
Some 3rd party libraries choose either float32 or float64 and using the native library format will eliminate type narrowing warnings.

Common abbreviations is DSP code

NAME SYMBOL ALTERNATE
Sample Rate FS (Frequency of Samples) SR
Period T (time), 1/FS P
Frequency f frq, freq
Number of Samples N nSamp
Radian/Angular Frequency ω w, 2 π f
π M_PI in <cmath> kPI
2 π 2*M_PI k2PI
2 π T, 2 π/FS 2*M_PI*T, 2*M_PI/FS k2PIT, k2PIoverFS

Build

cd $HOME312/cs312/hw72/hw721_gensine/build
# build
cmake .. && make

Run Make sure you've implemented the gen_sin() function.

# run and play
# cd build
./hw721_gensin

After running the program you'll see two WAV files appear in the build folder. 0pen and view the files in Audacity. Plot the spectrum. Verify results. They should be the same as the files Audacity generated, except for the duration.

hw722_gensin_phasor

Phasors

See Fig. 2 and 3 at https://en.wikipedia.org/wiki/Phasor for an animated demonstration of phasor/sine wave equivalence.

Read this for more information.
Phasors.pdf

Setup

cd $HOME312/cs312/hw72
cp -R hw721_gensin/ hw722_gensin_phasor
cd hw722_gensin_phasor
mv hw721_gensin.cpp hw722_gensin_phasor.cpp
sed -i '' s/hw721_gensin/hw722_gensin_phasor/g CMakeLists.txt

In hw721_gensin we noted that the period of a 440Hz sine wave was 100.23 samples at a sampling rate of 44100Hz. Here are formulas to convert from waveform periods in samples to waveform periods in radians.

\[\eqalign{ & 1Hz = 2\pi\ {\rm{\ radians/second}} \cr & freq = 2\pi f\ {\rm{ radians/second}} \cr & {\rm{phase\ increment = }}{{2\pi f{\rm{ radians}}} \over {\sec }} \cdot {{1\sec } \over {44100{\rm{ samples}}}} = {{2\pi f} \over {FS}}{\rm{ radians/sample}} \cr} \]

CMakeLists.txt should be ready.

Copy this code into hw722_gensin_phasor.cpp

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

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

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_sin_phasor(MY_TYPE freq, MY_TYPE ampl, MY_TYPE secs)
{
  std::vector<MY_TYPE> v;
  std::cout << "You need to implement gen_sin_phasor(...)\n";
  /*----------------------------------------------------
  declare variable MY_TYPE phzNow
  declare variable MY_TYPE phzIncr
  set phzNow to zero
  calculate the phase increment in radians for freq
  BEGIN_LOOP from 0 to number of samples in secs paramater
  push ampl*sin(phzNow) to vector v
  increment phzNow
  check if phzNow greater than 2pi
  if so wrap around (phzNow = phzNow - 2pi)
  END_LOOP
  ----------------------------------------------------*/
  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_sin_phasor(440.0, 1.0, 1.5);
  samples2wavfile("hw722_a440sine_phasor.wav", vsamps);
  return 0;
}

Build

cd $HOME312/cs312/hw72/hw722_gensine_phasor/build
# emptyBuildFolder.sh
# build
cmake .. && make

You'll get a message to implement the gen_sin_phasor(…) function. The k2PIoverFS warning should disappear after you implement the gen_sin_phasor(…) function. When the program builds and runs.

Open and view the hw722_a440sine_phasor.wav file in Audacity. Plot the spectrum. Verify results.

  • If you don't see a sine wave a your gen_sin_phasor() function is not correct.
  • If you plot the spectrum and the peak frequency is not 440Hz your gen_sin_phasor() function is not correct.

hw723_gensin_beatDemo

Adding two waveforms is as simple as adding their samples, taking care that the result stays within the -1.0 to +1.0 range.

Beats and tuning

Two sinewaves (musical notes) are said to be "in tune" when there are no perceptible "beat" frequencies. The greater the difference between the two frequencies, the faster the beats. Here's a demo.

Setup

cd $HOME312/cs312/hw72
cp -R hw722_gensin_phasor/ hw723_gensin_beatDemo
cd hw723_gensin_beatDemo
mv hw722_gensin_phasor.cpp hw723_gensin_beatDemo.cpp
sed -i '' s/hw722_gensin_phasor/hw723_gensin_beatDemo/g CMakeLists.txt
sed -i '' s/hw722_gensin_phasor.wav/hw723_gensin_beatDemo.wav/g hw723_gensin_beatDemo.cpp
emptyBuildFolder.sh

Replace main() with this.

int main(int argc, char *argv[])
{
  // beats
  std::vector<MY_TYPE> v1 = gen_sin_phasor(220.0, 0.5, 5.0); // note the sum of the two amplitudes are <= 1.0
  std::vector<MY_TYPE> v2 = gen_sin_phasor(220.5, 0.5, 5.0);
  std::vector<MY_TYPE> vout;
  for (auto n = 0; n != v1.size(); ++n)
    vout.push_back(v1.at(n) + v2.at(n));
  samples2wavfile("hw723_a220sine_beats.wav", vout);

  // reset for no beats
  v1 = gen_sin_phasor(220.0, 0.5, 2.5); // note the sum of the two amplitudes are <= 1.0
  v2 = gen_sin_phasor(220, 0.5, 2.5);
  vout.clear();
  for (auto n = 0; n != v1.size(); ++n)
    vout.push_back(v1.at(n) + v2.at(n));
  samples2wavfile("hw723_a220sine_nobeats.wav", vout);

  return 0;
}

Build and run

# cd hw723_gensin_beatDemo/build
cd build
emptyBuildFolder.sh
cmake .. && make && ./hw723_gensin_beatDemo

Open and play in Audacity.

Beats
aud012.png

Modulation

Modulation is the process of varying one or more properties of a periodic waveform by another waveform called the modulator. Modulation requires two waveforms called the modulating wave and the carrier wave. The carrier wave is modulated (modified) by the modulator wave. Typically the carrier wave is the higher frequency. A musician modulates a tone (a periodic waveform) on a musical instrument through touch and pressure. Digital modulation is sample by sample multiplication.

Bipolar and Unipolar waveforms

The modulating wave can be either bipolar or unipolar.

  • Bipolar waveform amplitudes pass above and below 0 on the y axis.
  • Unipolar waveform amplitudes are never negative.

We'll briefly look at three forms of modulation.

Ring modulation
multiplication of two bipolar waveforms sample by sample.
Amplitude modulation
multiplying the amplitude of a waveform by a second waveform.
Frequency/Phase modulation
modifying the phase of a waveform by a second waveform.

hw724_gensin_ringmod

Ring Modulation is the simplest form of modulation. It multiplies two bipolar signals sample by sample. If the two signals are sinusoidal, the output of the ring modulator would be the sum and difference of the two frequencies. The fundamental frequencies of the two sinusoids will disappear. This corresponds to the trigonometric identity for multiplying two sine or cosine waves.

\[\sin A*\sin B = \frac{1}{2}\left[ {\cos \left( {A - B} \right) + {\mathop{\rm Cos}\nolimits} (A + B)} \right]\]

Setup

cd $HOME312/cs312/hw72
cp -R hw722_gensin_phasor/ hw724_gensin_ringmod
cd hw724_gensin_ringmod
mv hw722_gensin_phasor.cpp hw724_gensin_ringmod.cpp
sed -i '' s/hw722_gensin_phasor/hw724_gensin_ringmod/g CMakeLists.txt
sed -i '' s/hw723_gensin_beatDemo.wav/hw724_gensin_ringmod.wav/g hw724_gensin_ringmod.cpp
emptyBuildFolder.sh

Replace the main() function with this.

int main(int argc, char *argv[])
{
/*----------------------------------------------
modulating sine wave at 400 Hz, ampl 1.0, duration 1.5 seconds
carrier sine wave at 700 Hz, ampl 1.0, duration 1.5 seconds
output wave file name: "hw724_gensin_ringmod.wav"
----------------------------------------------*/
  std::cout << "You need to implement ring modulation in main()\n";
  return 0;
}

Build and run

cd ~/cs312/hw72/hw724_gensin_ringmod/build
# emptyBuildFolder.sh
cmake .. && make

Open hw724_gensin_ringmod.wav in Audacity.

Time domain
rm01.png

Frequency domain

rm02.png

The spectrum shows the sum and difference of the two frequencies. Neither original frequency is present in the output.

Sine wave Frequency Hz
A 400
B 700
A-B 300
A+B 1100

Amplitude Modulation

Amplitude Modulation (AM) modulates the amplitude of the carrier wave by the modulating wave. It's the principle used in AM radio. In audio it's called AM synthesis. The general formula for an AM waveform is shown below.

\[\eqalign{ & {A_m} = 2\pi {f_m}T \cr & {F_c} = 2\pi {f_c}T \cr & M \in [0,1]{\text{ is the modulation index}} \cr & samp(n) = [1 + M*\sin ({A_m}*n)]*{A_c}\sin ({F_c}*n) \cr} \]

Notes:

  • f_sub_m is the modulation frequency
  • f_sub_c is the carrier frequency
  • A_sub_m and F_sub_c are constants that can be calculated outside of any loop.
  • The site below limits M to between 0 and 1. Other sites say between -1 and +1. Still others allow values above and below plus/minus 1.

One of the best sources available for audio DSP of all types is

https://ccrma.stanford.edu/~jos/mdft/Sinusoidal_Amplitude_Modulation_AM.html

hw725_gensin_ammod

Setup

Execute these commands in Terminal.

cd $HOME312/cs312/hw72
cp -r hw724_gensin_ringmod hw725_gensin_ammod
cd hw725_gensin_ammod
mv hw724_gensin_ringmod.cpp hw725_gensin_ammod.cpp
sed -i '' s/hw724_gensin_ringmod/hw725_gensin_ammod/g CMakeLists.txt
sed -i '' s/hw724_gensin_ringmod.wav/hw725_gensin_ammod.wav/g hw725_gensin_ammod.cpp
emptyBuildFolder.sh

CMakeLists.txt
Should already configured to compile hw725_gensin_ammod.

hw725_gensin_ammod.cpp
Add a new function am_modulation(…)

// Fc carrierFrequncy, Ac carrierAmplitude,
// Fm modulationFrequency, Am modulationAmplitude,
// M modulation Index, secs duration in seconds
std::vector<MY_TYPE> am_modulation(MY_TYPE Fc, MY_TYPE Ac,
                                   MY_TYPE Fm, MY_TYPE Am,
                                   MY_TYPE M, MY_TYPE secs)
{
  std::vector<MY_TYPE> v;
  const MY_TYPE kAM = k2PIT * Fm;
  const MY_TYPE kFC = k2PIT * Fc;

  /*----------------------------------------------------
   implement the FM formula on the class web page
   kAM = 2*pi*T*Fm is a constant that can be pre-calculated outside of the loop
   kFC = 2*pi*T*Fc is a constant that can be pre-calculated outside of the loop

  loop for samples 0 to total number of samples
       samp = (1 + M * sin(kAM * n)) * Ac * sin(kFC * n);
       vout.push_back(samp);
  end loop
  ----------------------------------------------------*/
  std::cout << "You need to implement std::vector<float> am_modulation(...)\n";
  return v;
}

Replace main() with this code.

int main(int argc, char *argv[])
{
  std::vector<MY_TYPE> vout;
  // params are
  // carrierFreq, carrierAmplitude, modulatorFreq, modulatorAmplitude, modulation Index, duration
  vout = am_modulation(400, 0.4, 97, 0.4, 1.25, 2.5);
  if (!samples2wavfile("hw725_m_test.wav", vout))
    std::cout << "Could not write am_test.wav";

  /*----------------------------------------------------
   These are the settings I used to generate the over and
   under modulated figures on the web page.
   Once you get it working try other settings.
  ----------------------------------------------------*/
  vout.clear();
  vout = am_modulation(700, 0.3, 7, 0.5, -0.5, 2.5);
  if (!samples2wavfile("hw725_am_undermod.wav", vout))
    std::cout << "Could not write am_undermod.wav";

  vout.clear();
  vout = am_modulation(700, 0.2, 7, 0.2, 3, 2.5);
  if (!samples2wavfile("hw725_am_overmod.wav", vout))
    std::cout << "Could not write am_overmod.wav";

  return 0;
}

Build and run

cmake .. && make && ./hw725_gensin_ammod

Open the three wav files in Audacity. Compare your waveforms with the ones below.

am_test

am_test.png

Under modulated

am_underMod.png

Over modulated

am_overMod.png

Try variations

Test variations in mod amplitude and frequency.

  • am_modulation(700, 0.5, 400, 0.5, .57, 2.5)
  • am_modulation(400, 0.5, 3.0, 0.8, -1.0, 2.5)
  • am_modulation(400, 0.5, 25.0, 0.8, 1.25, 2.5)
  • etc.

Frequency Modulation

History
From: https://en.wikipedia.org/wiki/Frequency_modulation_synthesis

The technique of the digital implementation of frequency modulation was
developed by John Chowning...at Stanford University in 1967–68 and
patented in 1975. Prior to that, the FM synthesis algorithm was licensed
to Japanese company Yamaha in 1973.[1] It was initially designed for
radios to transmit voice by modulating one waveform's frequency with
another's. This is why FM radio is called FM (frequency modulation).
...
With the expiration of the Stanford University FM patent in 1995,
digital FM synthesis can now be implemented freely by other
manufacturers. The FM synthesis patent brought Stanford $20 million
before it expired, making it (in 1994) "the second most lucrative
licensing agreement in Stanford's history".

This is Chowning's 1973 paper. Many of the examples in hw726_gensin_fmmod come from that paper.

https://ccrma.stanford.edu/sites/default/files/user/jc/fm_synthesispaper-2.pdf

If you modulate the frequency of a carrier waveform, by a modulation waveform at radio frequencies the technique is called Frequency Modulation (FM). Using audio frequencies the technique is called FM synthesis. Chowning's general formula for a FM waveform is shown below.

\[\eqalign{ & sample(n) = A\sin \left[ {2\pi {f_c}nT + I\sin (2\pi {f_m}nT)} \right] \cr & {\text{A = amplitude}} \cr & {{\text{f}}_{\text{c}}}{\text{ = carrier}}\;{\text{frequency}} \cr & {{\text{f}}_{\text{m}}}{\text{ = modulation}}\;{\text{frequency}} \cr & {\text{n = sample}}\;{\text{number}} \cr & {\text{T = }}\frac{1}{{SampleRate}} \cr & {\text{I = modulation}}\;{\text{index}} \cr & {\text{I = }}\frac{{\Delta {f_m}}}{{{f_m}}} \cr & \Delta {f_m}\;{\text{ = frequency}}\;{\text{deviation}}\;{\text{from}}\;{{\text{f}}_{\text{m}}} \cr & \Delta {f_m} \geqslant 0 \cr & \Delta {f_m} \leqslant {f_m} \leqslant \Delta {f_m} \cr} \]

hw726_gensin_fmmod

Setup

cd $HOME312/cs312/hw72
cp -r hw725_gensin_ammod hw726_gensin_fmmod
cd hw726_gensin_fmmod
mv hw725_gensin_ammod.cpp hw726_gensin_fmmod.cpp
sed -i '' s/hw725_gensin_ammod/hw726_gensin_fmmod/g CMakeLists.txt
emptyBuildFolder.sh

CMakeLists.txt
Should already configured to compile hw726_gensin_fmmod. Here it is for reference.

hw726_gensin_fmmod.cpp
Copy/paste and implement.

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

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

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_sin_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++)
  {
    v.push_back(ampl * sin(phzNow));
    phzNow += phzIncr;
    // wrap around 2pi
    if (phzNow > k2PI)
      phzNow -= k2PI;
  }
  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;
}

std::vector<MY_TYPE> fm_modulation(MY_TYPE A, MY_TYPE Fc, MY_TYPE Fm, MY_TYPE dFm, MY_TYPE secs)
{
// Function Parameters
// A:     amplitude
// Fc:    carrierFrequency
// Fm:    modulationFrequency
// secs:  duration in floating point seconds
// dFm:   deltaFm

// Variables/constants needed
// samp:  a single sample value
// n:     sample number as in samp[n]
// Fs:    sampling frequency 44100
// T:     sampling period 1/FS
// I:     modulationIndex

std::vector<MY_TYPE> vout;
  /*---------------------------------------------------
   implement the FM formula on the class web page
   carF = 2*pi*Fc*T is a constant that can be pre-calculated outside of the loop
   carM = 2*pi*Fm*T is a constant that can be pre-calculated outside of the loop

  loop for samples 0 to total number of samples
       samp = A * sin( carF * n + I* sin( carM * n ) );
       vout.push_back(samp);
  end loop
  ----------------------------------------------------*/
  std::cout << "You need to implement fm_modulation(...)\n";
  return vout;
}

void write_fm_file(const std::string &fname, const std::vector<MY_TYPE> &v)
{
  if (!samples2wavfile(fname.c_str(), v))
    std::cout << fname << std::endl;
}

int main(int argc, char *argv[])
{
  // DO NOT MODIFY
  /*--------------------------------------------------
      Chowning examples without Envelopes
      Parameters on class web page
      Chowning used A=1000 (amplitude) but did not indicate units
      Test with one example at a time until you get all to work
      Compare your results with mp3 files on web page.
    --------------------------------------------------*/
  MY_TYPE A = 1.0;
  std::vector<MY_TYPE> vout = fm_modulation(A, 100, 100, 400, 1.5);
  write_fm_file("Example1.wav", vout);

//  vout.clear();
//  vout = fm_modulation(A, 440, 440, 2200, 1.5);
//  write_fm_file("Example2.wav", vout);

//  vout = fm_modulation(A, 900, 300, 600, 1.5);
//  write_fm_file("Example3a.wav", vout);

//  vout = fm_modulation(A, 500, 100, 150, 1.5);
//  write_fm_file("Example3b.wav", vout);

//  vout.clear();
//  vout = fm_modulation(A, 900, 600, 2400, 1.5);
//  write_fm_file("Example3c.wav", vout);

//  vout.clear();
//  vout = fm_modulation(A, 200, 280, 2800, 6);
//  write_fm_file("Example4a.wav", vout);

//  vout.clear();
//  vout = fm_modulation(A, 200, 280, 560, 0.2);
//  write_fm_file("Example4b.wav", vout);

//  vout.clear();
//  vout = fm_modulation(A, 80, 55, 375, 2.0);
//  write_fm_file("Example4c.wav", vout);

  /*--------------------------------------------------
      JE examples
    --------------------------------------------------*/
//  vout.clear();
//  vout = fm_modulation(0.75, 549, 387, 300, 1.5);
//  write_fm_file("hw725_fm_bell.wav", vout);

//  vout.clear();
//  vout = fm_modulation(0.75, 440, 3.0, 3.0, 3.0);
//  write_fm_file("hw725_fm_vibrato.wav", vout);

//  vout.clear();
//  vout = fm_modulation(0.75, 440, 10, 10 * 20, 2.0);
//  write_fm_file("hw725_fm_raygun.wav", vout);


  /*--------------------------------------------------
     Create four of your own fm modulation examples below
    --------------------------------------------------*/

  return 0;
}

Note:
An important synthesis function is missing which is missing when you listen to these examples, namely an Envelope. We'll look at envelopes in a future lab. Here's an example of my fm_bell with and without an envelope applied to the samples.

No envelope
hw726_fm_bell.wav

fmbell_noenv.png

With exponential decay envelope
hw818_fm_bell_env.mp3

fmbell_env.png

Build and run

These examples are directly from the Chowning paper above.
P4-P8 are the parameters Chowning using in the paper.
A, Fc, Fm, dFm are the equivalent parameters used in the homework.
dFm (delta FM) is the numerator of the Modulation Index I.

Example 1 p.3

P4 P5 P6 P7_P8 P7_P8
A Fc Fm dFm I=dFm/Fm
1.0 100 100 400 4

Example1.wav

Waveform

fm101.png

Spectrum
Compare with Figure 4c, p.3

fm102.png

Brass-like Tones

Chowning states on p.7 that

1) The frequencies in the spectrum are in the harmonic series
2) Both odd and even numbered harmonics are at some times present

Example 2

P5 P4 P6 (P4) P7_P8 P7_P8
Fc Ac Fm Am dFm I=dFm/Fm
440 1.0 440 (Ac) 2200 5

Example2.wav

Spectrum

fm202.png

Woodwind-like Tones

Chowning states on p.7 that

1) The frequencies in the spectrum are in the harmonic
series and for some woodwind tones are predominantly
odd numbered harmonics
Example 3a
P5 P4 P6 (P4) P7_P8 P7_P8
Fc Ac Fm Am dFm I=dFm/Fm
900 1.0 300 (Ac) 600 2

Example3a.wav

Spectrum

fm302a.png

Example 3b (bassoon-like)
P5 P4 P6 (P4) P7_P8 P7_P8
Fc Ac Fm Am dFm I=dFm/Fm
500 1.0 100 (Ac) 150 1.5

Example3b.wav

Spectrum

fm302b.png

Example 3c (clarinet-like)
P5 P4 P6 (P4) P7_P8 P7_P8
Fc Ac Fm Am dFm I=dFm/Fm
900 1.0 600 (Ac) 2400 (1200) 4 (2)

Example3c.wav

Spectrum

fm302c.png

Percussive Sounds

Example 4a Bell-like
P5 P4 P6 (P4) P7_P8 P7_P8 P3
Fc Ac Fm Am dFm I=dFm/Fm Secs
200 1.0 280 (Ac) 2800 10 15

Example4a.wav

Spectrum

fm402a.png

Example 4b Drum
P5 P4 P6 (P4) P7_P8 P7_P8 P3
Fc Ac Fm Am dFm I=dFm/Fm Secs
200 1.0 280 (Ac) 560 2 0.2

Example4b.wav

Spectrum

fm402b.png

Example 4c Wood Drum
P5 P4 P6 (P4) P7_P8 P7_P8 P3
Fc Ac Fm Am dFm I=dFm/Fm Secs
80 1.0 55 (Ac) 1375 25 2

Example4c.wav

Spectrum

fm402c.png

Assignment 726

Create four of your own examples below the code section

/*--------------------------------------------------
   Create four of your own fm modulation examples below
  --------------------------------------------------*/

Submission Format

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

$HOME312/common
├── 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

$HOME312/cs312/hw72
├── hw721_gensin
│   ├── CMakeLists.txt
│   ├── build
│   └── hw721_gensin.cpp
├── hw722_gensin_phasor
│   ├── CMakeLists.txt
│   ├── build
│   └── hw722_gensin_phasor.cpp
├── hw723_gensin_beatDemo
│   ├── CMakeLists.txt
│   ├── build
│   └── hw723_gensin_beatDemo.cpp
├── hw724_gensin_ringmod
│   ├── CMakeLists.txt
│   ├── build
│   └── hw724_gensin_ringmod.cpp
├── hw725_gensin_ammod
│   ├── CMakeLists.txt
│   ├── build
│   └── hw725_gensin_ammod.cpp
└── hw726_gensin_fmmodJE
    ├── CMakeLists.txt
    ├── build
    └── hw726_gensin_fmmod.cpp

Author: John Ellinger

Created: 2020-02-21 Fri 22:19

Validate