CS 312 - Week 3.3
CS 312 Audio Programming Winter 2020

Table of Contents

3.3 Class

Setup

Mount your course folder using the Go menu in Mac Finder

smb://courses.ads.carleton.edu/COURSES/cs312-00-w20

Execute in Mac Terminal

setup312 <your_carleton_email_name>

Folder structure
After you've mounted your cs312 course folder the mount point is

/Volumes/cs312-00-w20/StuWork/email_name
$HOME312 = /Volumes/cs312-00-w20/StuWork/email_name (same as above)
Inside $HOME312 are these folders
bin common cs312 googletest (the big download from class 3.2)
# anything else is ignored in assignment setups

Directory Notes

I'm writing this at home from memory and do not have access to your course folder in either labs.
This is the way I expect your course folder to be se

Test in Mac Terminal

echo $HOME312
# should return
# /Volumes/cs312-00-w20/StuWork/email_name
ls $HOME312
# should return
# bin/    common/ cs312/ googletest/ (the download)
ls $HOME312/bin
# should return command line tools like ascii and boilerplate
ls $HOME312/common
# should return things like hw222_CMidiPacket.h, hw222_CMidiPacket.cpp and googletest/ (include/ and lib/)
ls $HOME312/cs312
# should return your hwNN folders
# There should be no bin folder or common folder inside cs312
echo $PATH
# you need to see /Volumes/cs312-00-w20/StuWork/email_name/bin (hopefully first in line)

This setup needs to be correct.
Tell me if you get something different.

CMakeLists.txt

I'm including the following code in future CMakeLists.txt (CML) files. When I correct your homework I need to change pathname variables. In order to simplify things I've inserted the following conditional statements in CML needed when we start accessing files in the common folder.

# JE sets FOR_CLASS to 0
# YOU set FOR_CLASS to 1
set(FOR_CLASS 1)
if(${FOR_CLASS})
  set( HOME "/Volumes/cs312-00-w20/StuWork/<your_email_name>" )
elseif(NOT ${FOR_CLASS})
  set( HOME "/Users/je/cs312/_cs312" )
endif(${FOR_CLASS})

Be sure to use your Carleton email name in place of <your_email_name>.

Grading Notes

Grading is taking too much time when both partners submit their own Hand-in folder. I have to run a diff program to see if the folders match. If source code files differ I end up grading each person separately.

From now on partners submit in only one Hand-in folder
Your choice but make sure you both agree that its the folder that will be graded.

There were several low grades in hw23 primarily resulting from

  • failure to compile from the folder your submitting
  • missing files in CMakeLists.txt
  • failure to read the complete web page contents before starting the homework
  • failure to follow the homework directions
  • failure to generate different random sequences on each execution of the program
  • failure to test audio output in MIDIDisplay_DLS to see if it was listenable
  • on that note (pun intended) the MIDI note range of the piano is from 24-108.

These are the penalties I assessed in hw23 and will continue to assess.

-15 Could not compile
CMakeLists.txt missing files
Files needed by cmake not in the Hand-in folder

-15 This is a C++ class
You used C functions that have a modern C++ alternative.
You did not read the web page and used C functions you learned in the past.

-15 This is an Audio programming class
Did you ever play the output in MIDIDisplay_DLS
The pitch output range data1 of NON and NOF messages not listenable.
The lowest note on the piano is MIDI 24, the highest note is 108.

-10 no assigned command line tool in found your StuWork/bin folder.
I will be testing your command line tools from your StuWork/bin folder.

-10 no assigned files found in your StuWork/common folder

scifiSounds example

