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.
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.
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.
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.
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
- Implement hw411_rand_int.cpp.
- 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