4.1 Class
CS 312 Audio Programming Winter 2020

Table of Contents

Class 4.1

Notes

Efficient operator<< and operator>> code James Gardner and Shannon Liu

/****************************** FRIENDS ******************************/
// const ref parameter because mp will not be changed
std::ostream &CMP31::operator<<(std::ostream &os, const CMidiPacket &mp)
{
  std::string s;
  s = mp.to_string();
  os << s << std::endl;
  return os;
}

// removed const from mp because mp will be changed when reading from input stream
std::istream &CMP31::operator>>(std::istream &is, CMidiPacket &mp)
{
  std::string s;
  getline(is,s);
  mp = CMidiPacket(s);
  return is;
}

Today's assignment will create two classes, an abstract base class called CMidiTrack and a subclass of CMidiTrack called CScalesTrack. I'd suggest you read the following section before starting hw411_CScalesTrack in lab.

Reading

Music vocabulary

MIDI Dynamics

In music dynamics refers to loud and soft. Dynamics may affect a single note like an accent; a two note group like a slur; or a long phrase with a crescendo and decrescendo. Dynamics define the meter and rhythmic pulse of the music by accent patterns in groups of two, three, or four notes. The dynamic contour of a phrase makes the music expressive. Dynamics also refers to the volume balance of one instrument compared to another in a mix.

Three Methods to Control Dynamics

MIDI provides three methods to control dynamics
MIDI NON velocity MIDI Volume control messages MIDI Expression control messages

Why three methods for controlling volume
Here's a hypothetical situation. You decide to use NON velocity values for all dynamic changes in instrument one. You then add a second instrument and discover that instrument two overpowers instrument one and you need to increase all the velocities of instrument one. You then add instrument three and it overpowers instrument two so you reduce all velocities in instrument three. Later you decide you want instrument three to use a different sound but now the velocities are too soft. It gets worse as you add or change instruments. Wouldn't it be nice if every instrument had its own volume control knob that could control the balance of sound at the beginning of a song. That's the job of the Volume Control (CC7) message.

You're feeling good. All instruments are balanced and you can hear the rhythmic drive in each part. Now you'd like to make each instrument more expressive by adding crescendos and decrescendos to the phrases but you don't want to change the velocities that provide the rhythmic drive and you don't want to affect the balance too much. Wouldn't it be nice if there was a separate control knob for each instrument that you could move up or down to shape the soft/loud contour in each part. That's the job of the Expression Control message. By inserting expression messages spread out between NON/NOF messages you can create musically expressive phrases without changing the velocity.

Guidelines

NON velocity

Use velocity to produce single note accents and rhythmic drive in each instrument. Higher velocities are used on the strong beats and lower velocities are used on the weak beats. Note velocity sometimes affects the timbre of a note. Modern sample libraries have multiple waveforms for each note depending on the velocity. A piano library may contain samples of each note six different dynamic levels from pp to ff and choose samples based on velocity. A string instrument might use velocity levels to change bowing styles. A trumpet might create a growl effect within a certain velocity range.

Volume Control (0xBn 7 data2)

MIDI Volume Control (CC7) should be thought of as the master volume control. It is used at the beginning of the song to set the initial balance between instruments.

Expression Control (0xBn 11 data2)

MIDI Expression Control (CC11) should be thought of as a local volume control for a single instrument that can be used to create crescendos and decrescendos. It can also be used to shape the swell and decay of a long sustained note.

Scales

The hw411_CScalesTrack defines scales as a vector of numbers from 0-12. The numbers represent a span of 12 notes on the piano keyboard as shown below.

pianoKbd.png

The musical interval between two adjacent notes on the piano is called a half step. A musical octave contains 12 half steps and begins and ends on a note with the same letter name. The pattern of two black keys followed by three black keys repeats itself multiple times over the 88 notes on the piano.

The white keys have alphabet names C D E F G A B and repeat. The black keys have alphabet name plus a modifier like sharp or flat. For example, note zero is C, note 1 is C sharp (half step higher) or D flat (half step lower). You can remember the note names by remembering C is always the note to the left of the two black keys.