# Jonah Fisher
# Max Goldberg
# Tempo 140
0	c0	61
0	90	64	100
100	80	64	0
500	c0	4
500	90	70	100
600	80	70	0
1000	c0	120
1000	90	64	100
1100	80	64	0
1500	c0	82
1500	90	70	100
1600	80	70	0
2000	c0	123
2000	90	70	100
2100	80	70	0
2500	c0	74
2500	90	64	100
2600	80	64	0
3000	c0	12
3000	90	65	100
3100	80	65	0
3500	c0	97
3500	90	62	100
3600	80	62	0
4000	c0	101
4000	90	65	100
4100	80	65	0
4500	c0	49
4500	90	62	100
4600	80	62	0
5000	c0	21
5000	90	68	100
5100	80	68	0
5500	c0	81
5500	90	64	100
5600	80	64	0
6000	c0	55
6000	90	71	100
6100	80	71	0
6500	c0	68
6500	90	71	100
6600	80	71	0
7000	c0	5
7000	90	70	100
7100	80	70	0
7500	c0	9
7500	90	69	100
7600	80	69	0
8000	c0	57
8000	90	64	100
8100	80	64	0
8500	c0	25
8500	90	72	100
8600	80	72	0
9000	c0	76
9000	90	63	100
9100	80	63	0
9500	c0	28
9500	90	64	100
9600	80	64	0
10000	c0	107
10000	90	70	100
10100	80	70	0
10500	c0	30
10500	90	62	100
10600	80	62	0
11000	c0	11
11000	90	61	100
11100	80	61	0
11500	c0	75
11500	90	66	100
11600	80	66	0
12000	c0	75
12000	90	61	100
12100	80	61	0
12500	c0	87
12500	90	69	100
12600	80	69	0
13000	c0	1
13000	90	63	100
13100	80	63	0
13500	c0	99
13500	90	63	100
13600	80	63	0
14000	c0	116
14000	90	64	100
14100	80	64	0
14500	c0	78
14500	90	72	100
14600	80	72	0
15000	c0	93
15000	90	60	100
15100	80	60	0
15500	c0	120
15500	90	61	100
15600	80	61	0
16000	c0	55
16000	90	69	100
16100	80	69	0
16500	c0	22
16500	90	63	100
16600	80	63	0
17000	c0	5
17000	90	72	100
17100	80	72	0
17500	c0	2
17500	90	60	100
17600	80	60	0
18000	c0	104
18000	90	71	100
18100	80	71	0
18500	c0	68
18500	90	64	100
18600	80	64	0
19000	c0	94
19000	90	64	100
19100	80	64	0
19500	c0	5
19500	90	70	100
19600	80	70	0
20000	c0	58
20000	90	60	100
20100	80	60	0
20500	c0	108
20500	90	66	100
20600	80	66	0
21000	c0	97
21000	90	68	100
21100	80	68	0
21500	c0	103
21500	90	64	100
21600	80	64	0
22000	c0	28
22000	90	70	100
22100	80	70	0
22500	c0	40
22500	90	71	100
22600	80	71	0
23000	c0	125
23000	90	67	100
23100	80	67	0
23500	c0	104
23500	90	62	100
23600	80	62	0
24000	c0	33
24000	90	72	100
24100	80	72	0
24500	c0	107
24500	90	70	100
24600	80	70	0
25000	c0	38
25000	90	68	100
25100	80	68	0
25500	c0	27
25500	90	64	100
25600	80	64	0
26000	c0	121
26000	90	72	100
26100	80	72	0
26500	c0	80
26500	90	66	100
26600	80	66	0
27000	c0	120
27000	90	61	100
27100	80	61	0
27500	c0	35
27500	90	69	100
27600	80	69	0
28000	c0	63
28000	90	69	100
28100	80	69	0
28500	c0	85
28500	90	65	100
28600	80	65	0
29000	c0	91
29000	90	63	100
29100	80	63	0
29500	c0	12
29500	90	63	100
29600	80	63	0
30000	c0	57
30000	90	60	100
30100	80	60	0
30500	c0	96
30500	90	70	100
30600	80	70	0
31000	c0	63
31000	90	67	100
31100	80	67	0
31500	c0	49
31500	90	63	100
31600	80	63	0
32000	c0	22
32000	90	67	100
32100	80	67	0
32500	c0	75
32500	90	67	100
32600	80	67	0
33000	c0	29
33000	90	69	100
33100	80	69	0
33500	c0	72
33500	90	63	100
33600	80	63	0
34000	c0	119
34000	90	65	100
34100	80	65	0
34500	c0	67
34500	90	71	100
34600	80	71	0
35000	c0	110
35000	90	62	100
35100	80	62	0
35500	c0	43
35500	90	61	100
35600	80	61	0
36000	c0	70
36000	90	70	100
36100	80	70	0
36500	c0	20
36500	90	60	100
36600	80	60	0
37000	c0	65
37000	90	64	100
37100	80	64	0
37500	c0	105
37500	90	68	100
37600	80	68	0
38000	c0	69
38000	90	62	100
38100	80	62	0
38500	c0	39
38500	90	63	100
38600	80	63	0
39000	c0	18
39000	90	70	100
39100	80	70	0
39500	c0	15
39500	90	68	100
39600	80	68	0
40000	c0	106
40000	90	67	100
40100	80	67	0
40500	c0	68
40500	90	63	100
40600	80	63	0
41000	c0	93
41000	90	63	100
41100	80	63	0
41500	c0	61
41500	90	60	100
41600	80	60	0
42000	c0	80
42000	90	64	100
42100	80	64	0
42500	c0	37
42500	90	70	100
42600	80	70	0
43000	c0	64
43000	90	60	100
43100	80	60	0
43500	c0	79
43500	90	66	100
43600	80	66	0
44000	c0	60
44000	90	72	100
44100	80	72	0
44500	c0	8
44500	90	60	100
44600	80	60	0
45000	c0	112
45000	90	65	100
45100	80	65	0
45500	c0	51
45500	90	68	100
45600	80	68	0
46000	c0	100
46000	90	72	100
46100	80	72	0
46500	c0	10
46500	90	60	100
46600	80	60	0
47000	c0	59
47000	90	70	100
47100	80	70	0
47500	c0	41
47500	90	62	100
47600	80	62	0
48000	c0	34
48000	90	62	100
48100	80	62	0
48500	c0	33
48500	90	69	100
48600	80	69	0
49000	c0	100
49000	90	62	100
49100	80	62	0
49500	c0	51
49500	90	60	100
49600	80	60	0

More Bulletproofing

We'll take two approaches to making CMidiPacket immune to bad input data.

  • Use googletest to test function return values.
  • Use invariant testing to test for valid function input parameters.

We'll aslo test std::vector<CMidiPacket> vectors to ensure timestamps are in strictly ascending order.

Googletest was introduced in class 3.2. Your CMidiPacket files need to pass every test.

Today's class and homework involve implementing invariant testing and timestamp testing of CMidiPacket vectors.

We'll start with timestamp testing.

Play in MIDIDisplay_DLS
A student generated this std::vector<CMidiPacket> and played in MIDIDisplay_DLS.
It played a few notes and then stopped.
Try it.
Quit the program when it stops.

