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

Table of Contents

2.1 Class Setup

  • Mount your course folder using the Go menu in Mac Finder:
    smb://courses.ads.carleton.edu/COURSES/cs312-00-w20
  • Open Mac Terminal
  • Execute:
    setup312 <your_carleton_email_name>

Download
class21.zip

Execute in Mac Terminal

cd $HOME312/cs312
mkdir hw21
unzip ~/Downloads/class21.zip -d $HOME312/cs312/hw21/
cd $HOME312/cs312/hw21
rm -fR  __MACOSX
code $HOME312/cs312/hw21/class21

You should have these folders and files:

$HOME312/cs312/hw21
└── class21
    ├── c211_io
    │   ├── io01_global_struct.cpp
    │   ├── io02_boolalpha.cpp
    │   ├── io03_showpos.cpp
    │   ├── io04_showbase.cpp
    │   ├── io05_format1.cpp
    │   ├── io06_format2.cpp
    │   ├── io07_format3.cpp
    │   ├── io08_format4.cpp
    │   ├── io09_format5.cpp
    │   ├── io10_format6.cpp
    │   ├── io11_format7.cpp
    │   ├── io12_format8.cpp
    │   └── io13_stdcintest.cpp
    ├── c212_string
    │   ├── str01_initialize.cpp
    │   ├── str02_length_size.cpp
    │   ├── str03_copy_constructor.cpp
    │   ├── str04_assignment_operator.cpp
    │   ├── str05_append.cpp
    │   ├── str06_append_char.cpp
    │   ├── str07_chars_index.cpp
    │   ├── str08_chars_at.cpp
    │   ├── str09_assign.cpp
    │   ├── str10_insert.cpp
    │   ├── str11_erase.cpp
    │   ├── str12_replace.cpp
    │   ├── str13_compare.cpp
    │   ├── str14_substr.cpp
    │   ├── str15_find.cpp
    │   └── str16_find_first_of.cpp
    ├── c213_sstream
    │   ├── ss01_dec_hex_convert.cpp
    │   ├── ss02_string_to_number.cpp
    │   ├── ss03_stringstreams.cpp
    │   ├── ss04_str2float.cpp
    │   ├── ss05_floating_point_precision.cpp
    │   └── ss06_binary_bitset.cpp
    ├── c214_pointers
    │   ├── ptr01_at_star.cpp
    │   ├── ptr02_elementAccess.cpp
    │   ├── ptr03_commonErrors.cpp
    │   ├── ptr04_set2null.cpp
    │   └── ptr05_doubleDelete.cpp
    └── deleteme.cpp

Create a vsCode user snippet

This useful utility will create the required functions of a C++ class. It will remain part of vsCode in the CMC lab but will disappear from WCC when you logout. You can always recreate it.

Choose Preferences/User Snippets from the Code menu.

CodeScreenSnapz016.png

Choose cpp (C++) from the popup window.

c21c01.png

You should see this text appear in the class.code-snippets window

c21c02.png

Add this code just above the closing brace. Picture below.h

  "class6": {
  // modified from: https://github.com/one-harsh/vscode-cpp-snippets
    "prefix": "class6",
    "body": [
        "class ${MyClass}",
        "{",
        "public:",
        "      ${MyClass}();",
        "      ~${MyClass}();",
        "      ${MyClass}(const ${MyClass} &) = default;",
        "      ${MyClass}(${MyClass} &&) = default;",
        "      ${MyClass} &operator=(const ${MyClass} &) = default;",
        "      ${MyClass} &operator=(${MyClass} &&) = default;",
        "",
        "private:",
        "      $1",
        "};"
    ],
    "description": "Code snippet for class"
  }
}

c21c03.png

Save and close the cpp.json file.

Open the deleteme.cpp file.

Type class6 in the deleteme.cpp window.
Momentarily pause.
The Snippet popup panel will appear.

c21c04.png

Type RETURN and the following text will appear in deleteme.cpp.

c21c05.png

Type TAB and all occurrences of MyClass will be highlighted.

c21c06.png