The chromatic scale is the scale of half steps.

  • Chromatic scale using sharps (#): C C# D D# E F F# G G# A A# B C…
  • Chromatic scale using flats (b) : C Db D Eb E F Gb G Ab A Bb B C…

The MIDI note number range for the 88 piano keys is from 24-108. The scale pattern vectors have to be offset to fall within the piano range. If you want your scales to start on C you'd use a multiple of 12 for the offset. If you wanted your scales to start on G you'd use a multiple of 12 offset and then add 7.

I've defined these scale patterns in hw411_CScalesTrack.h.

  0 1 2 3 4 5 6 7 8 9 10 11 12
Major pentatonic 0   2   4     7   9     12
Minor pentatonic 0     3   5   7     10   12
Ragtime 0   2 3 4     7   9     12
Rock blues 0     3 4     7   9 10   12
Minor blues 0     3   5 6 7     10   12
6 note blues 0     3   5 6 7     10   12
7 note blues 0   2 3   5 6     9 10   12
Middle Eastern 0 1     4 5 6   8     11 12
Japanese 0 1       5   7 8       12
Octotonic jazz 0 1   3 4   6 7   9 10   12
Whole tone 0   2   4   6   8   10   12
Flamenco 0 1     4 5   7 8     11 12
Phrygian dominant 0 1     4 5   7 8   10   12

You can find many more patterns on this Wikipedia page. Feel free to add your own.

https://en.wikipedia.org/wiki/Scale_(music)

Measures, beats, rhythm, tempo

The structure of many songs follow a predefined form based on the number of measures and the number of beats within a measure. You may have heard the term "12 bar blues." Measure and bar are synonyms. The blues form follows a repeating cycle of 12 measures.

Measures

Measures are classified based on the number of beats per measure. Most songs keep the same number of beats in every measure.

Duple meter
Two beats per measure.
Triple meter
Three beats per measure.
Quadruple meter
Four beats per measure.

This mp3 plays four measures in quadruple time, triple time, and duple time. The accent is on the first beat of each measure. You'll hear four accents in each example. The number of beats in each example changes.

QuadTripleDuple meter.mp3

Beat

A beat is a uniformly spaced unit of time that exists external to the music. When you tap your foot to the music you are instinctively following the beats.

You may have heard the music note value terms half note, quarter note, eighth note, etc. One note value, usually the quarter note, is assigned as the beat unit. In CS312 we'll assume the quarter note is the beat unit and has a duration of 1000 ms at a tempo of 60. Here's a table of music note values and durations compared to the quarter note. The table does not show the 64th or 128th note that also exist in music notation.

Note type Number of beats Duration
Whole note 4 4000
Half note 2 2000
Quarter note 1 1000
Eighth note 1/2 500
16th note 1/4 250
32nd note 1/8 125

Rhythm

Rhythm is the organization of beats into repeating patterns. Spoke text also has rhythm. Consider saying "go to the store". You can hear it as a long-short-short-long (go to-the store) pattern. In music that rhythmic pattern could be represented as quarter eighth eighth quarter notes. Rhythm is independent of the tempo. hw411_CScalesTrack uses this rhythm.

Tempo

Tempo refers to the speed of the beat and is measured in beats per minute. At a tempo of 60 bpm each quarter note beat lasts one second. The following table displays the single "long-short-short-long" millisecond note durations at different tempos. The tempo (speed) changes but the rhythm stays the same.

  long short short long Tempo bpm
Very slow 4000 2000 2000 4000 15
Slow 2000 1000 1000 2000 30
Medium 1000 500 500 1000 60
Faster 500 250 250 500 120
Very fast 250 125 125 250 240

Here's how it sounds from Slow to Very fast.

rhythm.mp3

CMidiTrack class

A professional music recording studio uses multiple microphones to record a musical group. Let's say the group consists of five musicians, voice, piano, bass, guitar, and drums with a microphone for each instrument. Each microphone is connected to a mixing console (Mixer) that records each instrument to a separate track. After recording the mixing engineer can process the tracks to balance the volume of each instrument for the final mix. Additionaly the Mixer has volume controls for each track that can be motorized and programmed to make an individual instrument follow its own volume curve. The Mixer also has stereo panorama (pan) controls that position each instrument left to right in space. Drums are often placed in the center with other instruments on either side.

We'll create a CMidiTrack class that will represent a single instrument. MIDI supports 16 channels meaning a MIDI song can have up to 16 instruments playing at once with each instrument assigned to a different channel. Remember channel 9 (zero based) is reserved for drums. Once you've assembled your tracks you can send MIDI control messages to manipulate the tracks similar to the studio mixing console described above.

MIDI Control commands

0x90 data1 data2
(NON data1 = MIDI note number, data2 = velocity (volume),
0xBn 7 data2
the MIDI control message for volume (0-127),
0xBn 11 data2
the MIDI control message for expression (0-127),
0xBn 10 data2
the MIDI control message for pan (0 = left, 64 = center, 127 = right and values in between),
0xBn 64 data2
the MIDI control message for piano pedal (0-63 = up, 64-127 = down) only two values needed, 0 and 127).
0xBn 121 0
used at the end of the song to set controllers back to their default state.

CMidiTrack capabilities

The CMidiTrack class is used to represent a complete track for a single instrument. At a minimum it should include these capabilities.

Timestamps
Beginning and ending timestamps.
Not all tracks begin and end at the same time.
MIDI channel (MIDI track)
There are 16 MIDI channels available.
Each channel represents a separate track.
Channel 9 (zero based) is reserved for the drum track.
MIDI NON messages for Note on
0x9n data1 data2
n is MIDI channel
data1 is MIDI note number (pitch)
data2 is velocity (higher is louder)
MIDI NOF messages for Note off
Turns a previous NON message off provided data1 matches.
There are two distinct note off messages.
0x8n data1 data2
data2 is ignored

0x9n data1 0
Turning a note on with a velocity of zero is the same as a NOF
I recommend using the 0x8n status with data2 set to zero
MIDI messages for patch change
0xCn data1 (no data2) There are 128 (0-127) General MIDI instruments available.
Use patch change messages to change instruments.
Use one instrument per track.
MIDI messages for Volume
0xBn 7 data2 data1 = 7 for volume.
data2 = 0-127 from softest to loudest.
Think of it like the knob you used to turn up the volume on a radio.
Every MIDI channel has its own volume knob.
Volume is often used to balance overall instrument levels within the final mix
Volume messages can be sent at any time, especially at the beginning.
MIDI messages for Expression
0xBn 11 data2
data1 = 11 for expression.
data2 = 0-127 from softest to loudest.
Think of it as linear fader on a mixer that you can move up or down while the song is playing.
Every MIDI channel has its own fader control.
Expression is used to control the volume curves of a single instrument.
Expression messages can be sent at any point in the time.
MIDI messages for Pan
0xBn 10 data2 data1 = 10 for pan.
data2 = 0-127 for position. 0 is extreme left, 64 is center, and 127 is extreme right.
Think of it as the Left/Right balance control between speakers.
Pan is used to position instruments in the stereo field.
Pan messages can be sent at any time.
Message Storage
We'll use a std::vector<CMidiPacket>
The write_track() = 0 pure virtual function
Subclasses will implement their own write_track() function. For example a piano track can play chords while a melody track only plays one note at a time.

Virtual functions

virtual void write_track();
The word virtual means "MAY be redefined later in a class derived from this one."
A virtual function can be overridden in subclasses.

Pure virtual function

virtual void write_track()=0;
The =0 syntax says the function is pure virtual and means "MUST be redefined later in a class derived from this one." A pure virtual function has no implementation in its .cpp class and must be overridden by a subclass.

Abstract base class

A class containing a pure virtual function is called an abstract base class. It cannot be instantiated but is often used to provide a set of interface functions that its subclasses inherit. Any functions defined and implemented in CMidiTrack will be available to its subclasses.

Base class and subclasses

In order to use multiple tracks it would be nice to encapsulate common features in a single base class. Then subclasses would inherit the common features from the base class and would only need to supply functions unique to the subclass.

Base class
Channel (1 per track).
Instrument (1 per track).
Message sending functions (all subclasses can use them).
Duration (determines max value of final timestamp).
Message storage.
Subclasses
Note offset 9each track might use a restricted range like soprano, alto, tenor, bass)
Scale type (each track might use a different scale type).
Rhythm (some tracks might use mainly fast notes, others slow notes).
Sublcass inherit the CMidiPacket storage vector but it's unique to each subclass.

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