0	c0	11
0	90	69	100
1000	80	69	0
1000	90	64	100
1125	80	64	0
1125	90	69	100
1250	80	69	0
1250	90	64	100
1375	80	64	0
1375	90	67	100
2375	80	67	0
2375	90	69	100
2875	80	69	0
2875	90	64	100
3875	80	64	0
3875	90	67	100
4125	80	67	0
4125	90	67	100
4375	80	67	0
4375	90	64	100
4875	80	64	0
4875	90	72	100
5375	80	72	0
5365	90	64	100
5500	80	64	0
5500	90	67	100
5750	80	67	0
5750	90	60	100
6000	80	60	0
6000	90	72	100
7000	80	72	0
7000	90	64	100
7250	80	64	0
7250	90	69	100
7750	80	69	0
7750	90	72	100
8250	80	72	0
8250	90	64	100
8375	80	64	0
8375	90	67	100
8625	80	67	0
8625	90	64	100
8750	80	64	0
8650	90	67	100
8875	80	67	0
8875	90	69	100
9375	80	69	0
9375	90	60	100
9500	80	60	0
9500	90	67	100
9750	80	67	0
9750	90	62	100
10750	80	62	0
10750	90	62	100
11750	80	62	0
11750	90	64	100
12750	80	64	0
12750	90	72	100
13750	80	72	0
13750	90	69	100
14250	80	69	0
14250	90	64	100
14375	80	64	0
14375	90	62	100
14500	80	62	0
14500	90	69	100
14625	80	69	0
14625	90	69	100
15625	80	69	0
15625	90	60	100
15875	80	60	0
15875	90	72	100
16125	80	72	0
16125	90	67	100
16625	80	67	0
16625	90	62	100
16750	80	62	0
16750	90	60	100
16875	80	60	0
16875	90	69	100
17000	80	69	0
17000	90	72	100
17125	80	72	0
17125	90	72	100
17250	80	72	0
17250	90	67	100
17500	80	67	0
17500	90	72	100
16750	80	72	0
17750	90	69	100
18000	80	69	0
18000	90	72	100
18250	80	72	0
18250	90	62	100
18375	80	62	0
18375	90	72	100
18625	80	72	0
18625	90	64	100
18750	80	64	0
18750	90	67	100
19000	80	67	0

The problem was MIDIDisplay_DLS (MD) encountered a timestamp that was of order. Look through the timestamps above and see if you can find any timestamps out of order. There are three timestamps out of order. MD gets stuck at the first one it encounters. Once MD starts playing it cannot deal with timestamps going backwards. Timestamps must maintain strict ascending order (chronological order).

c331_checkTimestamps

c331_checkTimestamps finds and reports where timestamps are out of order.

Setup

# copy your hw322_CMidiPacket files to common folder
cp $HOME312/cs312/hw32/hw322_CMP32gtest/hw322_CMidiPacket.h $HOME312/common
cp $HOME312/cs312/hw32/hw322_CMP32gtest/hw322_CMidiPacket.cpp $HOME312/common
mkdir $HOME312/cs312/hw33
mkdir $HOME312/cs312/hw33/c331_checkTimestamps
cd $HOME312/cs312/hw33/c331_checkTimestamps
mkdir build
touch CMakeLists.txt
touch hw331_checkTimestamps.cpp
touch hw331_checkTimestamps.h
touch hw331_main.cpp
code .

Copy paste this code into the appropriate files.

CMakeLists.txt

cmake_minimum_required(VERSION 2.8)
set(APPNAME "checkTimestamps")
project(${APPNAME})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -Wall ")

# JE sets FOR_CLASS to 0
# YOU set FOR_CLASS to 1
set(FOR_CLASS 1)
if(${FOR_CLASS})
  set( HOME "/Volumes/cs312-00-w20/StuWork/<your_email_name>" )
elseif(NOT ${FOR_CLASS})
  set( HOME "/Users/je/cs312/_cs312")
endif(${FOR_CLASS})
set(COMMON "${HOME}/common")

set(SOURCE_FILES
  ${COMMON}/hw322_CMidiPacket.cpp
  hw331_checkTimestamps.cpp
  hw331_main.cpp
  )

include_directories(${COMMON})
add_executable(${APPNAME} ${SOURCE_FILES})

hw331_checkTimestamps.h

// hw331_checkTimestamps.h
#ifndef HW331_CHECKTIMESTAMPS_H_
#define HW331_CHECKTIMESTAMPS_H_

#ifndef hw322_CMIDIPACKET_H_
#include "hw322_CMidiPacket.h"
#endif

#include <vector>

using namespace CMP32;

extern std::vector<CMidiPacket> vts;

extern void createBadTimestampVector();
extern void check_timestamps();

#endif // HW331_CHECKTIMESTAMPS_H_

hw331_checkTimestamps.cpp

// hw331_checkTimestamps.cpp
#ifndef HW331_CHECKTIMESTAMPS_H_
#include "hw331_checkTimestamps.h"
#endif

#include <iostream>

std::vector<CMidiPacket> vts;