Type CMidiPacket and all selected items should change to CMidiPacket simultaneously.

c21c07.png

Type TAB again and you're done.

Note:
The keyword default is used to let the compiler to generate the code for you. I'll refer to these six functions as "The Big Six". There's more info about The Big Six in the Reading section.

Sinppet Reference
https://code.visualstudio.com/docs/editor/userdefinedsnippets

Class 2_1 lab examples

We'll look at four C++ libraries for manipulating and formatting the text. We'll be using many of these functions to produce the MIDI messages required by MIDIDisplay_DLS to play music.

  • <iostream> home of std::cout and std::cin plus many others.
  • <string> is like a vector of text characters with abilites to find, search, replace, and delete substrings.
  • <sstream> contains the std::stringstream class that adds put to << and get from >> capabilities to write/read to/from a string.
  • <iomanip> and adds formatting functions to <iostream>.

There are a lot of files to compile in this lab but they've all been written for you. The goal of this lab is to acquaint you with useful functions in these C++ libraries that you can use in future assignments. You do not have to memorize every function and it's parameters but you should try to remember the general features these libraries offer. You can always refresh your memory on the web.

General instructions for each folder/file

Open the file and read/study the code.
Compile and run the file using cl <filename> && ./a.out
Compare the output with the code until you understand it.
Go to the next example.

You should already know how to open and compile the files without detailed explanations. I've included some of the vsCode shortcuts I use in a few examples.

We'll begin with the the 13 examples in the c211_io folder.

<iostream> and <iomanip>

You've already used std::cout from the <iostream> library. In this section we'll look at several additional features. The <iomanip> library adds additional formatting functions that can be used with <iostream>.

Setup

Open vsCode Terminal. Shortcut: Control-backtick.
Type cd <space> in the terminal.
Click and drag the c211_io folder name in the left EXPLORER panel to the right of cd <space> in the terminal.

c21c08.png

Release the mouse button and type Return.

c21c09.png

The c211_io folder should now be the working directory.

# cd $HOME312/cs312/hw21/class21
cd c211_io
ls -1 # minus 1 for one column output
# output
# io01_global_struct.cpp
# io02_boolalpha.cpp
# io03_showpos.cpp
# io04_showbase.cpp
# io05_format1.cpp
# io06_format2.cpp
# io07_format3.cpp
# io08_format4.cpp
# io09_format5.cpp
# io10_format6.cpp
# io11_format7.cpp
# io12_format8.cpp
# io13_stdcintest.cpp

Follow these instructions for all files.

  • Open the file and study the code.
  • Compile and run the file using cl <filename> && ./a.out
  • Compare the output with the code until you understand it.

io01_global_struct.cpp

A common admonition you hear in programming is to avoid global variables. A common C++ technique is to put all globals in a struct, class, or namespace. This keeps the possibility of name collisions with third party code and libraries to a minimum. Adding a print() function to the struct or class let's you examine their values at any point in your program.

io02_boolalpha.cpp

  • std:boolalpha, std::noboolalpha
  • Display bool as true, false

io03_showpos.cpp

  • std:boolalpha, std::noboolalpha
  • Show the + sign for positive numbers.

io04_showbase.cpp

  • Decimal hex conversion with std::dec, std::hex
  • show the 0x hex prefix or not with std::showbase, std::noshowbase

The next several examples show several formatting options.
When the the <iomanip> library is needed listed in the #includes.

io05_format1.cpp

  • default std::cout

io06_format2.cpp

  • line up using quotes
  • line up using \t (tab char)
  • using spaces to offset the length of the number

io07_format3.cpp

  • std::setw()

io08_format4.cpp

  • std::setw() placement

io09_format5.cpp

  • std::setw() effects

io10_format6.cpp

  • std::right, std::setw
  • Adjusts the length of the strings to the left of std::setw(10) to 10 total characters by prepending spaces to the beginning of the string.

io11_format7.cpp

  • std::fixed
  • Line up the decimal ponts.

io12_format8.cpp

  • std::setfill(char)
  • Uses the char parameter to std::setfill(char) as the padding instead of the default space character.