cd $HOME312/common touch hw411_rand_int.h touch hw411_rand_int.cpp

Execute in Terminal

# hw411_random_int
cd $HOME312/cs312/
mkdir hw41 && cd hw41
mkdir hw411_rand_int && cd hw411_rand_int
touch hw411_rand_int.h
touch hw411_rand_int.cpp
touch hw411_main.cpp

# hw412_gmnames
cd $HOME312/cs312/hw41
mkdir hw412_gmnames && cd hw412_gmnames
touch instrumentGMcategories.txt
touch instrumentGMnames.txt
touch hw412_gmnames.cpp

#hw411_CScalesTrack
cd $HOME312/cs312/hw41
mkdir hw413_CScalesTrack && cd hw413_CScalesTrack
# CMake stuff
touch CMakeLists.txt
mkdir build
touch hw413_CMidiTrack.cpp
touch hw413_CMidiTrack.h
touch hw413_CScalesTrack.h
touch hw413_CScalesTrack.cpp
touch hw413_main.cpp

#hw413_measures_beats
cd $HOME312/cs312/hw41
mkdir hw414_measures_beats && cd hw414_measures_beats
mkdir build
touch CMakeLists.txt
touch hw414_CMeasuresBeatTrack.cpp
touch hw414_CMeasuresBeatTrack.h
touch hw414_main.cpp

hw411_random_int

Copy this code into the appropriate files.

hw411_rand_int.h

// hw411_rand_int.h
#ifndef HW411_RAND_INT_H_
#define HW411_RAND_INT_H_

extern int get_rand_int(int lo, int hi);

#endif // HW411_RAND_INT_H_

hw411_rand_int.cpp
The C rand() function is not allowed. Class web page 2.3 discussed C++ random functions. You're free to adapt code you wrote for class 2.3 homework. I had to make slight modifications to the random number code from TCPP2 section 14.5, page 191 in order to generate different random numbers.

Requirements:

  • You must use C++ functions.
  • Us std::uniform_int_distribution.
  • You must return a new number on every call.
  • Use header guards around your #include statements

hw411_main.cpp

#include <iostream>

#ifndef HW411_RAND_INT_H_
#include "hw411_rand_int.h"
#endif

int main(int argc, char const *argv[])
{
  for (int n = 0; n < 12; ++n)
    std::cout << get_rand_int(0, 12) << " ";
  std::cout << '\n';

  for (int n = 0; n < 12; ++n)
    std::cout << get_rand_int(55, 67) << " ";
  std::cout << '\n';

  for (int n = 0; n < 12; ++n)
    std::cout << get_rand_int(0, 1) << " ";
  std::cout << '\n';

  return 0;
}

My output (2x)

je$ cl *.cpp && ./a.out
2 10 10 9 7 4 7 0 1 11 8 2
63 65 64 55 63 58 62 65 64 60 67 55
0 1 1 0 0 0 0 1 1 0 0 0

je$ cl *.cpp && ./a.out
0 6 12 1 11 10 9 1 8 1 0 0
66 64 67 64 58 55 56 64 57 59 63 60
0 0 1 1 1 1 1 1 1 0 0 0

Assignment

  1. Implement hw411_rand_int.cpp.
  2. When you get valid output from main() copy these files to your course common folder.
# cd $HOME312/cs312/hw41/hw411_rand_int
cp  hw411_rand_int.cpp $HOME312/common
cp  hw411_rand_int.h $HOME312/common

hw413_CScalesTrack and hw414_measures_beats will both need to #include them from the /common folder.

hw412_gmnames

There are 128 General MIDI (GM) instruments available numbered from 0 to 127.
The 0xCn data1 patch change message is used to change instruments.

The 128 instruments are arranged into 16 categories with 8 instruments in each category. The basic idea behind GM is a standardized numbering and naming system. Instrument number 0 is always a piano, instrument 11 is a vibraphone, and instrument number 42 is a cello. The GM standard is supported by many different synthesizer manufacturers. However, the sound quality of each instrument can vary. Generally the more expensive the instrument the better (more realistic) the sound.

I've supplied two text files, one for the 128 instrument names and one for the 16 instrument categories. Copy this code into the appropriate files.

instrumentGMcategories.txt

Piano
Chromatic Percussion
Organ
Guitar
Bass
Strings
Ensemble
Brass
Reed
Pipe
Synth Lead
Synth Pad
Synth Effects
Ethnic
Percussive
Sound Effects

instrumentGMnames.txt.txt