void createBadTimestampVector()
{
    CMidiPacket mp;
    vts.push_back(mp = {0, 0xc0, 11});
    vts.push_back(mp = {0, 0x90, 69, 100});
    vts.push_back(mp = {1000, 0x80, 69, 0});
    vts.push_back(mp = {1000, 0x90, 64, 100});
    vts.push_back(mp = {1125, 0x80, 64, 0});
    vts.push_back(mp = {1125, 0x90, 69, 100});
    vts.push_back(mp = {1250, 0x80, 69, 0});
    vts.push_back(mp = {1250, 0x90, 64, 100});
    vts.push_back(mp = {1375, 0x80, 64, 0});
    vts.push_back(mp = {1375, 0x90, 67, 100});
    vts.push_back(mp = {2375, 0x80, 67, 0});
    vts.push_back(mp = {2375, 0x90, 69, 100});
    vts.push_back(mp = {2875, 0x80, 69, 0});
    vts.push_back(mp = {2875, 0x90, 64, 100});
    vts.push_back(mp = {3875, 0x80, 64, 0});
    vts.push_back(mp = {3875, 0x90, 67, 100});
    vts.push_back(mp = {4125, 0x80, 67, 0});
    vts.push_back(mp = {4125, 0x90, 67, 100});
    vts.push_back(mp = {4375, 0x80, 67, 0});
    vts.push_back(mp = {4375, 0x90, 64, 100});
    vts.push_back(mp = {4875, 0x80, 64, 0});
    vts.push_back(mp = {4875, 0x90, 72, 100});
    vts.push_back(mp = {5375, 0x80, 72, 0});
    vts.push_back(mp = {5365, 0x90, 64, 100});
    vts.push_back(mp = {5500, 0x80, 64, 0});
    vts.push_back(mp = {5500, 0x90, 67, 100});
    vts.push_back(mp = {5750, 0x80, 67, 0});
    vts.push_back(mp = {5750, 0x90, 60, 100});
    vts.push_back(mp = {6000, 0x80, 60, 0});
    vts.push_back(mp = {6000, 0x90, 72, 100});
    vts.push_back(mp = {7000, 0x80, 72, 0});
    vts.push_back(mp = {7000, 0x90, 64, 100});
    vts.push_back(mp = {7250, 0x80, 64, 0});
    vts.push_back(mp = {7250, 0x90, 69, 100});
    vts.push_back(mp = {7750, 0x80, 69, 0});
    vts.push_back(mp = {7750, 0x90, 72, 100});
    vts.push_back(mp = {8250, 0x80, 72, 0});
    vts.push_back(mp = {8250, 0x90, 64, 100});
    vts.push_back(mp = {8375, 0x80, 64, 0});
    vts.push_back(mp = {8375, 0x90, 67, 100});
    vts.push_back(mp = {8625, 0x80, 67, 0});
    vts.push_back(mp = {8625, 0x90, 64, 100});
    vts.push_back(mp = {8750, 0x80, 64, 0});
    vts.push_back(mp = {8650, 0x90, 67, 100});
    vts.push_back(mp = {8875, 0x80, 67, 0});
    vts.push_back(mp = {8875, 0x90, 69, 100});
    vts.push_back(mp = {9375, 0x80, 69, 0});
    vts.push_back(mp = {9375, 0x90, 60, 100});
    vts.push_back(mp = {9500, 0x80, 60, 0});
    vts.push_back(mp = {9500, 0x90, 67, 100});
    vts.push_back(mp = {9750, 0x80, 67, 0});
    vts.push_back(mp = {9750, 0x90, 62, 100});
    vts.push_back(mp = {10750, 0x80, 62, 0});
    vts.push_back(mp = {10750, 0x90, 62, 100});
    vts.push_back(mp = {11750, 0x80, 62, 0});
    vts.push_back(mp = {11750, 0x90, 64, 100});
    vts.push_back(mp = {12750, 0x80, 64, 0});
    vts.push_back(mp = {12750, 0x90, 72, 100});
    vts.push_back(mp = {13750, 0x80, 72, 0});
    vts.push_back(mp = {13750, 0x90, 69, 100});
    vts.push_back(mp = {14250, 0x80, 69, 0});
    vts.push_back(mp = {14250, 0x90, 64, 100});
    vts.push_back(mp = {14375, 0x80, 64, 0});
    vts.push_back(mp = {14375, 0x90, 62, 100});
    vts.push_back(mp = {14500, 0x80, 62, 0});
    vts.push_back(mp = {14500, 0x90, 69, 100});
    vts.push_back(mp = {14625, 0x80, 69, 0});
    vts.push_back(mp = {14625, 0x90, 69, 100});
    vts.push_back(mp = {15625, 0x80, 69, 0});
    vts.push_back(mp = {15625, 0x90, 60, 100});
    vts.push_back(mp = {15875, 0x80, 60, 0});
    vts.push_back(mp = {15875, 0x90, 72, 100});
    vts.push_back(mp = {16125, 0x80, 72, 0});
    vts.push_back(mp = {16125, 0x90, 67, 100});
    vts.push_back(mp = {16625, 0x80, 67, 0});
    vts.push_back(mp = {16625, 0x90, 62, 100});
    vts.push_back(mp = {16750, 0x80, 62, 0});
    vts.push_back(mp = {16750, 0x90, 60, 100});
    vts.push_back(mp = {16875, 0x80, 60, 0});
    vts.push_back(mp = {16875, 0x90, 69, 100});
    vts.push_back(mp = {17000, 0x80, 69, 0});
    vts.push_back(mp = {17000, 0x90, 72, 100});
    vts.push_back(mp = {17125, 0x80, 72, 0});
    vts.push_back(mp = {17125, 0x90, 72, 100});
    vts.push_back(mp = {17250, 0x80, 72, 0});
    vts.push_back(mp = {17250, 0x90, 67, 100});
    vts.push_back(mp = {17500, 0x80, 67, 0});
    vts.push_back(mp = {17500, 0x90, 72, 100});
    vts.push_back(mp = {16750, 0x80, 72, 0});
    vts.push_back(mp = {17750, 0x90, 69, 100});
    vts.push_back(mp = {18000, 0x80, 69, 0});
    vts.push_back(mp = {18000, 0x90, 72, 100});
    vts.push_back(mp = {18250, 0x80, 72, 0});
    vts.push_back(mp = {18250, 0x90, 62, 100});
    vts.push_back(mp = {18375, 0x80, 62, 0});
    vts.push_back(mp = {18375, 0x90, 72, 100});
    vts.push_back(mp = {18625, 0x80, 72, 0});
    vts.push_back(mp = {18625, 0x90, 64, 100});
    vts.push_back(mp = {18750, 0x80, 64, 0});
    vts.push_back(mp = {18750, 0x90, 67, 100});
    vts.push_back(mp = {19000, 0x80, 67, 0});
}

// You implement
void check_timestamps()
{
  uint32_t prevTs = 0;
  uint32_t nowTs = 0;

  for (auto itr : vts)
  {
  /*
  iterate through the packets
  compare now timestamp with previous timestamp
  if now < previous report this error
        std::cout << "### " << nowTs << " Timestamp out of order\n";
  print now packet
  continue
  */
  }
}

hw331_main.cpp

// hw331_main.cpp
#ifndef HW331_CHECKTIMESTAMPS_H_
#include "hw331_checkTimestamps.h"
#endif

int main()
{
  createBadTimestampVector();
#if 0
  sort(begin(vts), end(vts));
#endif
  check_timestamps();
}

