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

Table of Contents

3.2 Class

Folder structure

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

/Volumes/COURSES/cs312-00-w20/StuWork/email_name
$HOME312 = above.
Inside $HOME312 are three folders
bin
common
cs312

Inside bin are things like ascii and boilerplate
Inside common are things like hw222_CMidiPacket.h
Inside cs312 are all your hwNNN assignment folders

There should be no bin folder or common folder inside cs312.

Bulletproofing CMidiPacket

One of the goals of the CMidiPacket class is make it as immune to errors as possible. There are three areas we're going to test.

Static check of function results from all functions declared the the class
We did that using googletest does.
Ensure that the timestamps of a vector<CMidiPacket> appear in chronological order when sorted
Sorting uses the function of operator<() and is one of the trickiest functions to get right.
The hw322_timestampCheck homework will help you check your operator<() function.
Tracing data through the debugger will also help.
That's hw322_timestampCheck
Add Invariant checks to CMidiPacket
Invariants are coding guarantees that your data structures will never be in an illegal state. If the data is invalid report an error. We'll get to that next class.

Homework 3.2

Reference

Googletest
The docs folder inside the googletest folder you downloaded.
Googletest homepage
https://github.com/google/googletest

c321_googletest_install

Necessary for hw322_CMP32gtest

Setup googletest

Download and build googletest
Google's open source project googletest was developed to test their own C++ software.
googletest is hosted on GitHub googletest.

The following shell script will

  • download googletest into your home directory
  • build with cmake

Copy, paste, and execute in Terminal.

cd $HOME312
git clone https://github.com/google/googletest
cd googletest
mkdir build
cd build
cmake .. && make

Copy the necessary googletest files into common folder.

cd $HOME312/common
mkdir googletest
cd googletest
mkdir lib
mkdir include
cd $HOME312/googletest
cp build/lib/libgtest_main.a $HOME312/common/googletest/lib
cp build/lib/libgtest.a $HOME312/common/googletest/lib
cd googletest/include  # yes there is a googletest/googletest/include
cp -R gtest $HOME312/common/googletest/include

This is what you should have in the common folder.

$HOME312/common/googletest/
├── include
│   └── gtest
│       ├── gtest-death-test.h
│       ├── gtest-matchers.h
│       ├── gtest-message.h
│       ├── gtest-param-test.h
│       ├── gtest-printers.h
│       ├── gtest-spi.h
│       ├── gtest-test-part.h
│       ├── gtest-typed-test.h
│       ├── gtest.h
│       ├── gtest_pred_impl.h
│       ├── gtest_prod.h
│       └── internal
│           ├── custom
│           │   ├── README.md
│           │   ├── gtest-port.h
│           │   ├── gtest-printers.h
│           │   └── gtest.h
│           ├── gtest-death-test-internal.h
│           ├── gtest-filepath.h
│           ├── gtest-internal.h
│           ├── gtest-param-util.h
│           ├── gtest-port-arch.h
│           ├── gtest-port.h
│           ├── gtest-string.h
│           └── gtest-type-util.h
└── lib
    ├── libgtest.a
    └── libgtest_main.a

googletest documentation

Documentation for google test is found in the googletest/docs folder in $HOME312

hw322_CMP32gtest

We compiled googletest earlier but did not implement any files that use it. The testing we'll do will verify that return values of CMidiPacket functions are what we expect them to be. There's still additional testing we'll do in later homework. For example googletest does not check whether function parameters are valid. The goal of testing is to make CMidiPacket as bulletproof as possible.

Setup

Download
hw322_CMP32gtest.zip

Execute in Mac Terminal

mkdir $HOME312/cs312/hw32
cd $HOME312/cs312/hw32
unzip ~/Downloads/hw322_CMP32gtest.zip -d $HOME312/cs312/hw32/
cd $HOME312/cs312/hw32
rm -fR  __MACOSX
cd hw322_CMP32gtest

Open hw322_CMP32gtest in vsCode