Acoustic Grand Piano
Bright Acoustic Piano
Electric Grand Piano
Honky-tonk Piano
Electric Piano 1
Electric Piano 2
Harpsichord
Clavinet
Celesta
Glockenspiel
Music Box
Vibraphone
Marimba
Xylophone
Tubular Bells
Dulcimer
Drawbar Organ
Percussive Organ
Rock Organ
Church Organ
Reed Organ
Accordion
Harmonica
Tango Accordion
Acoustic Guitar (nylon)
Acoustic Guitar (steel)
Electric Guitar (jazz)
Electric Guitar (clean)
Electric Guitar (muted)
Overdriven Guitar
Distortion Guitar
Guitar harmonics
Acoustic Bass
Electric Bass (finger)
Electric Bass (pick)
Fretless Bass
Slap Bass 1
Slap Bass 2
Synth Bass 1
Synth Bass 2
Violin
Viola
Cello
Contrabass
Tremolo Strings
Pizzicato Strings
Orchestral Harp
Timpani
String Ensemble 1
String Ensemble 2
SynthStrings 1
SynthStrings 2
Choir Aahs
Voice Oohs
Synth Voice
Orchestra Hit
Trumpet
Trombone
Tuba
Muted Trumpet
French Horn
Brass Section
SynthBrass 1
SynthBrass 2
Soprano Sax
Alto Sax
Tenor Sax
Baritone Sax
Oboe
English Horn
Bassoon
Clarinet
Piccolo
Flute
Recorder
Pan Flute
Blown Bottle
Shakuhachi
Whistle
Ocarina
Lead 1 (square)
Lead 2 (sawtooth)
Lead 3 (calliope)
Lead 4 (chiff)
Lead 5 (charang)
Lead 6 (voice)
Lead 7 (fifths)
Lead 8 (bass + lead)
Pad 1 (new age)
Pad 2 (warm)
Pad 3 (polysynth)
Pad 4 (choir)
Pad 5 (bowed)
Pad 6 (metallic)
Pad 7 (halo)
Pad 8 (sweep)
FX 1 (rain)
FX 2 (soundtrack)
FX 3 (crystal)
FX 4 (atmosphere)
FX 5 (brightness)
FX 6 (goblins)
FX 7 (echoes)
FX 8 (sci-fi)
Sitar
Banjo
Shamisen
Koto
Kalimba
Bag pipe
Fiddle
Shanai
Tinkle Bell
Agogo
Steel Drums
Woodblock
Taiko Drum
Melodic Tom
Synth Drum
Reverse Cymbal
Guitar Fret Noise
Breath Noise
Seashore
Bird Tweet
Telephone Ring
Helicopter
Applause
Gunshot

hw412_gmnames.cpp
You write.

Assignment 4.1.2

Your assignment is to create a command line tool called gmnames that displays the output shown below. This assignment is similar to but more complicated than hw231_gmdrums because there are two files to read and merge together. When completed you must be able to run the executable without the two text files present.

Finally, rename a.out to gmnames and copy it to the course common folder.

gmnames final output
Note: The category dividers all begin with seven equal signs and a space followed by the category name.
The category dividers all end with the same length by varying the number of equal signs following the category name.
There are a total of 45 characters in each category divider. The instrument numbers are zero based and are separated from the name by five spaces.

=============================================

General MIDI instrument patch numbers

======= Piano (0-7) =========================
0     Acoustic Grand Piano
1     Bright Acoustic Piano
2     Electric Grand Piano
3     Honky-tonk Piano
4     Electric Piano 1
5     Electric Piano 2
6     Harpsichord
7     Clavinet
======= Chromatic Percuss ion (8-15) ==========
8     Celesta
9     Glockenspiel
10    Music Box
11    Vibraphone
12    Marimba
13    Xylophone
14    Tubular Bells
15    Dulcimer
======= Organ (16-23) =========================
16    Drawbar Organ
17    Percussive Organ
18    Rock Organ
19    Church Organ
20    Reed Organ
...

Usage hints
Use the shell pipe symbol | with grep -i to search for specific instruments. The -i flag means case insensitive. Try these commands.

gmnames | grep -i guitar
======= Guitar (24-31) ===============
24    Acoustic Guitar (nylon)
25    Acoustic Guitar (steel)
26    Electric Guitar (jazz)
27    Electric Guitar (clean)
28    Electric Guitar (muted)
29    Overdriven Guitar
30    Distortion Guitar
31    Guitar harmonics
120   Guitar Fret Noise

gmnames | grep -i sax
64    Soprano Sax
65    Alto Sax
66    Tenor Sax
67    Baritone Sax

gmnames | grep -i synth
38    Synth Bass 1
39    Synth Bass 2
50    SynthStrings 1
51    SynthStrings 2
54    Synth Voice
62    SynthBrass 1
63    SynthBrass 2
======= Synth Lead (80-87) ===========
======= Synth Pad (88-95) ============
90    Pad 3 (polysynth)
======= Synth Effects (96-103) ========
118   Synth Drum

gmnames | grep -i flute
73    Flute
75    Pan Flute

gmnames | grep -i string
======= Strings (40-47) ==============
44    Tremolo Strings
45    Pizzicato Strings
48    String Ensemble 1
49    String Ensemble 2
50    SynthStrings 1
51    SynthStrings 2

Important:
Name the executable gmnames and copy it to your courses bin folder. Test it from the command line to make certain it works without the presence of the two .txt files.

gmnames

hw413_CScalesTrack

The CMidiTrack class encapsulates many common functions needed by a single playback track. The functions are quite small and deal with for getting, setting, and sending MIDI messages for the variables shown below. In this case sending means stuffing into a /std::vector<CMidiPacket>.

beginTimestamp and endTimestamp
Many tracks begin at timestamp zero but not all do.
Sometimes instruments enter and leave during the middle of the song.
noteOffset
We'll be using scales that range from 0-12.
This variable will set the offset to MIDI notes 24-108, the range of the piano.
channel
This is one of the 16 MIDI channels 0-0xF used for the MIDI message.
It is the n in status terminology 0xBn.
Best practice is use a single MIDI channel on a single track.
patch
This is the instrument sound used for the track.
Best practice is use a single patch on a single track unless you need more than 16 different instruments.
volume
This is used at the beginning of the song to set the relative volume balance between all instruments.
It can be used sparingly during the the song to bring out solo instruments and soften accompaniment.
It only needs to be sent once and remains in effect until a new message is sent.
These often use timestamps from 0-5 and definitely before the timestamp of any NON messages.
pan
This is used at the beginning of the song before any NON messages are set.
It is used to set the player positions on the stereo sound field from left to right.
It does not affect the drum channel which has separate pan positions for each drum sound.