Fix squiggles in hw331_checkTimestamps.h

Click the squiggle.

c33101.png

When the light bulb appears click the light bulb.

c33102.png

When the popup menu appears choose Edit "includePath" setting.

c33103.png

In the C++ configuartion navigate to the Include path setting and add the your path to the common folder.
Note:
The picture below shows my path. You'll need to edit it.
You'll need to do this anytime your program needs access to files outside of the hwNN directory.

/Volumes/cs312-00-w20/StuWork/<your_email_name>/common/**
# Find it in Terminal using this command
# echo $HOME312/common

c33104.png Click outside of the text area. If vsCode cannot find your common folder a red error message will appear.

Build and run using cmake

My output shows

0       c0      11
0       90      69      100
...
4875    90      72      100
5375    80      72      0
### 5365 Timestamp out of order
5365    90      64      100
5500    80      64      0
...
8625    90      64      100
8750    80      64      0
### 8650 Timestamp out of order
8650    90      67      100
8875    80      67      0
...
17500   80      67      0
17500   90      72      100
### 16750 Timestamp out of order
16750   80      72      0
17750   90      69      100
...
18750   90      67      100
19000   80      67      0

Next set the #if…#endif statements to 1

int main()
{
  createBadTimestampVector();
#if 1
  sort(begin(vts), end(vts));
#endif
  check_timestamps();
}

Build and run again
If the timestamps are error, free play the example in MIDIDisplay_DLS. It's an example of hw234_scifiSounds.

If the timestamps are not error free something is wrong with your hw322_CMidiPacket.cpp operator less function.

Important:
The std::sort() function uses your class operator less function if one is implemented. CMidiPackets have special sorting requirements. That's why operator less is needed.

Homework 3.3

Reading

Timestamps

Timestamps are not part of the MIDI 1.0 specification. They are part of the Standard MIDI File specification and use a format called PPQ (Parts Per Quarter note). PPQ and other timestamp formats will be introduced in a future class.

Tempo and beat

A musical "beat" is a uniformly recurring pulse that is perceived by the performer or listener and is external to the music. The beat roughly corresponds to tapping your foot to the music. Musical tempo is number of beats per minute. At a tempo of 60 each beat lasts one second (1000ms). The duration of a song is determined by the tempo and the number of beats in the song. A song with 600 beats at a tempo of 60 would last 10 minutes. This table shows the final timestamp value in milliseconds reached at different durations and tempi.

Tempo timestamp timestamp timestamp timestamp
beats/min after 1 second after 1 minute after 10 minutes after 1 hour
60 1000 60,000 600,000 3,600,000
30 2000 120,000 1,200,000 7,200,000
120 500 30,000 300,000 1,800,000

The maximum uint32_t value is 4,294,967,295. A song with that final timestamp at a tempo of 60 would last 49.7 days. It would probably be safe for the class to set an upper limit of 2,000,000 as an invariant for the maximum timestamp value. It's unlikely that any of our class projects will last over 30 minutes at a fast tempo.

The CMidiPacket timestamp represents chronological time advancing in milliseconds. When you press the play button millisecond time is reset to zero. The program periodically checks the computer clock. When the timestamp is equal to or greater than the computer clock the message is sent.

MIDI messages happen rarely as far as the computer is concerned. A medium speed 2.5 GHz CPU has 2,500,000 cycles between milliseconds. During these idle times the software can respond to other tasks like buttons pushed, menus opened, windows moved, etc. The Tempo Slider in MIDIDisplay can be moved while the song is playing and the music will speed up or slow down. Every time the tempo changes the time the software recalculates when the next message needs to be sent. We'll be dealing with time and tempo in more detail in a future class.

hw331_checkTimestamps

This command line tool will check the output of CMidiPacket vectors for out of order timestamps.
The motivation for this tool is that future assignments will be generating longer songs to play in MIDIDisplay_DLS (MD). You would use this command to somewhere in your code to create the output vector.

std::vector<CMidiPacket> vOutput;
...
for (auto itr : vOutput)
  std::cout << itr;

Then you'd compile and execute your program like this to directly copy your output to the clipboard ready to paste into MD.

cmake .. && make && mySong | pbcopy

Next instead of pasting into MD paste it into a file called output.txt and run the checkTimestamps command on output.txt.

checkTimestamps <pathname_to_output.txt>

command line tool

Assignment hw_331

Copy these MIDI messages into a file named output.txt. It's the same bad file used in c331.

0	c0	11
0	90	69	100
1000	80	69	0
1000	90	64	100
1125	80	64	0
1125	90	69	100
1250	80	69	0
1250	90	64	100
1375	80	64	0
1375	90	67	100
2375	80	67	0
2375	90	69	100
2875	80	69	0
2875	90	64	100
3875	80	64	0
3875	90	67	100
4125	80	67	0
4125	90	67	100
4375	80	67	0
4375	90	64	100
4875	80	64	0
4875	90	72	100
5375	80	72	0
5365	90	64	100
5500	80	64	0
5500	90	67	100
5750	80	67	0
5750	90	60	100
6000	80	60	0
6000	90	72	100
7000	80	72	0
7000	90	64	100
7250	80	64	0
7250	90	69	100
7750	80	69	0
7750	90	72	100
8250	80	72	0
8250	90	64	100
8375	80	64	0
8375	90	67	100
8625	80	67	0
8625	90	64	100
8750	80	64	0
8650	90	67	100
8875	80	67	0
8875	90	69	100
9375	80	69	0
9375	90	60	100
9500	80	60	0
9500	90	67	100
9750	80	67	0
9750	90	62	100
10750	80	62	0
10750	90	62	100
11750	80	62	0
11750	90	64	100
12750	80	64	0
12750	90	72	100
13750	80	72	0
13750	90	69	100
14250	80	69	0
14250	90	64	100
14375	80	64	0
14375	90	62	100
14500	80	62	0
14500	90	69	100
14625	80	69	0
14625	90	69	100
15625	80	69	0
15625	90	60	100
15875	80	60	0
15875	90	72	100
16125	80	72	0
16125	90	67	100
16625	80	67	0
16625	90	62	100
16750	80	62	0
16750	90	60	100
16875	80	60	0
16875	90	69	100
17000	80	69	0
17000	90	72	100
17125	80	72	0
17125	90	72	100
17250	80	72	0
17250	90	67	100
17500	80	67	0
17500	90	72	100
16750	80	72	0
17750	90	69	100
18000	80	69	0
18000	90	72	100
18250	80	72	0
18250	90	62	100
18375	80	62	0
18375	90	72	100
18625	80	72	0
18625	90	64	100
18750	80	64	0
18750	90	67	100
19000	80	67	0

Create the command line tool
Read in the file call the checkTimestamps() function. Name your executable checkTimestamps and copy it to your bin folder. Execute this command to test it.

# cd to the folder where output.txt is located.
checkTimestamps output.txt

You should see where the bad timestamps are located.

hw332_CMidiPacket33

This assignment implements the invariant testing routines.

Preliminary setup

These commands will

  • Backup hw322_CMidiPacket files.
  • Rename them to hw332_CMidiPacket.h and .cpp
  • Use sed to change namespace to CMP33 and all references to hw322 to hw333.
  • Create the files and folder for hw332_testInvariants
cd $HOME312/common
## backup CMidiPacket32
tar czvf hw322_CMidiPacket.tgz hw322_CMidiPacket.h hw322_CMidiPacket.cpp
# rename
mv hw322_CMidiPacket.h hw332_CMidiPacket.h
mv hw322_CMidiPacket.cpp hw332_CMidiPacket.cpp

# sed find/replace
sed -i '' 's/CMP32/CMP33/g' hw332_CMidiPacket.h
sed -i '' 's/HW322/HW332/g' hw332_CMidiPacket.h
sed -i '' 's/CMP32/CMP33/g' hw332_CMidiPacket.cpp
sed -i '' 's/HW322/HW332/g' hw332_CMidiPacket.cpp
sed -i '' 's/hw322/hw332/g' hw332_CMidiPacket.cpp

# create the hw332_test_invariants folder and files
mkdir $HOME312/cs312/hw33/hw332_test_invariants
cd $HOME312/cs312/hw33/hw332_test_invariants
mkdir build
touch CMakeLists.txt
touch hw332_main.cpp
touch hw332_testInvariants.cpp
touch hw332_testInvariants.h
touch hw332_testInvariants.cpp

Note: If you need to recover your hw322_CMidiPacket files back you can retrieve them with this command.

#cd $HOME312/common
tar xzvf hw322_CMidiPacket.tgz

Compile

cd $HOME312/common
cl hw332_CMidiPacket.cpp

This error's ok. It just says it couldn't find a main() function.
A long as there are no other errors or warnings, continue.

Undefined symbols for architecture x86_64:
  "_main", referenced from:
     implicit entry/start for main executable
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Add code to common/hw332_CMidiPacket.h and .cpp

common/hw332_CMidiPacket.h
Append this code after the end of the friends section. Replace this

  /****************************** END FRIENDS ******************************/
};
} // namespace CMP33
#endif // HW332_CMIDIPACKET_H