io13_stdcintest.cpp

  • std::cin
  • uses the "get from" operator >> to read from std::cin.

Input
For example, a MIDIDisplay_DLS message.

1000 90 60 100 <Return>
2000 80 60 0 <Return>
<Control-D>

Output

You entered
1000  90  60  100
2000  80  60  0

Compile All Bash script

Setup
Click the New File icon to create a new file in the c211_io folder.

c21c10.png

Name it c211_compileAll.sh.

c21c11.png

Copy/paste/save into c211_compileAll.sh

#!/bin/bash
# change working directory
cd $HOME312/cs312/hw21/class21/c211_io

# name the output file
OUTFILE=c211_all_IO.txt

# io13_stdcintest.cpp will hang the output waiting for input
# prevent that by renaming the file
mv io13_stdcintest.cpp io13_stdcintest.Xcpp
# loop through every .cpp file in the current directory ./
for file in ./*.cpp
do
   # append the file name to outfile
   echo "== $file ==" >> $OUTFILE
   # aliases like cl do not work in bash scripts
   # so use what cl expands to
   # run a.out and append the output to outfile
   clang++ -std=c++17 -Wall $file && ./a.out >> $OUTFILE
   # append a newline to outfile
   echo ""  >> $OUTFILE #newline
done
# name io13_stdcintest.cpp back
mv io13_stdcintest.Xcpp io13_stdcintest.cpp
code c211_all_IO.txt

Execute
Takes about 15 seconds.

# cd $HOME312/cs312/hw21/class21/c211_io
chmod 755 c211_compileAll.sh
./c211_compileAll.sh

<string>

Setup
In vsCode Terminal

# cd $HOME312/cs312/hw21/class21
cd c212_string
ls -1
# output
# str01_initialize.cpp
# str02_length_size.cpp
# str03_copy_constructor.cpp
# str04_assignment_operator.cpp
# str05_append.cpp
# str06_append_char.cpp
# str07_chars_index.cpp
# str08_chars_at.cpp
# str09_assign.cpp
# str10_insert.cpp
# str11_erase.cpp
# str12_replace.cpp
# str13_compare.cpp
# str14_substr.cpp
# str15_find.cpp
# str16_find_tab_chars.cpp

str01_initialize.cpp

  • Four string initialization methods.

str02_length_size.cpp

  • length
  • size

str03_copy_constructor.cpp

  • Copy constructor

str04_assignment_operator.cpp

  • Assignment operator

str05_append.cpp

  • append function
  • +
  • +=

str06_append_char.cpp

  • std::vector<char>
  • char to string
  • for loop output methods
  • print function output

str07_chars_index.cpp

  • index with []
  • no range checking
  • out of range is undefined

str08_chars_at.cpp

  • index with at()
  • compile time range checking

str09_assign.cpp

  • one string variable multiple assigns

str10_insert.cpp

  • insert strings within strings

str11_erase.cpp

  • erase sections of a string

str12_replace.cpp

  • replace sections of a string with other strings

str13_compare.cpp

  • compare strings by ASCII values

str14_substr.cpp

  • extract substrings from a string

str15_find.cpp

  • search for substring within a string
  • returns index position if found
  • returns std::str::npos if not found
  • std::str::npos == 0xffffffffffffffff
    (max unsigned 64 bit int)
    (-1 signed 64 bit int)

str16_find_tab_chars.cpp

  • could be used for finding the number of data bytes in a MIDIDisplay_DLS message.

<sstream>

Setup

cd $HOME312/cs312/hw21/class21
cd c213_sstream
ls -1
# output
# ss01_dec_hex_convert.cpp
# ss02_string_to_number.cpp
# ss03_stringstreams.cpp
# ss04_str2float.cpp
# ss05_floating_point_precision.cpp
# ss06_binary_bitset.cpp

Types of string streams

input and output
std::stringstream
output
std::ostringstream
input
std::istringstream

The next three examples require the <sstream> library. The string stream library provides the << and >> operators for strings much like <iostream> does for input output.

ss01_dec_hex_convert.cpp

  • function dec2hex()
  • function hex2dec()

ss02_string_to_number.cpp

  • std::stoi(string)
  • function str2num(const std::string &s, bool is_dec = true)

ss03_stringstreams.cpp

  • <sstream>
  • istringstream
  • ostringstream
  • std::to_string()
  • pass by reference to return more than one variable

The next two examples deal with floating point accuracy and precision.

ss04_str2float.cpp

  • std::stof()
  • std::stod()
  • default number of digits displayed for both float and double is 6 split between left and right of decimal point

ss05_floating_point_precision.cpp

  • std::setprecision(24)
  • float accurate to 6 digits after decimal point
  • double accurate to 15 digits after decimal point
  • Both types have 24 bits of precision but differ in accuracy.
    float has 6 bits of accuracy after the decimal point.
    double has 15 bits of accuracy after the decimal point.

ss06_binary_bitset.cpp

  • display number in binary
  • bitset<N> b(number)
  • bitset<N> b("10…")
  • b.to_string()

Pointers

A variable has a specific address in memory. A pointer can access that variable by dereferencing the address. The symbol * is used both for delcaring a pointer and dereferencing a pointer.

Setup
In vsCode Terminal

# cd $HOME312/cs312/hw21/class21
cd c214_pointers
ls -1
# output
# ptr01_at_star.cpp
# ptr02_elementAccess.cpp
# ptr03_commonErrors.cpp
# ptr04_set2null.cpp
# ptr05_doubleDelete.cpp

ptr01_at_star.cpp

  • & is the "address of <data>" operator
  • * is the "data at <address>" operator
  • int* p = &n;
    &n is the address of the memory location where 2020 is stored
    *p is the value 2020
    *(&n) is also the value 2020

ptr02_elementAccess.cpp

Class object
use . to access elements as in obj.data
Pointer to Class object
use -> to access elements as in obj->data
use (*). to access elements as in (*obj).data

ptr03_set2null.cpp

  • always set a pointer to nullptr immediately after you delete it.

ptr04_doubleDelete.cpp

  • avoid deleting the same pointer twice.

Reading

TCCP2

Basics
§1.10
Pointers
§1.7-1.7.1
Class
§2.3, 4.1-4.2, 4.6 [1-10]
Namespace
§3.4
Functions
§3.6-3.6.2
Libraries
§8.1-8.2
Strings
\9.1-9.2
IO
§10.1-10.6, 10.8
Array
§13.4.1
Bitset
§13.4.2

C++ classes and the big six

All C++ classes should have these six basic functions. Let's call them "The Big Six."

  • default constructor
  • destructor
  • copy constructor
  • move copy constructor
  • assignment operator
  • move assignment operator

Constructor

All constructors have the same name as the class name. The constructor is one of only two functions in all of C++ that does not declare a return type. The other is the destructor whose class name is prefixed with ~.

Default Constructor

The default constructor takes no parameters is responsible for initializing all class data members to a default value. Constructors are often overloaded. The CMidiPacket class has a default constructor and three overloaded constructors.

// default constructor
CMidiPacket();

// constructor for a one data byte message
CMidiPacket(uint32_t ts, uint16_t st, uint16_t d1);

// constructor for a two data byte message
CMidiPacket(uint32_t ts, uint16_t st, uint16_t d1, uint16_t d2);

// constructor to initialize a CMidiPacket from a valid MIDIDisplay string
CMidiPacket(const std::string& str);

Constructor initialization

The preferred method of initializtion uses a colon immediately after the closing function parameter parentheses followed by a comma separated list if member data variables in the order they were declared in the class. The function body can be empty if all member variables were initialized in the colon list.

// Header: hw213_CMidiPacket.h
  CMidiPacket();

// Implementation: hw213_CMidiPacket.cpp
CMidiPacket::CMidiPacket()
    : timestamp{0}, length{3}, status{0x80}, data1{0}, data2{0}
{
  // empty body
}

Destructor

The destructor shares the same name as the class prefixed with the tilde symbol ~. The job of the destructor is to deallocate any memory your class has allocated. If your class allocates memory either with operator new or with the deprecated malloc() family of functions you will have to implement a destructor to delete or free the memory you've used. If your class doesn't allocate memory the default destructor should suffice.

~CMidiPacket() = default;

Copy Constructor

The copy constructor is used whenever a class object is passed as a parameter. If the class contains a pointer variable pointing to memory allocated by the class, only the pointer is copied, not the data the pointer points to. That can lead to two objects sharing the same data. If one of the objects is deleted and it's destructor deallocates the memory, the remaining object's pointer is no longer valid. It is up to the programmer to copy the data in memory so that both objects have their own copy. This is not a problem with CMidiPacket becuase it does not allocate memory.

  CMidiPacket A; // packet A is initialized to default values

// Copy constructor -  packet B will be a clone of A
  CMidiPacket B(A);

Move copy constructor

This almost never needed in user code. It is used in the C++ libraries if the compiler can figure out whether a simple copy of pointers can be done rather than making a deep copy of the entire objects memory block. It's much faster to swap two 8 byte (64 bit addressing) pointers than it is to copy 100 Megbytes of data from one object to another. You won't need it in this class.

Assignment Operator

The assignment operator is the symbol = .

CMidiPacket& operator=(const CMidiPacket& other) = default;

Packet B is a clone of packet A.

CMidiPakcet A;
CMidiPacket B;
// Every data member of B is set to the value of the corresponding data member in A.
B = A; // assignment

Move Assignment Operator

The same comments for the move assignment operator apply here.

Which of the big six functions should I always write?

In general you should always write:

  • a default constructor to set data member to valid default values.

* a destructor (can be =default if you don't allocate memory using new)

  • a copy constructor
  • an assignment operator

If you decide to just declare two or three of the big six functions, the compiler may not generate the others. It's best to declare all six and indicate that you're willing to let the compiler generate them for you by using the keyword default. That way someone else reading your code will know you didn't accidentally forget them.

Homework 2.1

Setup

Quit vsCode if it is open.

Execute in Mac Terminal.

cd $HOME312/cs312/hw21
mkdir hw211_hexIntToString
cd hw211_hexIntToString
touch hw211_hexIntToString.cpp
cd ..
mkdir hw212_CMidiPacket
cd  hw212_CMidiPacket
touch hw212_main.cpp
touch hw212_CMidiPacket.cpp
touch hw212_CMidiPacket.h
code $HOME312/cs312/hw21

You should have this directory structure.

$HOME312/cs312/hw21
├── class21
├── hw211_hexIntToString
│   └── hw211_hexIntToString.cpp
└── hw212_CMidiPacket
    ├── hw212_CMidiPacket.cpp
    ├── hw212_CMidiPacket.h
    └── hw212_main.cpp

hw211_hexIntToString.cpp

Setup
Open vsCode Terminal (Control-backtick)

Execute

cd hw211_hexIntToString
code hw211_hexIntToString.cpp

Copy this code into hw211_hexIntToString.cpp.

int main() {
    int n = 0x90;
    {
        std::ostringstream oss;
        oss << std::hex << n;
        std::cout << oss.str() << std::endl;
    }

    {
        std::ostringstream oss;
        oss << std::hex << std::showbase << n;
        std::cout << oss.str() << std::endl;
    }
}

Add the appropriate #include libraries.

Compile and run

Output

90
0x90

Remove the four inner curly braces leaving only the two outer braces.

int main()
{
    int n = 0x90;
    std::ostringstream oss;
    oss << std::hex << n;
    std::cout << oss.str() << std::endl;

    std::ostringstream oss;
    oss << std::hex << std::showbase << n;
    std::cout << oss.str() << std::endl;
}

Compile the program.
You'll get an error because oss is declared twice.

hw211_hexIntToString.cpp:11:22: error: redefinition of 'oss'
std::ostringstream oss;
                     ^
hw211_hexIntToString.cpp:7:22: note: previous definition is here
std::ostringstream oss;
                     ^
1 error generated.

Remove the second std::ostringstream oss; statement.

Compile and run

Output
It's wrong.

90
900x90

Assignment 2.1.1

Fix the output keeping the first std::ostringstream variable.

This is the output you want to see.
It's a one added line fix.

90
0x90

Question:
What is the purpose of the extra curly braces in the original example?
Append your answer in a block comment at the end of hexIntToString.cpp.

hw212_CMidiPacket

This code will be used the next class.
Finish as much as you can. I chose MidiPacket2 form hw131_MidiPacket.h for use in CS312. I'll explain my choice in a future lecture.

Setup
Open hw212_CMidiPacket folder in vsCode.

# assuming you're in hw211_hexIntToString folder
cd ../hw212_CMidiPacket

Copy and paste the contents of these three files into their vsCode counterparts.

hw212_CMidiPacket.h

// hw212_CMidiPacket.h

#ifndef HW212_CMIDIPACKET_H_
#define HW212_CMIDIPACKET_H_

#include <iostream>
#include <string>

namespace CMP21
{
class CMidiPacket
{
public:
  // MidiPacket2 data
  uint32_t timestamp;
  uint8_t status;
  uint8_t data1;
  uint8_t data2;
  uint8_t length;

  // from class snippet
  CMidiPacket();                                         // constructor
  ~CMidiPacket();                                        // destructor
  CMidiPacket(const CMidiPacket &) = default;            // copy constructor
  CMidiPacket(CMidiPacket &&) = default;                 // move constructor
  CMidiPacket &operator=(const CMidiPacket &) = default; // assignment
  CMidiPacket &operator=(CMidiPacket &&) = default;      // move assignment

  // three overlaoded constructors
  // construct a CMidiPacket for a one data byte message
  CMidiPacket(uint32_t ts, uint8_t st, uint8_t d1);

  // construct a CMidiPacket for a two data byte message
  CMidiPacket(uint32_t ts, uint8_t st, uint8_t d1, uint8_t d2);

  // construct a CMidiPacket from a valid MIDIDisplay string
  CMidiPacket(const std::string &str);

  // return a string from this CMidiPacket data
  std::string to_string() const;

  // print this CMidiPacket data to std::cout
  // in MIDIDisplay format accounting for message lengths of 2 or 3
  void print() const;
};
} // namespace CMP21
#endif // HW212_CMIDIPACKET_H_

Note:
The namespace CMP21; directive.
The term const after the functions to_string() and print() indicates that no class variables will be modified within the function.

https://www.tutorialspoint.com/const-member-functions-in-cplusplus

hw212_CMidiPacket.cpp

// hw212_CMidiPacket.cpp

#ifndef HW212_CMIDIPACKET_H_
#include "hw212_CMidiPacket.h"
#endif

#include <iostream>
#include <string>
#include <sstream>
#include <vector>

using namespace CMP21;

// the C++ method of initializing constructors is to use a colon
// after the function declaration followed by a comma separated list
// of class data members using curly brace initialization
// the body of the constructor is often empty

// DO NOT CHANGE
// Default constructor
// Initializes a "do nothing" NOF message
CMidiPacket::CMidiPacket()
    : timestamp{0}, status{0x80}, data1{0}, data2{0}, length{3}
{
  // empty body
}

// Default destructor
// Note placement of ~
// no need to do anything because there was no memory allocation in class
// Alternatively you could use ~CMidiPacket() = default in the header file
// and let the compiler implement the destructor.
CMidiPacket::~CMidiPacket()
{
  // empty body
}
// END DO NOT CHANGE

// ******************************************************
// FROM HERE TO END YOU HAVE TO IMPLEMENT THESE FUNCTIONS
// ******************************************************

// Constructor overload for one data byte message
// use colon initialization with parameters inside curly braces
CMidiPacket::CMidiPacket(uint32_t ts, uint8_t st, uint8_t d1)
{
}

// Constructor overload for two data bytes message
// use colon initialization with parameters inside curly braces
CMidiPacket::CMidiPacket(uint32_t ts, uint8_t st, uint8_t d1, uint8_t d2)
{
}

// Constructor overload for string parameter
// Initialize the CMidiPacket data from a string parameter.
// The string is valid MIDIDisplay format.
// Remember status is hex without 0x prefix in the string.
// You'll need to find a parsing routine that will
// separate the string into tokens separated by whitespace
// any number of spaces or tabs
// assign the tokens to the data members
// ignore any status 0xFn
// remember status 0xCn and 0xDn are length 2 and do not use data2
// all other status are length 3 and use both data1 and data2
// assign the length once you know the status
CMidiPacket::CMidiPacket(const std::string &str)
{
}

// Convert the CMidiPacket data to a string.
// Separate numbers with a single tab char.
// Account for 1 data byte or 2 data byte messages.
// <timestamp>TAB<status(hex)>TAB<data1>
// <timestamp>TAB<status-hex>TAB<data1>TAB<data2>
// Status is hex number without 0x prefix.
// Length should be set at the same time as status.
// Length will never be displayed in a MIDIDisplay message.
// timestamp, data1, and data2 (if used) are decimal numbers.
// send a not processed message if status is 0xFn
std::string CMidiPacket::to_string() const
{
  // need to return a string to avoid compile error
  return "You need to implement CMidiPacket::to_string()";
}

// Do not change print()
// Send a MIDIDisplay string to std::cout using toString()
void CMidiPacket::print() const
{
  std::cout << to_string() << std::endl;
}

Note:
The using namespace CMP21; statement
When we add additional functions to CMidiPacket in future assignments we can simply change the namespace and not have to rename any functions.

hw212_main21.cpp

// hw212_main.cpp

#ifndef HW212_CMIDIPACKET_H_
#include "hw212_CMidiPacket.h"
#endif

using namespace CMP21;

int main()
{
  // test the four constructors
  CMidiPacket mp1;
  CMidiPacket mp2{0, 0xc0, 11};
  CMidiPacket mp3{0, 0x90, 67, 100};
  CMidiPacket mp4{"900\t80\t67\t0"};
  CMidiPacket mp5{1000, 0x90, 76, 100};
  CMidiPacket mp6{"1900\t80\t76\t0"};
  CMidiPacket mp7{2000, 0x90, 72, 100};
  CMidiPacket mp8{"2900\t80\t72\t0"};
  CMidiPacket mp9{"3000\tF0\t0"};

  // output messages using print()
  mp1.print();
  std::cout << "# length " << +mp1.length << std::endl;
  mp2.print();
  std::cout << "# length " << +mp2.length << std::endl;
  mp3.print();
  std::cout << "# length " << +mp3.length << std::endl;
  mp4.print();
  std::cout << "# length " << +mp4.length << std::endl;
  mp5.print();
  std::cout << "# length " << +mp5.length << std::endl;
  mp6.print();
  std::cout << "# length " << +mp6.length << std::endl;
  mp7.print();
  std::cout << "# length " << +mp7.length << std::endl;
  mp8.print();
  std::cout << "# length " << +mp8.length << std::endl;
  mp9.print();
  std::cout << "# length " << +mp9.length << std::endl;
}

Compile and Run

cl *.cpp && ./a.out

You'll see output similar to this.

you need to implement CMidiPacket::to_string()
# length 3
you need to implement CMidiPacket::to_string()
# length 0
you need to implement CMidiPacket::to_string()
# length 0
you need to implement CMidiPacket::to_string()
# length 0
you need to implement CMidiPacket::to_string()
# length 0
you need to implement CMidiPacket::to_string()
# length 0
you need to implement CMidiPacket::to_string()
# length 0
you need to implement CMidiPacket::to_string()
# length 0
you need to implement CMidiPacket::to_string()
# length 0

The reason the first CMidiPacket has length 3 is because it is the only one that called the default constructor.
All other CMidiPackets were initialized by overloaded constructors and bypassed the default contructor.
C++ assigns a memory location for all data variables and their value will be whatever "junk" is at that location.
The zero values in this case were accidental.

MORAL:
Always initialize every class variable in the constructors, either default or overloaded.
Colon initialization is preferred but sometimes you may have to write an "initX()" function and call that in the body of a constructor.

Assignment 2.1.2

This is the output you want to see

0	80	0	0
# length 3
0	c0	11
# length 2
0	90	67	100
# length 3
900	80	67	0
# length 3
1000	90	76	100
# length 3
1900	80	76	0
# length 3
2000	90	72	100
# length 3
2900	80	72	0
# length 3
# 0xFn status not processed
# length 0

The only file you're allowed to change is hw212_CMidiPacket.cpp.
Implement all functions in hw212_CMidiPacket.cpp.
Hints are given below and in source code comments.

Default Constructor
The recommended method of constructor initialization is to us a colon after the function name's closing () and then initialize as many data members as possible inside curly braces in the order they were declared in the class interface. If the constructor takes parameters, their values are placed inside curly braces. The body of the function is often empty.

This has been done for you in the default constructor below.

// DO NOT CHANGE
// Default constructor
// Initializes a "do nothing" NOF message
CMidiPacket::CMidiPacket()
    : timestamp{0}, status{0x80}, data1{0}, data2{0}, length{3}
{
}

Destructor

// Default destructor
// Note placement of ~
// no need to do anything because there was no memory allocation in class
// Alternatively you could use ~CMidiPacket() = default in the header file
// and let the compiler implement the destructor.
CMidiPacket::~CMidiPacket()
{
}

Constructor overload 1 data byte message

// Constructor overload for one data byte message
// use colon initialization
CMidiPacket::CMidiPacket(uint32_t ts, uint8_t st, uint8_t d1)
{
}

Constructor overload 2 data bytes message

// Constructor overload for two data bytes message
// use colon initialization
CMidiPacket::CMidiPacket(uint32_t ts, uint8_t st, uint8_t d1)
{
}

Constructor overload 3 initialize from a MIDIDisplay format string

// Initialize the CMidiPacket data from a string parameter.
// The string is valid MIDIDisplay format.
// Remember status is hex without 0x prefix in the string.
// You'll need to find a parsing routine that will
// separate the string into tokens separated by whitespace
// any number of spaces or tabs
// assign the tokens to the data members
// ignore any status 0xFn
// remember status 0xCn and 0xDn are length 2 and do not use data2
// all other status are length 3 and use both data1 and data2
// assign the length once you know the status
CMidiPacket::CMidiPacket(const std::string &str)
{
  // you implement
}

to_string() converts the class data to a MIDIDisplay string

// Convert the CMidiPacket data to a string.
// Separate numbers with a single tab char.
// Account for 1 data byte or 2 data byte messages.
// <timestamp>TAB<status(hex)>TAB<data1>
// <timestamp>TAB<status-hex>TAB<data1>TAB<data2>
// Status is hex number without 0x prefix.
// Length should be set at the same time as status.
// Length will never be displayed in a MIDIDisplay message.
// timestamp, data1, and data2 (if used) are decimal numbers.
// send a not processed message if status is 0xFn
std::string CMidiPacket::to_string() const
{
  // necessary to return some string to avoid a compile error
  return "You need to implement CMidiPacket::to_string()";
}

print_midi_message() sends a MIDIDisplay string to std::cout

// Do not change print()
// Send a MIDIDisplay string to std::cout using toString()
void CMidiPacket::print_midi_message()
{
  std::cout << "You need to implement CMidiPacket::print_midi_message()\n";
}

This is the output you want to see

0	80	0	0
# length 3
0	c0	11
# length 2
0	90	67	100
# length 3
900	80	67	0
# length 3
1000	90	76	100
# length 3
1900	80	76	0
# length 3
2000	90	72	100
# length 3
2900	80	72	0
# length 3
# 0xFn status not processed
# length 0

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
    

hw21_LastnameFirstname1_LastnameFirstname2

hw21_LastnameFirstname1_LastnameFirstname2
├── hw211_hexIntToString
│   └── hw211_hexIntToString.cpp
└── hw212_CMidiPacket
    ├── hw212_CMidiPacket.cpp
    ├── hw212_CMidiPacket.h
    └── hw212_main.cpp

Author: John Ellinger

Created: 2020-01-14 Tue 22:29

Validate