The CScalesTrack inherits all the properties of its parent CMidiTrack and then adds its own. In future assignments we'll create subclasses for piano, bass, drums, and other instruments.

Copy this code into their appropriate files.

hw413_CMidiTrack.h
Do not modify.

// hw413_CMidiTrack.h
#ifndef HW413_CMIDITRACK_H_
#define HW413_CMIDITRACK_H_

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

#include <vector>

// DO NOT MODIFY THIS CODE

using namespace CMP33;

class CMidiTrack
{
private: // not really needed they'd be private by default
  uint32_t beginTimestamp_;
  uint32_t endTimestamp_;
  uint8_t noteOffset_;
  uint8_t channel_;
  uint8_t patch_;
  uint8_t volume_;
  uint8_t pan_;
  uint8_t expr_;

public:
  std::vector<CMidiPacket> vtrk;

  // the big six - all default - not declared

  // only constructor
  // Note use of default parameter for expr
  // A default paramter does not need to be included when the constructor is called
  // unless you want to change it to a default value
  // You can have more than one default parameter but they must appear at then end
  CMidiTrack(uint32_t beginTime, uint32_t endTime, uint8_t noteoffset, uint8_t channel,
             uint8_t patch, uint8_t volume, uint8_t pan, uint8_t expr = 100);

  // Getters
  // These simple functions are all declared and defined inline
  // They do not appear in the .cpp file
  // You should only do this for simple functions.
  // Inline header files may not show up in the debugger
  uint32_t get_beginTimestamp() const { return beginTimestamp_; };
  uint32_t get_endTimestamp() const { return endTimestamp_; };
  uint8_t get_noteOffset() const { return noteOffset_; };
  uint8_t get_channel() const { return channel_; };
  uint8_t get_patch() const { return patch_; };
  uint8_t get_volume() const { return volume_; };
  uint8_t get_pan() const { return pan_; };
  uint8_t get_expression() const { return expr_; };

  // Setters - also declared and defined inline
  void set_beginTimestamp(const uint32_t &ts) { beginTimestamp_ = ts; };
  void set_endTimestamp(const uint32_t &ts) { endTimestamp_ = ts; };
  void set_noteOffset(const uint8_t &ofst) { noteOffset_ = ofst; };
  void set_channel(const uint8_t &chan) { channel_ = chan; };
  void set_patch(const uint8_t &patch) { patch_ = patch; };
  void set_volume(const uint8_t &vol) { volume_ = vol; };
  void set_pan(const uint8_t &pan) { pan_ = pan; };
  void set_expression(const uint8_t &expr) { expr_ = expr; };

  // Send these Midi Message
  // These will appear in the .cpp file and you'll need to implement them
  // For now send means write to std::cout
  void send_non(uint32_t tm, uint8_t chan, uint8_t note, uint8_t vel);
  void send_nof(uint32_t tm, uint8_t chan, uint8_t note);
  void send_patch(uint32_t tm, uint8_t chan, uint8_t patch);
  void send_volume(uint32_t tm, uint8_t chan, uint8_t vol);
  void send_pan(uint32_t tm, uint8_t chan, uint8_t pan);
  void send_expression(uint32_t tm, uint8_t chan, uint8_t expr);

  // Example of a pure virtual function
  // This is now an abstract bass class and cannot be created in other files
  // That means you'll have to write subclasses that inherit from CMidiTrack
  // Subclasses are required to declare an implement the write_track() function
  // Subclasses get all the functions found in CMidiTrack for free.
  virtual void write_track() = 0;
};

#endif // HW413_CMIDITRACK_H_

hw413_CMidiTrack.cpp
You write code for the implementation.

// hw413_CMidiTrack.cpp
#ifndef HW413_CMIDITRACK_H_
#include "hw413_CMidiTrack.h"
#endif

#include <iostream>

using namespace CMP33;

// This constructor initializes the class variables
// Subclasses can add additional parameters or class variables as needed
CMidiTrack::CMidiTrack(uint32_t beginTime, uint32_t endTime, uint8_t noteoffset, uint8_t channel,
                       uint8_t patch, uint8_t volume, uint8_t pan, uint8_t expr)
    : beginTimestamp_{beginTime}, endTimestamp_{endTime}, noteOffset_{noteoffset}, channel_{channel},
      patch_{patch}, volume_{volume}, pan_{pan}
{
} // expr not set because it defaults to 100.

// Functions to send MIDI messages
// for now send means write to std::cout
void CMidiTrack::send_non(uint32_t tm, uint8_t chan, uint8_t note, uint8_t vel)
{
  // I did the first one
  CMidiPacket mp(tm, chan, note, vel);
  std::cout << mp;
}

void CMidiTrack::send_nof(uint32_t tm, uint8_t chan, uint8_t note)
{
  // you write - use 0x80 with data2 == 0
}

void CMidiTrack::send_patch(uint32_t tm, uint8_t chan, uint8_t patch)
{
  // you write
}

void CMidiTrack::send_volume(uint32_t tm, uint8_t chan, uint8_t vol)
{
  // you write
}

void CMidiTrack::send_pan(uint32_t tm, uint8_t chan, uint8_t pan)
{
  // you write
}

void CMidiTrack::send_expression(uint32_t tm, uint8_t chan, uint8_t expr)
{
  // you write
}

hw413_CScalesTrack.h
Do not modify.

// hw411_CScalesTrack.h
#ifndef HW413_CSCALESTRACK_H_
#define HW413_CSCALESTRACK_H_

#ifndef HW413_CMIDITRACK_H_
#include "hw413_CMidiTrack.h"
#endif

// DO NOT MODIFY THIS CODE

class CScalesTrack : public CMidiTrack
{
public:
  std::vector<int> vscale;
  std::vector<int> vrhythm;

  CScalesTrack(uint32_t beginTime, uint32_t endTime, uint8_t noteoffset, uint8_t channel,
               uint8_t patch, uint8_t volume, uint8_t pan);

