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.
When the light bulb appears click the light bulb.
When the popup menu appears choose Edit "includePath" setting.
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
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