Modifying CMakeLists.txt for googletest
Gtest uses its own int main() function contained its own link library, common/google_test/lib/libgtest_main.a.
Because we want to be able to compile the program with or without googletest we'll use of one CMakeLists.txt file and two helper. We'll use copy/paste to change builds.

  • CMakeLists.txt the regular cmake build file.
  • CMakeLists_normal.txt contains the normal build info.
  • CMakeLists_gtest.txt file contains the build info for googletest

CMakeLists_gtest.txt
Copy/paste into CMakeLists_gtest.txt.

# CMakeLists_gtest.txt

cmake_minimum_required(VERSION 3.0)
set(APPNAME "gtestCMP32")
project(${APPNAME})

# JE sets to 0
# class labs sets 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(GTEST_INC "${HOME}/common//googletest/include/")
set(GTEST_LIB "${HOME}/common/googletest/lib")
include_directories(${GTEST_INC})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -std=c++17 -Wall ")
set(SOURCE_FILES
  hw322_CMidiPacket.cpp
  hw322_gtests.cpp
)
add_executable(${APPNAME} ${SOURCE_FILES})
set(LINK_LIBS
    ${GTEST_LIB}/libgtest_main.a
    ${GTEST_LIB}/libgtest.a
)
target_link_libraries(${APPNAME} ${LINK_LIBS})
add_test(NAME CMP32_test COMMAND ${APPNAME})

CMakeLists_normal.txt
Copy/paste into CMakeLists_normal.txt.

# CMakeLists_normal.txt

cmake_minimum_required( VERSION 2.8 )
set( APPNAME "CMP32friends" )
project( ${APPNAME} )

set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -Wall " )
set( SOURCE_FILES
  hw322_CMidiPacket.cpp
  hw322_CMP32friends.cpp 
)

add_executable( ${APPNAME} ${SOURCE_FILES} )

Normal build without googletest
Copy/paste CMakeLists_normal.txt into CMakeLists.txt and build.

Output

# v_ordered
JE - this is operator "put to"
You need to write operator<<
... lots more
You need to write operator<<

# mixedup_order followed by sort
JE - this is operator less
You need to write operator<.
... lots more
You need to write operator<.

JE - this is operator "put to"
You need to write operator<<
... lots more
JE - this is operator less

If you've implemented the the operator<<, operator>>, and operator== copy your code to hw322_CMidiPacket.cpp and some of the errors will disappear.

vsCode squiggle errors

Click squiggle
Click light bulb
Add C++ include path except use full pathname for $HOME312
/Volumes/COURSES/cs312-00-w20/StuWork/email_name