with this

  /****************************** END FRIENDS ******************************/

  /*********************** CMP33 Invariant checks ***********************/
 private:
  void ERROR(const std::string& msg) const;  // helper
  void invariant_check() const;
};// end CMidiPacket class
} // end namespace CMP33

const uint32_t kMAX_TIMESTAMP = 1800000; // 30 minutes in milliseconds
const std::string kERR_TIMESTAMP_OUT_OF_RANGE =
    "Timestamp exceeds 30 minutes in milliseconds\n";
const std::string kERR_STATUS_OUT_OF_RANGE =
    "Status out of range. Must be 0x80 - 0xFF\n";
const std::string kERR_DATA1_OUT_OF_RANGE =
    "Data1 out of range. Must be 0 - 127\n";
const std::string kERR_DATA2_OUT_OF_RANGE =
    "Data2 out of range. Must be 0 - 127\n";
const std::string kERR_BAD_8n_MIDIPACKET_LENGTH = "Bad 8n MidiPacket length\n";
const std::string kERR_BAD_9n_MIDIPACKET_LENGTH = "Bad 9n MidiPacket length\n";
const std::string kERR_BAD_An_MIDIPACKET_LENGTH = "Bad An MidiPacket length\n";
const std::string kERR_BAD_Bn_MIDIPACKET_LENGTH = "Bad Bn MidiPacket length\n";
const std::string kERR_BAD_Cn_MIDIPACKET_LENGTH = "Bad Cn MidiPacket length\n";
const std::string kERR_BAD_Dn_MIDIPACKET_LENGTH = "Bad Dn MidiPacket length\n";
const std::string kERR_BAD_En_MIDIPACKET_LENGTH = "Bad En MidiPacket length\n";
const std::string kERR_Fn_MESSAGES_NOT_SUPPORTED =
    "0xFn messages not supported\n";
  /*********************** END CMP33 Invariant checks ***********************/

#endif // HW332_CMIDIPACKET_H_

common/hw332_CMidiPacket.cpp
Add invariant_check() code
Append this code at the end of your CMidiPacket.cpp file.

/****************************** END FRIENDS ******************************/
COPY FROM HERE TO END
/****************************** CMP33 Invariant checking ******************************/
void CMidiPacket::ERROR(const std::string &msg) const
{
    if (1)
    {
        std::cout << "ERROR: " << msg << std::endl;
    }
    else
    {
        throw std::logic_error(msg);
    }
}