  int get_note(); // choose a random scale note
  void write_scales_track();
  virtual void write_track(); // must define it was declared pure virtual in CMidiTrack
};

// SCALE NOTES
// See class web page for more information
// You can add your own to the list.
// clang-format off
const std::vector<int> vmajor_pentatonic{0, 2, 4, 7, 9, 12};        // Major pentatonic
const std::vector<int> vminor_pentatonic{0, 3, 5, 7, 10, 12};       // Minor pentatonic
const std::vector<int> vragtime{0, 2, 3, 4, 7, 9, 12};              // Ragtime scale
const std::vector<int> vrock_blues{0, 3, 4, 7, 9, 10, 12};          // Rock blues Scales
const std::vector<int> vminor_blues{0, 3, 5, 6, 7, 10, 12};         // Minor blues Scales
const std::vector<int> v6note_blues{0, 3, 5, 6, 7, 10, 12};         // 6 note blues Scales
const std::vector<int> v7note_blues{0, 2, 3, 5, 6, 9, 10, 12};      // 7 note blues Scales
const std::vector<int> vmideast{0, 1, 4, 5, 6, 8, 11, 12};          // Middle Eastern scale
const std::vector<int> vjapanese{0, 1, 5, 7, 8, 12};                // Japanese scale
const std::vector<int> voctotonic{0, 1, 3, 4, 6, 7, 9, 10, 12};     // Octotonic jazz scale half step, whole step
const std::vector<int> vwholetone{0, 2, 4, 6, 8, 10, 12};           // Whole tone, whole step, whole step
const std::vector<int> vflamenco{0, 1, 4, 5, 7, 8, 11, 12};          // Flamenco
const std::vector<int> vphrygian_dominant{0, 1, 4, 5, 7, 8, 10, 12}; // Phrygian dominant
// clang-format ON

#endif // HW411_CSCALESTRACK_H_

hw413_CScalesTrack.cpp
You implement.

// hw413_CScalesTrack.cpp
#ifndef HW413_CSCALESTRACK_H_
#include "hw413_CScalesTrack.h"
#endif

// in common folder
#ifndef HW411_RAND_INT_H_
#include "hw411_rand_int.h"
#endif

// FOR REFERENCE
// assign scale - choose 1
// vmajor_pentatonic{0, 2, 4, 7, 9, 12};        // Major pentatonic
// vminor_pentatonic{0, 3, 5, 7, 10, 12};       // Minor pentatonic
// vragtime{0, 2, 3, 4, 7, 9, 12};              // Ragtime scale
// vrock_blues{0, 3, 4, 7, 9, 10, 12};          // Rock blues Scales
// vminor_blues{0, 3, 5, 6, 7, 10, 12};         // Minor blues Scales
// vheptatonic_blues{0, 2, 3, 5, 6, 9, 10, 12}; // Heptatonic blues Scales
// vmideast{0, 1, 4, 5, 7, 8, 10, 12};          // Middle Eastern scale
// vjapanese{0, 1, 5, 7, 8, 12};                // Japanese scale
// voctotonic{0, 1, 3, 4, 6, 7, 9, 10, 12};     // Octotonic jazz scale half step, whole step
// vwholetone{0, 2, 4, 6, 8, 10, 12};           // Sc-fi
// vflamenco{0, 1, 4, 5, 7, 8, 11, 12};         // Flamenco
// vphrygian_dominant{0, 1, 4, 5, 7, 8, 10, 12};// Phrygian_dominant

// additional constructor.
CScalesTrack::CScalesTrack(uint32_t beginTime, uint32_t endTime, uint8_t noteoffset, uint8_t channel,
                           uint8_t patch, uint8_t volume, uint8_t pan)
    : CMidiTrack(beginTime, endTime, noteoffset, channel,
                 patch, volume, pan)
{
  // assign vscale to one of the above scales
  // assign rhythm pattern
  // we'll use the long, short-short, long described on the class 4.1 web page
  vrhythm = {500, 250, 250, 500};
}

int CScalesTrack::get_note()
{
  // get a random note from the vscale
  // add the get_noteOffset
  // return the result
}

void CScalesTrack::write_scales_track()
{
  std::cout << "# CScalesTrack must override pure virtual function write_track()\n";

  /*
  CScalesTrack declares a private variable tm for keeping track
  I used a do..while loop because we set the endTime in the constructor
  do {
    get a random note from vscale
    vrhythm has 4 elements
    every time you get a new NON you'll have cycle through vrhythm 
        indices using the pattern 0 1 2 3 0 1 2 3 etc.
    every time it's vrhythm[0] the NON velocity is 127
    otherwise the NON velocity is 80.
    this will accent the first note of rhythm pattern every time it repeats.
    keep a running sum of the timestamps using the tm variable
    if the NON timestamp is tm, the NOF timestamp is tm - 1
  } while (tm < get_endTimestamp());
  */
}

void CScalesTrack::write_track()
{
  vtrk.clear();
  // these items should be set at the beginning of the track
  send_patch(0, get_channel(), get_patch());
  send_volume(0, get_channel(), get_volume());
  send_pan(0, get_channel(), get_pan());

  write_scales_track();
}

hw413_main.cpp
Do not modify

// hw411_main.cpp
#ifndef HW413_CSCALESTRACK_H_
#include "hw413_CScalesTrack.h"
#endif

#include <iostream>

// DO NOT MODIFY

int main()
{
  // beginTS, endTS, noteOffset, channel, patch, volume, pan
  CScalesTrack mtrk{0, 50000, 60, 0, 11, 100, 64};
  mtrk.write_track();
  for (auto itr : mtrk.vtrk)
    std::cout << itr;

  std::cout << "# Play in MIDIDisplay\n";
}

Assignment

Write, build, and exucute a program that plays random notes from a scale of your choice using your CMidiPacket (CMP33) and your CMidiTrack class. The song ends when the timestamp exceeds 50000. Build and run using cmake.

CMakeLists.txt