${workspaceFolder}/**
$HOME312/common/googletest/**

Preparing for googletest

I've written the hw322_gtests.cpp file for you. Because googletest has no knowledge of our CMidiPacket, I added three helper functions that call googletest functions. They're useful for testing overloaded constructors and comparing CMidiPackets.

// JE functions for CMidiPacket
void CMP_EQUALS(const CMidiPacket &mp, uint32_t ts, uint8_t st, uint8_t d1);
void CMP_EQUALS(const CMidiPacket &mp, uint32_t ts, uint8_t st, uint8_t d1, uint8_t d2);
bool CMP_EQUALS(const CMidiPacket &mp1, const CMidiPacket &mp2);
// END JE functions for CMidiPacket

Here are some examples of how you would create your own googletest functions. googletest examples

// the first parameter is the test group name
// the second parameter is the test name
// you can have multiple tests in a group as long as their names are unique
// it will be reported as overload.constructor_two_data_bytes
TEST(overload, constructor_two_data_bytes)
{
    CMidiPacket mp{1001, 0x91, 61, 101};
    CMP_EQUALS(mp, 1001, 0x91, 61, 101);
}

TEST(overload, constructor_string1)
{
    std::string s{"100\tc0\t11"};
    CMidiPacket mp(s);
    // compares c style strings (char *str)
    EXPECT_STREQ(mp.to_string().c_str(), s.c_str());
}

TEST(io_input, operator_input2)
{
    std::string s = "1000\t90\t60\t100";
    std::istringstream iss(s);
    CMidiPacket A;
    iss >> A;
    EXPECT_EQ(A.to_string(), s);
}

TEST(is_status, is_note_off2)
{
    CMidiPacket mp2(1001, 0x91, 61, 0);
    EXPECT_TRUE(mp2.is_note_off());
}

TEST(big6, copy_constructor2)
{
    CMidiPacket C{5678, 0x98, 99, 100};
    CMidiPacket D(C);
    EXPECT_TRUE(CMP_EQUALS(C, D));
}

TEST(big6, move_assignment_operator2)
{
    CMidiPacket A{1001, 0x91, 61, 101};
    CMidiPacket B;
    B = std::move(A);
    EXPECT_TRUE(CMP_EQUALS(A, B));
}

TEST(less, lessthan4a)
{
    CMidiPacket A = {1000, 0x91, 61, 101};
    CMidiPacket B = {1000, 0x91, 60, 0};
    EXPECT_FALSE(A < B);
}

Replace CMakeLists.txt
Replace/copy/paste the contents of CMakeLists_gtest.txt into CMakeLists.txt.

Build and run

# cd build
emptybuildfolder.sh
cmake .. && make
./gtestCMP32

Output Tests are broken into categories. The category is the first parameter to the TEST function. This shows the number of tests in each category. The actual category results have been omitted.

At the end of the output you'll find a summary of PASSED/FAILED tests. Any tests that report FAILED need fixing in your hw322_CMidiPacket.cpp source code. The failed tests are all from the four friend functions.

[==========] Running 75 tests from 12 test suites.
[----------] Global test environment set-up.
[----------] 1 test from dc
[----------] 6 tests from big6
[----------] 5 tests from overload
[----------] 3 tests from to_string
[----------] 7 tests from get
[----------] 5 tests from set
[----------] 18 tests from is_status
[----------] 2 tests from io_out
[----------] 4 tests from io_in
[----------] 6 tests from eq
[----------] 5 tests from less_helper
[----------] 13 tests from less

[----------] Global test environment tear-down
[==========] 75 tests from 12 test suites ran. (6 ms total)
[  PASSED  ] 59 tests.
[  FAILED  ] 16 tests, listed below:
[  FAILED  ] io_out.operator_output1
[  FAILED  ] io_out.operator_output2
[  FAILED  ] io_in.operator_input1
[  FAILED  ] io_in.operator_input1a
[  FAILED  ] io_in.operator_input2
[  FAILED  ] io_in.operator_input3
[  FAILED  ] eq.operator_equals1
[  FAILED  ] eq.operator_equals2
[  FAILED  ] less.lessthan2
[  FAILED  ] less.lessthan3
[  FAILED  ] less.lessthan5
[  FAILED  ] less.lessthan8
[  FAILED  ] less.lessthan9
[  FAILED  ] less.lessthan11
[  FAILED  ] less.lessthan12
[  FAILED  ] less.lessthan13

If you've implemented the the operator<<, operator>>, and operator== copy your code to hw322_CMidiPacket.cpp and some of the errors will disappear.

operator less

You still need to implement operator less.

bool CMP32::operator<(const CMidiPacket &a, const CMidiPacket &b)

In future assignments we'll use CMidiPackets to create CMidiTracks. Then we'll create songs with multiple CMidiTracks. Then we'll need to merge and sort all the tracks for playback. It's essential to implement operator< (less) for playback to work correctly.

Operator less sorting rules

Rule 1
The lowest timestamp comes first.
Rule 2
When two messages have equal timestamps sorting becomes more difficult.

For equal timestamps
The primary messages are NOF and NON.
NOF messages should always come before NON.
Other messages vary.

NOF
ts 0x8n d1 d2
ts 0x9m d1 d2=0
NON
ts 0x9n d1 d2>0
Polyphonic Aftertouch
ts 0xAn d1 d2
After NON
Control Message
ts 0xBn d1 d2
After NON
Patch Change
ts 0xCn d1
Before NON
Channel Aftertouch
ts 0xDn d1 d2
After NON
Pitch Bend
ts 0xEn d1 d2
After NON

Pseudo code for the way I did it
You're free to do it an different way if it passes these tests.

hw322_CMidiPacket compiled with CMakeLists_normal.txt
in this test all timestamps must be in strict ascending order
hw322_CMidiPacket compiled with CMakeLists_gtest.txt
You must pass every one of the 75 tests.
// the most complicated overload in CMidiPacket
// This function will be called repeatedly by sort() until the vector is sorted
// if variable is_a_first = true it means CMidiPacket a will come before CMidiPacket #+BEGIN_EXAMPLE
// Remember if a == b then a is not less than b
bool CMP32::operator<(const CMidiPacket &a, const CMidiPacket &b)
{
  bool is_a_first = false;

  // Sorting rules when timestamps are equal
  // std::cout << "# A " << a << "# B " << b;
  if (a.timestamp_ < b.timestamp_)
  {
    // lowest timestamp_ always comes first.
    is_a_first = true;
  }
  else if (a.timestamp_ == b.timestamp_)
  {
    // std::cout << "# (a.timestamp_ == b.timestamp_)\n";
    // NOF comes before anything
    if (a.is_note_off())
    {
      switch (b.status_ & 0xF0)
      {
      case 0x90:
      case 0xA0:
      case 0xB0:
      case 0xD0:
      case 0xE0:
        is_a_first = true; // means NOF comes before the five above
        break;
      case 0x80: // if a==b, a not < b
      case 0xC0:
        is_a_first = false; // means NOF comes after the two above
        break;
      default:
        break;
      }
    }

    else if (a.is_note_on())
    {
      switch (b.status_ & 0xF0)
      {
        // use all cases 0x80-0xE0
        // decide when a comes before
        // decide when a comes after
      }
    }

    if (a.is_status_An())
    {
      switch (b.status_ & 0xF0)
      {
        // use all cases 0x80-0xE0
        // decide when a comes before
        // decide when a comes after
      }
    }

    if (a.is_status_Bn())
    {
      switch (b.status_ & 0xF0)
      {
        // use all cases 0x80-0xE0
        // decide when a comes before
        // decide when a comes after
      }
    }

    if (a.is_status_Cn())
    {
      switch (b.status_ & 0xF0)
      {
        // use all cases 0x80-0xE0
        // decide when a comes before
        // decide when a comes after
      }
    }

    if (a.is_status_Dn())
    {
      switch (b.status_ & 0xF0)
      {
        // use all cases 0x80-0xE0
        // decide when a comes before
        // decide when a comes after
      }
    }

    if (a.is_status_En())
    {
      switch (b.status_ & 0xF0)
      {
        // use all cases 0x80-0xE0
        // decide when a comes before
        // decide when a comes after
      }
    }
  }
  return is_a_first;
}

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
    

hw32_LastnameFirstname_LastnameFirstname

$HOME312/common
├── google_test
│   ├── include
│   │   └── gtest
│   │       ├── gtest-death-test.h
│   │       ├── gtest-matchers.h
│   │       ├── gtest-message.h
│   │       ├── gtest-param-test.h
│   │       ├── gtest-printers.h
│   │       ├── gtest-spi.h
│   │       ├── gtest-test-part.h
│   │       ├── gtest-typed-test.h
│   │       ├── gtest.h
│   │       ├── gtest_pred_impl.h
│   │       ├── gtest_prod.h
│   │       └── internal
│   │           ├── custom
│   │           │   ├── README.md
│   │           │   ├── gtest-port.h
│   │           │   ├── gtest-printers.h
│   │           │   └── gtest.h
│   │           ├── gtest-death-test-internal.h
│   │           ├── gtest-filepath.h
│   │           ├── gtest-internal.h
│   │           ├── gtest-param-util.h
│   │           ├── gtest-port-arch.h
│   │           ├── gtest-port.h
│   │           ├── gtest-string.h
│   │           └── gtest-type-util.h
│   └── lib
│       ├── libgtest.a
│       └── libgtest_main.a
////////////////////////////////////////
$HOME312/cs312/hw32
└── hw322_CMP32gtest
    ├── CMakeLists.txt
    ├── CMakeLists_gtest.txt
    ├── CMakeLists_normal.txt
    ├── build
    ├── hw322_CMP32friends.cpp
    ├── hw322_CMidiPacket.cpp
    ├── hw322_CMidiPacket.h
    └── hw322_gtests.cpp

Author: John Ellinger

Created: 2020-01-22 Wed 19:20

Validate