void CMidiPacket::invariant_check() const
{
    // range check
    if (timestamp_ > kMAX_TIMESTAMP)
        ERROR(kERR_TIMESTAMP_OUT_OF_RANGE);
    if ((status_ < 0x80) || (status_ >= 0xFF))
        ERROR(kERR_STATUS_OUT_OF_RANGE);
    // data 1 and 2 range check
    else if (data1_ > 0x7F)
        ERROR(kERR_DATA1_OUT_OF_RANGE);
    else if ((data2_ > 0x7F) && length_ == 3)
        ERROR(kERR_DATA2_OUT_OF_RANGE);

    //  status length check
    else if (is_status_8n() && length_ != 3)
        ERROR(kERR_BAD_8n_MIDIPACKET_LENGTH);
    else if (is_status_9n() && length_ != 3)
        ERROR(kERR_BAD_9n_MIDIPACKET_LENGTH);
    else if (is_status_An() && length_ != 3)
        ERROR(kERR_BAD_An_MIDIPACKET_LENGTH);
    else if (is_status_Bn() && length_ != 3)
        ERROR(kERR_BAD_Bn_MIDIPACKET_LENGTH);
    else if (is_status_Cn() && length_ != 2)
        ERROR(kERR_BAD_Cn_MIDIPACKET_LENGTH);
    else if (is_status_Dn() && length_ != 2)
        ERROR(kERR_BAD_Dn_MIDIPACKET_LENGTH);
    else if (is_status_En() && length_ != 3)
        ERROR(kERR_BAD_En_MIDIPACKET_LENGTH);

    //  unsupported check
    else if (is_status_Fn())
        ERROR(kERR_Fn_MESSAGES_NOT_SUPPORTED);
}
/****************************** End CMP33 Invariant checking ******************************/

Compile

cd $HOME312/common
cl hw332_CMidiPacket.cpp

This error's ok. It just says it couldn't find a main() function.

Undefined symbols for architecture x86_64:
  "_main", referenced from:
     implicit entry/start for main executable
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

A long as there are no other errors or warnings, continue.

hw332_testInvariants

The only way bad data can enter CMidiPacket is through calls to its constructors setters. Calling the invariant_check() function in all constructors and setter functions will catch and report bad data. The from string is used to report the function name where the bad data occurred. You'll need to add invariant_check() calls to the following functions.

Open the hw332_test_invariants folder in vsCode.

Copy this code into their respective files.

CMakeLists.txt

cmake_minimum_required(VERSION 2.8)
set(APPNAME "hw332_testInvariants")
project(${APPNAME})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -Wall ")

# JE sets FOR_CLASS to 0
# YOU set FOR_CLASS to 1
set(FOR_CLASS 1)
if(${FOR_CLASS})
  set( HOME "/Volumes/cs312-00-w20/StuWork/<your_email_name>" )
elseif(NOT ${FOR_CLASS})
  set( HOME "/Users/je/cs312/_cs312")
endif(${FOR_CLASS})
set(COMMON "${HOME}/common")

set(SOURCE_FILES
  ${COMMON}/hw332_CMidiPacket.cpp
  hw332_testInvariants.cpp
  hw332_main.cpp
)

include_directories(${COMMON})
add_executable(${APPNAME} ${SOURCE_FILES})

hw332_main.cpp

// hw332_main.cpp
#ifndef HW332_TESTINVARIANTS_H
#include "hw332_testInvariants.h"
#endif

int main()
{
  testInvariants();
};

hw332_testInvariants.cpp

// hw332_testInvariants.cpp

#ifndef HW332_CMIDIPACKET_H_
#include "hw332_CMidiPacket.h"
#endif

#ifndef HW332_TESTINVARIANTS_H
#include "hw332_testInvariants.h"
#endif

#include <iostream>
#include <sstream>

using namespace CMP33;

void print_header(const std::string msg)
{
    std::cout << "\n===> " << msg << " <====" << std::endl;
}

// Constructors
void testConstructorOneDataByte()
{
    // Example - you implement others
    // Deliberately enter bad data
    // Enter several tests
    // Try to find a false positive where bad data gets through
    // Test bad bad timestamp, status, data1, data2, length
    print_header("testConstructorOneDataByte");
    CMidiPacket mp1{1000, 0xC0, 11, 100}; // bad length
}

void testConstructorTwoDataBytes()
{
    print_header("testConstructorTwoDataBytes");
    // Create one CMidiPacket that will fail this test
    // After first successful compile add several more
    // try to find a false positive (bad data gets through)
}

void testStringConstructor()
{
    print_header("testStringConstructor");
    // Create one CMidiPacket that will fail this test
    // After first successful compile add several more
    // try to find a false positive (bad data gets through)
}

// Setters
void test_set_timestamp()
{
    print_header("test_set_timestamp");
    CMidiPacket p{1000, 0x80, 100, 0};
    p.set_timestamp(kMAX_TIMESTAMP + 1);
    // make up more bad data
}

void test_set_status()
{
    print_header("test_set_status");
    // After first successful compile add several more
    // try to find a false positive (bad data gets through)
}

void test_set_data1()
{
    print_header("test_set_data1");
    CMidiPacket p{1000, 0x90, 60, 100};
    // Create one CMidiPacket that will fail this test
    // After first successful compile add several more
    // try to find a false positive (bad data gets through)
}

void test_set_data2()
{
    print_header("test_set_data2");
    // Create one CMidiPacket that will fail this test
    // After first successful compile add several more
    // try to find a false positive (bad data gets through)
}

void test_set_midi_channel()
{
    print_header("test_set_midi_channel");
    // Create one CMidiPacket that will fail this test
    // After first successful compile add several more
    // try to find a false positive (bad data gets through)
}

// Operator overloads
void test_output_operator()
{
    print_header("test_ouput_operator");
    // Create one CMidiPacket that will fail this test
    // After first successful compile add several more
    // try to find a false positive (bad data gets through)
}