cmake_minimum_required(VERSION 3.5)
set(APPNAME "hw413_scalesTrack")
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 0)
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(SOURCES_FILES
  ${COMMON}/hw332_CMidiPacket.cpp
  ${COMMON}/hw411_rand_int.cpp
  hw413_CMidiTrack.cpp
  hw413_CScalesTrack.cpp
  hw413_main.cpp
)

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

Build and run
When you get it to work listen to all of the scales.
Experiment with the tempo.

hw414_CMeasuresBeatTrack

Copy hw413_CMidiTrack .h and .cpp files to common folder

cp $HOME312/cs312/hw41/hw413_CScalesTrack/hw413_CMidiTrack.cpp $HOME312/common
cp $HOME312/cs312/hw41/hw413_CScalesTrack/hw413_CMidiTrack.h $HOME312/common

The CMeasuresBeatTrack class adds rhythm patterns to the CScalesTrack. The patterns are based on the beat unit which in our case is a quarter note with a duration of 1000 ms. In music terms

  • Quarter note (1000ms) = 2 Eighth notes (500 ms each)
  • Eighth note (500 ms) = 2 Sixteenth notes (250 ms each)

Our standard beat will have four subdivisions equally spaced 250 ms apart.
A note can appear in none/any/all subdivisions.
We'll use a four bit binary string to represent the 16 different rhythm patterns for each beat.

From hw/hw414_CMeasuresBeatTrack.h

/*
 One beat is one quarter note is 1000 ms
 We'll divide a beat into 4 subdivisions
 In music that means allowed note values are quarter, eighth, sixteenth
 We won't deal with dotted notes.
 Each subdivision will be 250 ms.
 There will be 16 subdivisions based on 4 bit binary strings
*/
// binary counting 0-15
const std::string b0000 = "0000";
const std::string b0001 = "0001";
const std::string b0010 = "0010";
const std::string b0011 = "0011";
const std::string b0100 = "0100";
const std::string b0101 = "0101";
const std::string b0110 = "0110";
const std::string b0111 = "0111";
const std::string b1000 = "1000";
const std::string b1001 = "1001";
const std::string b1010 = "1010";
const std::string b1011 = "1011";
const std::string b1100 = "1100";
const std::string b1101 = "1101";
const std::string b1110 = "1110";
const std::string b1111 = "1111";

Open hw414CMeasuresBeatTrack folder in vsCode.
Copy this code into their respective files.

hw414_CMeasuresBeatTrack.h
Do not modify.

// hw414_CMeasuresBeatTrack.h
#ifndef HW414_CMEASURESBEATTRACK_H_
#define HW414_CMEASURESBEATTRACK_H_

// now in common folder
#ifndef HW413_CMIDITRACK_H_
#include "hw413_CMidiTrack.h"
#endif

// DO NOT MODIFY THIS CODE

class CMeasuresBeatTrack : public CMidiTrack
{
private:
  uint32_t tm;

public:
  static int numMeas;
  std::vector<int> vscale;

  CMeasuresBeatTrack(uint32_t beginTime, uint32_t endTime, uint8_t noteoffset, uint8_t channel,
                     uint8_t patch, uint8_t volume, uint8_t pan);

  void write_one_beat();
  void write_one_measure();
  virtual void write_track(); // must define it was declared pure virtual in CMidiTrack
};

/*
 There are eleven notes in the chromatic scale numbered 0,1,2,3..11.
 Note 12 is one octave higher than note 0 and is also included.
 The sequence can repeat an octave higher by adding 12 to every note
 The sequence can repeat an octave lower by subracting 12 from every note
 The usable range of MIDI starting note offset is roughly 24-96
 */

// SCALE NOTES
// You can add your own to the list.
// clang-format off
// a much larger list can be found start with
// https://en.wikipedia.org/wiki/Scale_(music)
const std::vector<int> vmajor_pentatonic{0, 2, 4, 7, 9, 12};        // Major pentatonic
const std::vector<int> vminor_pentatonic{0, 3, 5, 7, 10, 12};       // Minor pentatonic
const std::vector<int> vragtime{0, 2, 3, 4, 7, 9, 12};              // Ragtime scale
const std::vector<int> vrock_blues{0, 3, 4, 7, 9, 10, 12};          // Rock blues Scales
const std::vector<int> vminor_blues{0, 3, 5, 6, 7, 10, 12};         // Minor blues Scales
const std::vector<int> v6note_blues{0, 3, 5, 6, 7, 10, 12};         // 6 note blues Scales
const std::vector<int> v7note_blues{0, 2, 3, 5, 6, 9, 10, 12};      // 7 note blues Scales
const std::vector<int> vmideast{0, 1, 4, 5, 6, 8, 11, 12};          // Middle Eastern scale
const std::vector<int> vjapanese{0, 1, 5, 7, 8, 12};                // Japanese scale
const std::vector<int> voctotonic{0, 1, 3, 4, 6, 7, 9, 10, 12};     // Octotonic jazz scale half step, whole step
const std::vector<int> vwholetone{0, 2, 4, 6, 8, 10, 12};           // Whole tone, whole step, whole step
const std::vector<int> vflamenco{0, 1, 4, 5, 7, 8, 11, 12};          // Flamenco
const std::vector<int> vphrygian_dominant{0, 1, 4, 5, 7, 8, 10, 12}; // Phrygian dominant
// clang-format ON

C

extern std::vector<std::string> vbeats;


#endif // HW414_CMEASURESBEATTRACK_H_

hw414_CMeasuresBeatTrack.cpp
You implement.

// hw414_CMeasuresBeatTrack.cpp
#ifndef HW414_CMEASURESBEATTRACK_H_
#include "hw414_CMeasuresBeatTrack.h"
#endif

#ifndef HW411_RAND_INT_H_
#include "hw411_rand_int.h"
#endif
// FOR REFERENCE
// assign scale - choose 1
// vmajor_pentatonic{0, 2, 4, 7, 9, 12};        // Major pentatonic
// vminor_pentatonic{0, 3, 5, 7, 10, 12};       // Minor pentatonic
// vragtime{0, 2, 3, 4, 7, 9, 12};              // Ragtime scalef
// vrock_blues{0, 3, 4, 7, 9, 10, 12};          // Rock blues Scales
// vminor_blues{0, 3, 5, 6, 7, 10, 12};         // Minor blues Scales
// vheptatonic_blues{0, 2, 3, 5, 6, 9, 10, 12}; // Heptatonic blues Scales
// vmideast{0, 1, 4, 5, 7, 8, 10, 12};          // Middle Eastern scale
// vjapanese{0, 1, 5, 7, 8, 12};                // Japanese scale
// voctotonic{0, 1, 3, 4, 6, 7, 9, 10, 12};     // Octotonic jazz scale half step, whole step
// vwholetone{0, 2, 4, 6, 8, 10, 12};           // Sc-fi
// vflamenco{0, 1, 4, 5, 7, 8, 11, 12};         // Flamenco
// vphrygian_dominant{0, 1, 4, 5, 7, 8, 10, 12};// Phrygian_dominant

int CMeasuresBeatTrack::numMeas = 12;

// Examples of using beat patterns
// Uncomment 1 from this list
// Make up your own.
// std::vector<std::string> vbeats = {
//     b0000, b0001, b0010, b0011,
//     b0100, b0101, b0110, b0111, 
//     b1000, b1001, b1010, b1011,
//     b1100, b1101, b1110, b1111};
// std::vector<std::string> vbeats = {b1000, b1010, b1011};
// std::vector<std::string> vbeats = {b1000, b0100, b0001};
// std::vector<std::string> vbeats = {b1000, b0001};
// std::vector<std::string> vbeats = {b1000, b1010};
// std::vector<std::string> vbeats = {b1000, b0110};
// std::vector<std::string> vbeats = {b0000, b1000, b0110};
//  std::vector<std::string> vbeats = {b1000};
// std::vector<std::string> vbeats = {b0000, b0000, b0001, b0110};

// only constructor.
CMeasuresBeatTrack::CMeasuresBeatTrack(uint32_t beginTime,
                                       uint32_t endTime,
                                       uint8_t noteoffset,
                                       uint8_t channel,
                                       uint8_t patch,
                                       uint8_t volume,
                                       uint8_t pan)
    : CMidiTrack(beginTime, endTime, noteoffset, channel, patch, volume, pan), tm{beginTime}
{ // choose one scale above
  vscale = vmajor_pentatonic;
  // choose an example beat pattern from examples above
  // or make up your own
  std::vector<std::string> vbeats = {b1000};
}

void CMeasuresBeatTrack::write_one_beat()
{
  /* pseudo code
    create a string from a random index into vbeats
    for loop 0 to string length
      get a random scale noteoffset
      cycle through the binary digits of the vbeat string
      if it's one
        send NON
        increment tm by 250
        send NOF at tm - 1
      else if it's zero
        increment tm by 250
    end for loop
*/ 
}

void CMeasuresBeatTrack::write_one_measure()
{
  // we're using 4 beats in a measure 
  // write the measure
}


void CMeasuresBeatTrack::write_track()
{
  vtrk.clear(); // from CMidiTrack
  // these messages should come at timestamp 0 before any NON
  send_patch(0, get_channel(), get_patch());
  send_volume(0, get_channel(), get_volume());
  send_pan(0, get_channel(), get_pan());

   /*
      the static variable CMeasuresBeatTrack::numMeas
      indicates how many measures long the song is

      for the number of measures in the song
      write_one_measure();
    */
}

hw414_main.cpp
Do not modify

// hw414_main.cpp
#ifndef HW414_CMeasuresBeatTrack_H_
#include "hw414_CMeasuresBeatTrack.h"
#endif

#include <iostream>

// DO NOT MODIFY

int main()
{
  // calc end time based on number measures
  uint32_t nm = CMeasuresBeatTrack::numMeas * 4000;

  // beginTS, endTS, noteOffset, channel, patch, volume, pan
  CMeasuresBeatTrack mtrk{0, nm, 60, 0, 11, 100, 64};

  mtrk.write_track();
  for (auto itr : mtrk.vtrk)
    std::cout << itr;

  std::cout << "# Play in MIDIDisplay\n";
}

CMakeLists.txt
You write.

common
These files should all be inside the common folder

hw332_CMidiPacket.cpp
hw332_CMidiPacket.h
hw411_rand_int.cpp
hw411_rand_int.h
hw413_CMidiTrack.cpp
hw413_CMidiTrack.h

Assignment

Write, build, and exucute a program that plays random notes from a scale of your choice beat patterns.

Build and run
When you get it to work listen to all of the scales.
Experiment with the tempo.

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
    
$HOME312/common
├── hw332_CMidiPacket.cpp
├── hw332_CMidiPacket.h
├── hw411_rand_int.cpp
├── hw411_rand_int.h
├── hw413_CMidiTrack.cpp
└── hw413_CMidiTrack.h

$HOME312/cs312/hw41
├── hw411_rand_int
│   ├── hw411_main.cpp
│   ├── hw411_rand_int.cpp
│   └── hw411_rand_int.h
├── hw412_gmnames
│   ├── hw412_gmnames1.cpp
│   ├── instrumentGMcategories.txt
│   └── instrumentGMnames.txt
├── hw413_CScalesTrack_org
│   ├── CMakeLists.txt
│   ├── build
│   ├── hw413_CMidiTrack.cpp
│   ├── hw413_CMidiTrack.h
│   ├── hw413_CScalesTrack.cpp
│   ├── hw413_CScalesTrack.h
│   └── hw413_main.cpp
└── hw414_CMeasuresBeatTrack
    ├── CMakeLists.txt
    ├── build
    ├── hw414_CMeasuresBeatTrack.cpp
    ├── hw414_CMeasuresBeatTrack.h
    └── hw414_main.cpp

Author: John Ellinger

Created: 2020-01-27 Mon 13:12

Validate