void test_input_operator()
{
    print_header("test_input_operator");
    // Create one CMidiPacket that will fail this test
    // After first successful compile add several more
    // try to find a false positive (bad data gets through)
}

void test_operator_equals()
{
    print_header("test_operator_equals");
    // Create one CMidiPacket that will fail this test
    // After first successful compile add several more
    // try to find a false positive (bad data gets through)
}

void test_operator_less()
{
    print_header("test_operator_less");
    // Create one CMidiPacket that will fail this test
    // After first successful compile add several more
    // try to find a false positive (bad data gets through)
}
void testInvariants()
{
    testConstructorOneDataByte();
    testConstructorTwoDataBytes();
    testStringConstructor();
    test_set_timestamp();
    test_set_status();
    test_set_data1();
    test_set_data2();
    test_set_midi_channel();
    test_output_operator();
    test_input_operator();
    test_operator_equals();
    test_operator_less();
}

hw332_testInvariants.h

// hw332_testInvariants.cpp
#ifndef HW332_TESTINVARIANTS_H
#define HW332_TESTINVARIANTS_H

void testInvariants();

#endif

Add the invariant_check() function to multiple common/hw332_CMidiPacket.cpp functions as shown

// example
CMidiPacket::CMidiPacket()
    : timestamp_{0}, status_{0x80}, data1_{0}, data2_{0}, length_{3}
{
    invariant_check();
}

CMidiPacket::CMidiPacket(uint32_t ts, uint8_t st, uint8_t d1, uint8_t d2)
// similar

CMidiPacket::CMidiPacket(const std::string &str)
// at end after CMidiPacket is constructed

std::string CMidiPacket::to_string() const
// just before return

All getters
// not needed

All setters
// last statement before closing brace

All Utility functions
// not needed

operator<<
// first statement
// mp.invariant_check();

operator>>
// just before return
// mp.invariant_check();

operator==
// first statements
// a.invariant_check();
// b.invariant_check();

operator<
// first statements
// a.invariant_check();
// b.invariant_check();

Build and Run You'll need to fix the above files so you get a clean compile.

cd $HOME312/hw33/hw332_testInvariants/build
cmake .. && make && ./hw332_testInvariants

Output

===> testConstructorOneDataByte <====

===> testConstructorTwoDataBytes <====

===> testStringConstructor <====

===> test_set_timestamp <====

===> test_set_status <====

===> test_set_data1 <====

===> test_set_data2 <====

===> test_set_midi_channel <====

===> test_ouput_operator <====

===> test_input_operator <====

===> test_operator_equals <====

===> test_operator_less <====

Not very interesting.

Assignment hw332

Add lots of bad data examples to hw332_testInvariants.cpp

  • Deliberately enter bad data
  • Enter several examples
  • Try to find a false positive where bad data gets through
  • Test for bad timestamp, status, data1, data2, length

Here's an example

// Constructors
void testConstructorOneDataByte()
{
    print_header("testConstructorOneDataByte");
    CMidiPacket mp1{1000, 0xC0, 11, 100}; // bad length
}

Example of bad data error report
The error message reported will vary depending on the error encountered.

===> testConstructorOneDataByte <====
ERROR: Bad Cn MidiPacket length


===> testConstructorTwoDataBytes <====
ERROR: Timestamp exceeds 30 minutes in milliseconds


===> testStringConstructor <====
ERROR: Data1 out of range. Must be 0 - 127


===> test_set_timestamp <====
ERROR: Timestamp exceeds 30 minutes in milliseconds


===> test_set_status <====
ERROR: Status out of range. Must be 0x80 - 0xFF

# 0xFn status_ not processed
ERROR: 0xFn messages not supported


===> test_set_data1 <====
ERROR: Data1 out of range. Must be 0 - 127


===> test_set_data2 <====
ERROR: Data2 out of range. Must be 0 - 127


===> test_set_midi_channel <====

===> test_output_operator <====
ERROR: Bad Bn MidiPacket length

ERROR: Bad Bn MidiPacket length

1000    b0      7

===> test_input_operator <====
ERROR: Data2 out of range. Must be 0 - 127

ERROR: Data2 out of range. Must be 0 - 127

ERROR: Data2 out of range. Must be 0 - 127


===> test_operator_equals <====
ERROR: Data1 out of range. Must be 0 - 127

ERROR: Data1 out of range. Must be 0 - 127


===> test_operator_less <====
ERROR: Status out of range. Must be 0x80 - 0xFF

ERROR: Status out of range. Must be 0x80 - 0xFF

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_LastnameFirstname1_LastnameFirstname2.
Substitute your name and your partner's name for LastnameFirstname1_LastnameFirstname2.

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%).
  • Each partner must submit identical homework folders to the course Hand-in folder.
  • If only one partner submits the homework send me an email explaining why.
  • 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
    
hw33_LastnameFirstname_LastnameFirstname
├── bin
│   └── check_timestamps
├── common
│   ├── hw322_CMidiPacket.tgz
│   ├── hw332_CMidiPacket.cpp
│   └── hw332_CMidiPacket.h
└── hw33
    ├── c331_checkTimestamps
    │   ├── CMakeLists.txt
    │   ├── build
    │   ├── hw331_checkTimestamps.cpp
    │   ├── hw331_checkTimestamps.h
    │   └── hw331_main.cpp
    ├── hw331_checkTimestamps
    │   ├── CMakeLists.txt
    │   ├── build
    │   ├── hw331_checkTimestamps.cpp
    │   └── output.txt
    └── hw332_test_invariants
        ├── CMakeLists.txt
        ├── build
        ├── hw332_main.cpp
        ├── hw332_testInvariants.cpp
        └── hw332_testInvariants.h

Author: John Ellinger

Created: 2020-01-24 Fri 19:50

Validate