CS 312 - Week 5.1
CS 312 Audio Programming Winter 2020
Table of Contents
5.1 Class
Project 1 - due week 7.1
Link: Project 1 proposal
Reading
12 bar blues
Blues Form
- The standard blues form is 12 measures long.
- A measure is four beats, just like the drum machine.
- A beat is 1000 ms.
- The beat can be subdivided in threes or fours but must add up to 1000 ms.
The musical form for a 12 bar (measure) blues usually shown in Roman numerals representing the chords used.
12 bar blues in the key of C
The I chord is built on C (startNoteOffset + 0)
the IV chord is built on F (startNoteOffset + 5
the V chord is built on G (startNoteOffset + 7)I I I I IV IV I I V IV I V
Blues piano chords
You can play a piano chord with three or more notes by sending multiple NON messages with the same timestamp.
Remember to turn the chord off using multiple NOF messages.
The notes of these chords are for a blues in the key of C. Sometimes called "Blues in C".
Chord | Notes | Numbers | Offset from I chord |
I (C7) | C E G Bb | 0 4 7 10 | 0 |
IV (F7) | F A C Eb | 5 9 (7+5) (10+5) | +5 |
V (G7) | G B D F | 7 (4+7) (7+7) (10+7) | +7 |
Octave shifts
You can add or subtract 12 from any MIDI note number to get the same note one octave higher or lower.
Blues scales We'll use the
- Rock blues scale
- {0, 3, 4, 7, 9, 10, 12}
Blues scale note offsets
MIDI note numbers can never be negative or greater than 127.
The piano uses MIDI note numbers 24-108 so subracting 5 or 7 will work in that range.
Scale notes | Scale Offset | Starting Note Offset 60 |
---|---|---|
With I chord | 0 | 60, 63, 64, 67, 69, 70, 72 |
With IV chord | +5 or -7 | 65, 68, 69, 72, 74, 75, 77 (+5) |
With V chord | +7 or -5 | 55, 59, 61, 62, 64, 65, 67 (-5) |
- Rock blues scale notes for the I chord (C = 0)
kROCK_BLUES_SCALE = {0, 3, 4, 7, 9, 10, 12};
Use in measures 1, 2, 3, 4, 7, 8, 11
I I I I I I I - Rock blues scale notes for the IV chord ( add 5)
kROCK_BLUES_SCALE = {5, 8, 9, 12, 15, 17};
Use in measures 5, 6, 10
IV IV IV - Rock blues scale notes for the V chord ( add 7)
kROCK_BLUES_SCALE = {7, 10, 11, 14, 17, 19};
Use in measures 9, 12
V V
Measure start times These are the millisecond start times for each of the 12 measures.
0 | 4000 | 8000 | 12000 |
16000 | 20000 | 24000 | 28000 |
32000 | 36000 | 40000 | 44000 |
Next repeat (2nd time through) would start at 48000 Next repeat (3rd time through) would start at 96000 etc.
Markov chains
From: https://en.wikipedia.org/wiki/Markov_chain
A Markov chain is a stochastic [random process] model describing a sequence of possible events in which the probability of each event depends only on the state attained in the previous event.
From: https://brilliant.org/wiki/markov-chains/
A Markov chain is a mathematical system that experiences transitions from one state to another according to certain probabilistic rules. The defining characteristic of a Markov chain is that no matter how the process arrived a t its present state, the possible future states are fixed. In other words, the probability of transitioning to any particular state is dependent solely on the current state and time elapsed. The state space, or set of all possible states, can be anything: letters, numbers, weather conditions, baseball scores, or stock performances.
Markov chains are usually set up as a probability matrix. Large scale Markov chains are derived by analyzing large amounts of existing data.
Here is a completely arbitrary table for the 7 notes of the rock blues scale. I made it up to discourage large leaps between the notes of the rock blues scale (RB). Each row sums to 10. If the current note is 3, then note 4 has a 5/10 chance of being chosen next.
RB | 0 | 1 |
2 |
3 | 4 | 5 |
6 |
7 | 8 |
9 | 10 | 11 |
12 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 5 | . | . | 2 | 3 | . | . | . | . | . | . | . | . |
3 | . | . | . | 2 | 4 | . | . | 4 | . | . | . | . | . |
4 | . | . | . | 2 | 3 | . | . | 5 | . | . | . | . | . |
7 | . | . | . | . | 3 | . | . | 4 | . | . | 3 | . | . |
9 | . | . | . | . | . | . | . | 4 | . | 1 | 5 | . | . |
10 | . | . | . | . | . | . | 4 | . | . | 2 | . | 4 | |
12 | . | . | . | . | . | . | . | 4 | . | . | 3 | . | 3 |
The first column represents the notes of the rock blues scale. The first row represents all 12 notes of the chromatic scale with the rock blues scale notes in bold. Each row adds up to ten. A cell in that row represents the probability that that note will follow the column one note. Dots are a probability of 0. The row for note 4 says that only 3, 4, or 7 can follow a 4. Note 7 is most likely to follow with a 5/10 chance. I've made the integer in each row add up to 10. That's not a strict requirement. C++ will sum the values in each row and figure out the the probability of each cell divided by the sum of that row. You you can also use floats instead of integers.
discrete_distribution
The C++ <random> library includes a discrete_distribution function that produces integer values based on probabilities. I've slightly modified example code from en.cppreference.com to to illustrate the discrete_distribution for the note that follows 3. I've reduced the probability of note 3 following itself from 1 to 0.5.
Run this code in https://www.onlinegdb.com/online_c++_compiler
#include <iostream> #include <map> #include <random> int main() { std::random_device rd; std::default_random_engine engine(rd()); // the array represents the 12 note os the chromatic scale 0-12 std::discrete_distribution<> d({2 , 0 , 0 , 0.5 , 5 , 0 , 0 , 2 , 0 , 0 , 0 , 0 , 0}); std::map<int, int> m; for(int n=0; n<10000; ++n) { ++m[d(engine)]; } for(auto p : m) { std::cout << p.first << " generated " << p.second << " times\n"; } }
Output
It might be different but in general quite close to these values.
0 generated 2088 times 3 generated 531 times 4 generated 5223 times 7 generated 2158 times
c511 c512 c513 Setup
cd $HOME312/cs312 mkdir hw51 mkdir hw51/c511_CInstrument cd hw51/c511_CInstrument touch hw511_CInstrument.cpp touch hw511_CInstrument.h cd $HOME312/cs312/hw51 mkdir c512_pianoChords cd c512_pianoChords mkdir build touch c512_main.cpp touch hw511_CBluesPianoTrack.cpp touch hw511_CBluesPianoTrack.h touch CMakeLists.txt touch c512_main.cpp cd $HOME312/cs312/hw51 mkdir c513_markovChain cd c513_markovChain mkdir build touch c513_CMarkovBlues.cpp touch c513_CMarkovBlues.h touch c513_main.cpp touch CMakeLists.txt
c511_CInstrument
The CScalesTrack class from homework hw411 was a sublcass of CMidiTrack, now defined in hw423_CMidiTrack. Homework hw511_blues uses four separate insturments, melody, piano rhythm, bass, and drums.
This is a new CInstrument class that is also a sublcass of CMidiTrack but implements functions to deal with among other things, counting measures, and adjusting the scale note offsets based on the what measure of the blues form we're in.
The c511_CInstrument folder is just a temporary placeholder. You'll copy the code below into the files and then move them to your common folder.
This class is fully written and you should not have to modify it. The hw511_ prefix indicates it will be used in hw511_blues.
hw511_CInstrument.h
// hw511_CInstrument.h #ifndef HW511_INSTRUMENT_H_ #define HW511_INSTRUMENT_H_ #ifndef HW423_CMIDITRACK_H_ #include "hw423_CMidiTrack.h" #endif // DO NOT MODIFY // Blues form const int kNUM_MEASURES = 12; const int kNUM_REPEATS = 1; const int kTOTAL_MEASURES = kNUM_REPEATS * kNUM_MEASURES; const int kSONG_END_TIME = kTOTAL_MEASURES * 4000; class CInstrument : public CMidiTrack { private: int start_note; // MIDI note offset for pitch 0 in kBLUES_SCALE; int meas_num; // updates measure number as song is playing public: // additional constructor CInstrument(uint32_t beginTm, uint32_t endTm, int noteOffset, int chan, int patch, int vol, int pan, int startNote, int measNum); // getters - defined inline int get_start_note() const { return start_note; } int get_meas_num() const { return meas_num; } // setters - defined inline void set_start_note(const int sn) { start_note = sn; }; void set_meas_num(const int mn) { meas_num = mn; }; // Utility functions used by melody, piano, and bass classes. int get_note_offset(int meas, int note_offset); uint32_t get_measure_start_time(int meas); // This is a pure virtual function in CMidiTrack // and we need to declare it here and define it in CInstrument.cpp void write_track() override; // write_one_measure is a pure virtual function in CInstrument // The sax, piano, and bass subclasses must implement their own versions virtual void write_one_measure(int meas, int chan) = 0; }; #endif // HW511_INSTRUMENT_H_
hw511_CInstrument.cpp
// hw511_CInstrument.cpp #ifndef HW511_CINSTRUMENT_H_ #include "hw511_CInstrument.h" #endif // additional constructor CInstrument::CInstrument(uint32_t beginTm, uint32_t endTm, int noteOffset, int chan, int patch, int vol, int pan, int startNote, int measNum) : CMidiTrack(beginTm, endTm, noteOffset, chan, patch, vol, pan), start_note{startNote}, meas_num{measNum} { } // This is a pure virtual function in CInstrument and we need to implement it // here. This CInstrument function does not implement write_one_measure() and // instead calls the functions implemented in sax, piano, and bass CInstrument // subclasses. void CInstrument::write_track() { vtrk.clear(); push_patch(0, get_channel(), get_patch()); push_volume(0, get_channel(), get_volume()); push_pan(0, get_channel(), get_pan()); // Calls write_one_measure(), however because it is a pure virtual function // it will be implemented in each of the Sax, Piano, and Bass subclasses // according to the needs of that subclass for (int ix = 0; ix < kTOTAL_MEASURES; ++ix) write_one_measure(ix, get_channel()); } // changes the note offset based on which measure // of the blues form is being written // measure numbers are zero based; measure 1 == 0, measure 12 == 11 // Return the scale note offset for meas_num // A 12 bar blues with 4 repeats means you have to account for measures 13-48 int CInstrument::get_note_offset(int meas, int note_offset) { uint16_t offset; switch (meas % 12) { case 0: case 1: case 2: case 3: case 6: case 7: case 10: offset = note_offset; break; case 4: case 5: case 9: offset = note_offset + 5; break; case 8: offset = note_offset + 7; break; case 11: offset = note_offset - 5; break; default: offset = note_offset; break; } return offset; } // returns the timestamp at the start of the measure // measure numbers are zero based // there are 4 beats in a measure, each beat == 1000ms uint32_t CInstrument::get_measure_start_time(int meas) { return meas * 4000; }
Then copy to your common folder.
cp $HOME312/cs312/hw51/c511_CInstrument/hw511_CInstrument.h $HOME312/common cp $HOME312/cs312/hw51/c511_CInstrument/hw511_CInstrument.cpp $HOME312/common
The CInstrument class will be used in all remaining projects.
c512_pianoChords
This project is also complete and demonstrates how you can write piano chords that follow the chord and scale changes of the blues form.
The files prefixed hw511_ will be used in homework hw511_blues.
hw511_CBluesPianoTrack.cpp
// hw511_CBluesPianoTrack.cpp #ifndef HW511_CBLUESPIANOTRACK_H_ #include "hw511_CBluesPianoTrack.h" #endif struct ChordTm { int note1; int note2; int note3; int note4; int dur; }; // rock blues scale ChordTm chord1{3, 7, 10, 12, 1500}; ChordTm chord2{7, 10, 12, 15, 2000}; // {7, 10, 12, 3+12} ChordTm chord3{3, 7, 10, 12, 2500}; const std::vector<ChordTm> one_meas_chord_vec = {chord1, chord2, chord3}; // additional constructor CBluesPianoTrack::CBluesPianoTrack(uint32_t beginTm, uint32_t endTm, int scaleOffset, int chan, int patch, int vol, int pan, int startNote, int meas) : CInstrument(beginTm, endTm, scaleOffset, chan, patch, vol, pan, startNote, meas) {} void CBluesPianoTrack::write_one_measure(int meas, int chn) { CMidiPacket mp; uint16_t offset = get_note_offset(meas, get_start_note()); uint32_t meas_tm = get_measure_start_time(meas); for (auto itr : one_meas_chord_vec) { push_non(meas_tm + itr.dur, chn, itr.note1 + offset, 127); push_non(meas_tm + itr.dur, chn, itr.note2 + offset, 127); push_non(meas_tm + itr.dur, chn, itr.note3 + offset, 127); push_non(meas_tm + itr.dur, chn, itr.note4 + offset, 127); push_nof(meas_tm + 300 + itr.dur, chn, itr.note1 + offset); push_nof(meas_tm + 300 + itr.dur, chn, itr.note2 + offset); push_nof(meas_tm + 300 + itr.dur, chn, itr.note3 + offset); push_nof(meas_tm + 300 + itr.dur, chn, itr.note4 + offset); } set_meas_num(get_meas_num() + 1); }
hw511_CBluesPianoTrack.h
// hw511_CBluesPianoTrack.h #ifndef HW511_CBLUESPIANOTRACK_H_ #define HW511_CBLUESPIANOTRACK_H_ #ifndef HW511_CINSTRUMENT_H_ #include "hw511_CInstrument.h" #endif // DO NOT MODIFY class CBluesPianoTrack : public CInstrument { public: // constructor CBluesPianoTrack(uint32_t beginTm, uint32_t endTm, int scaleOffset, int chan, int patch, int vol, int pan, int startNote, int meas); // This is the only function needed void write_one_measure(int meas, int chan) override; }; #endif // HW511_CBLUESPIANOTRACK_H_
c512_main.cpp
// c512_main.cpp #ifndef HW511_CINSTRUMENT_H_ #include "hw511_CInstrument.h" #endif #ifndef HW511_CBLUESPIANOTRACK_H_ #include "hw511_CBluesPianoTrack.h" #endif #ifndef HW421_CDELAYMS_H_ #include "hw421_CDelayMs.h" #endif #ifndef HW422_CAPPLEMIDISYNTH_H_ #include "hw422_CAppleMidiSynth.h" #endif #include <vector> using namespace CMP33; std::vector<CMidiPacket> vplay; void write_piano() { uint32_t startTm = 0; int scaleOffset = 0; int chan = 1; int patch = 0; // piano int vol = 100; int pan = 64; // pan center int startNote = 48; int meas = 0; CBluesPianoTrack pno_trk(startTm, kSONG_END_TIME, scaleOffset, chan, patch, vol, pan, startNote, meas); pno_trk.write_track(); // append to vplay std::copy(pno_trk.vtrk.begin(), pno_trk.vtrk.end(), std::back_inserter(vplay)); } int main(int argc, char const *argv[]) { write_piano(); CDelayMs::s_tempo = 135; CAppleMidiSynth ams; ams.send(vplay); return 0; }
CMakeLists.txt
cmake_minimum_required(VERSION 2.8) set(APPNAME "pianoChords") 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(SOURCE_FILES ${COMMON}/hw332_CMidiPacket.cpp ${COMMON}/hw411_rand_int.cpp ${COMMON}/hw421_CDelayMs.cpp ${COMMON}/hw422_CAppleMidiSynth.cpp ${COMMON}/hw423_CMidiTrack.cpp ${COMMON}/hw511_CInstrument.cpp hw511_CBluesPianoTrack.cpp c512_main.cpp ) include_directories(${COMMON}) add_executable(${APPNAME} ${SOURCE_FILES}) target_link_libraries(${PROJECT_NAME} PRIVATE "-framework AudioToolbox") target_link_libraries(${PROJECT_NAME} PRIVATE "-framework CoreMIDI") target_link_libraries(${PROJECT_NAME} PRIVATE "-framework CoreAudio")
Build/run/listen
Study the code.
The hw511_CBluesPianoTrack files will be copied and used in hw511_blues.
c513_markovChain
While testing hw511_blues I wrote this code to test adding Markov chain logic for the melody line. To incorporate it into hw511_blues I used the hw411_randUtils function get_rand_num(…) to choose the starting note of each measure. The remaining melody notes for the measure called the chooseMarkovNote(…) function from the CMarkov class.
The idea is to avoid large melodic leaps and opt for a smoother sequence of notes.
All code is complete and should compile.
c513_CMarkovBlues.cpp
The markov weights were set up to allow only the three notes closest to the starting note.
All weights add up to ten.
Each number times ten is the percentage probability for the next note that follows.
// c513_CMarkovBlues.cpp #ifndef C513_CMARKOVBLUES_H_ #include "c513_CMarkovBlues.h" #endif #include <iostream> #include <vector> #include <random> int CMarkovBlues::chooseMarkovNote(int note) { // rock blues // const std::vector<int> kweigh{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; const std::vector<int> k0weights{5, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0}; const std::vector<int> k3weights{0, 0, 0, 2, 4, 0, 0, 4, 0, 0, 0, 0, 0}; const std::vector<int> k4weights{0, 0, 0, 2, 3, 0, 0, 5, 0, 0, 0, 0, 0}; const std::vector<int> k7weights{0, 0, 0, 0, 3, 0, 0, 4, 0, 0, 3, 0, 0}; const std::vector<int> k9weights{0, 0, 0, 0, 0, 0, 0, 4, 0, 1, 5, 0, 0}; const std::vector<int> k10weights{0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 2, 0, 4}; const std::vector<int> k12weights{0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 3, 0, 3}; std::random_device rd; std::default_random_engine engine(rd()); std::discrete_distribution<int> distribution_k0(k0weights.begin(), k0weights.end()); std::discrete_distribution<int> distribution_k3(k3weights.begin(), k3weights.end()); std::discrete_distribution<int> distribution_k4(k4weights.begin(), k4weights.end()); std::discrete_distribution<int> distribution_k7(k7weights.begin(), k7weights.end()); std::discrete_distribution<int> distribution_k9(k9weights.begin(), k9weights.end()); std::discrete_distribution<int> distribution_k10(k10weights.begin(), k10weights.end()); std::discrete_distribution<int> distribution_k12(k12weights.begin(), k12weights.end()); int n; switch (note) { case 0: n = distribution_k0(engine); break; case 3: n = distribution_k3(engine); break; case 4: n = distribution_k4(engine); break; case 7: n = distribution_k7(engine); break; case 9: n = distribution_k9(engine); break; case 10: n = distribution_k10(engine); break; case 12: n = distribution_k12(engine); break; default: n = distribution_k0(engine); break; } return n; }
c513_CMarkovBlues.h
// c513_CMarkovBlues.h #ifndef C513_CMARKOVBLUES_H_ #define C513_CMARKOVBLUES_H_ #include <iostream> #include <vector> #include <random> extern std::random_device rd; extern std::default_random_engine engine; class CMarkovBlues { public: std::vector<int> vRBscale{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; int chooseMarkovNote(int note); }; #endif // CMARKOVBLUES_H_
c513_main.cpp
// c513_main.cpp #ifndef CMARKOVBLUES_H_ #include "CMarkovBlues.h" #endif #ifndef HW411_RAND_INT_H_ #include "hw411_rand_int.h" #endif #ifndef HW421_CDELAYMS_H_ #include "hw421_CDelayMs.h" #endif #ifndef HW422_CAPPLE_MIDISYNTH_H_ #include "hw422_CAppleMidiSynth.h" #endif #include <random> int startNote{0}; bool isStartSet = false; const std::vector<int> vscale{0, 3, 4, 7, 9, 10, 12}; // rock blues void play_melody(std::vector<int> v) { CDelayMs::s_tempo = 74; CMidiPacket mp; std::vector<CMidiPacket> vplay; uint32_t tm{0}; for (int ix = 0; ix < v.size(); ++ix) { // NON uint8_t note = v.at(ix); { mp = {tm, 0x90, note, 100}; if (ix % 2 == 0) { tm += 667; mp.set_data2(120); } else { tm += 333; mp.set_data2(85); } vplay.push_back(mp); // NOF mp = {tm - 1, 0x80, note, 0}; vplay.push_back(mp); } } CAppleMidiSynth ams; ams.send(vplay); CDelayMs d(1000, false); } void gen_random_melody(const int numNotes) { std::vector<int> vec; std::vector<int> vi; // vec.clear(); // vi.clear(); for (auto n = 0; n < numNotes; ++n) { // reset note for the next time through the inner loop int ni = get_rand_int(0, vscale.size() - 1); if (!isStartSet) { startNote = vscale.at(ni); isStartSet = true; } int note = vscale.at(ni) + 60; vec.push_back(note); vi.push_back(ni); } isStartSet = false; std::cout << "==== random_melody ====\n"; for (auto itr : vi) { std::cout << vscale.at(itr) << " "; } std::cout << std::endl; play_melody(vec); } void gen_markov_melody(const int numNotes) { // std::cout << "==== gen_markov_melody ====\n"; CMarkovBlues mb; std::vector<int> vec; std::vector<int> vi; vec.clear(); vi.clear(); int note = startNote; // Inner for loop for (auto n = 0; n < numNotes; ++n) { // reset note for the next time through the inner loop if (n == 0) note = startNote; else { // This statement chooses the next note based on the previous one // note = mb.chooseMarkovNote(note); // This statement chooses all notes based on the starting note // that was set as the first starting note of the random melody note = mb.chooseMarkovNote(startNote); } vec.push_back(note + 60); } std::cout << "==== markov_melody ====\n"; for (auto itr : vec) { std::cout << itr - 60 << " "; } std::cout << std::endl; play_melody(vec); } int main(int argc, char const *argv[]) { const int kNUM_NOTES = 8; // number of notes to generate const int kNUM_TESTS = 4; // number of experiments for (int i = 0; i < kNUM_TESTS; ++i) { gen_random_melody(kNUM_NOTES); // std::cout << std::endl; gen_markov_melody(kNUM_NOTES); std::cout << std::endl; } return 0; }
CMakeLists.txt
cmake_minimum_required(VERSION 2.8) set(APPNAME "markovTest") 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(SOURCE_FILES ${COMMON}/hw332_CMidiPacket.cpp ${COMMON}/hw411_rand_int.cpp ${COMMON}/hw421_CDelayMs.cpp ${COMMON}/hw422_CAppleMidiSynth.cpp c513_CMarkovBlues.cpp c513_main.cpp ) include_directories(${COMMON}) add_executable(${APPNAME} ${SOURCE_FILES}) target_link_libraries(${PROJECT_NAME} PRIVATE "-framework AudioToolbox") target_link_libraries(${PROJECT_NAME} PRIVATE "-framework CoreMIDI") target_link_libraries(${PROJECT_NAME} PRIVATE "-framework CoreAudio")
Build/run/listen
Study the code.
First you'll hear a random melody.
Then you'll hear a markov melody starting from the first note chosen in the random melody.
If you look at the code you'll see this. Try them both.
// This statement chooses the next note based on the previous one // note = mb.chooseMarkovNote(note); // This statement chooses all notes based on the starting note // that was set as the first starting note of the random melody note = mb.chooseMarkovNote(startNote);
Homework 5.1
Setup hw511
cd $HOME312/cs312/hw51 mkdir hw511_blues cd hw511_blues # files assigned previously cp $HOME312/cs312/hw43/hw431_drumMachine/hw431_CDrumTrack.cpp . cp $HOME312/cs312/hw43/hw431_drumMachine/hw431_CDrumTrack.h . cp $HOME312/cs312/hw43/hw431_drumMachine/hw431_CDrumMachine.cpp . cp $HOME312/cs312/hw43/hw431_drumMachine/hw431_CDrumMachine.h . cp $HOME312/cs312/hw51/c512_pianochords/hw511_CBluesPianoTrack.cpp . cp $HOME312/cs312/hw51/c512_pianochords/hw511_CBluesPianoTrack.h . # end files assigned previously mkdir build touch CMakeLists.txt touch hw511_CBluesMelodyTrack.cpp touch hw511_CBluesMelodyTrack.h touch hw511_CBluesBassTrack.cpp touch hw511_CBluesBassTrack.h touch hw511_main.cpp
hw511_blues
In this homework you'll write a twelve bar blues using four instruments: Melody, Piano, Acoustic Bass, and Drums. We'll use the parts of hw411_CMidiTrack, hw422_CMidiTrack_dls and hw431_drumMachine.
The CDrumTrack and CDrumMachine classes are from hw431_drumMachine which should be completed before continuing with hw511_blues.
Files from previous assignments.
hw431_CDrumTrack.h
Copy your latest version here.
hw431_CDrumTrack.cpp
Copy your latest version here.
hw431_CDrumMachine.h
Copy your latest version here.
hw431_CDrumMachihne.cpp
Copy your latest version here.
CInstrument class
This was given in c511_CInstrument earlier and should be in your common folder.
hw511_CInstrument.h
Should be in your common folder.
hw511_CInstrument.cpp
Should be in your common folder.
Notes:
- Subclass of CMidiTrack.
- New constructor overload to initialize variables startNote and measNum
- One override method to implement CMidiTrack's pure virtual function write_track()
- The keyword override is optional but helps indicate that it is overriding a function in the parent class
- One pure virtual function virtual write_one_measure( int meas, int startNote, int chan ) = 0;
This function will be implemented differently in each of the Melody, Piano, and Bass subclasses - CBluesMelodyTrack, CBluesPianoTrack, and CBluesBassTrack are subclasses of CInstrument which is in turn a sublcass of CMidiTrack.
CBluesMelodyTrack, CBluesPianoTrack, CBluesBassTrack classes
- Are all subclasses of CInstrument.
- All have a similar .h interface file.
- You'll need to implement two functions:
the constructor and the pure virtual function
virtual void write_one_measure(int meas, int chan) = 0;
CBluesMelodyTrack class
hw511_CBluesMelodyTrack.h
// hw511_CBluesMelodyTrack.h #ifndef HW511_CBLUESMELODYTRACK_H_ #define HW511_CBLUESMELODYTRACK_H_ #ifndef HW511_CINSTRUMENT_H_ #include "hw511_CInstrument.h" #endif // DO NOT MODIFY class CBluesMelodyTrack : public CInstrument { public: // constructor CBluesMelodyTrack(uint32_t beginTm, uint32_t endTm, int scaleOffset, int chan, int patch, int vol, int pan, int startNote, int meas); // This is the only function needed void write_one_measure(int meas, int chan) override; }; #endif // HW511_CBLUESMELODYTRACK_H_
hw511_CBluesMelodyTrack.cpp
// hw511_CBluesMelodyTrack #ifndef HW511_CBLUESMELODYTRACK_H_ #include "hw511_CBluesMelodyTrack.h" #endif #ifndef HW411_RAND_INT_H_ #include "hw411_rand_int.h" #endif #include <cstdint> #include <vector> const std::vector<int> vBluesScale = {0, 3, 4, 7, 9, 10, 12}; // rock blues scale const std::vector<uint32_t> vTime = {500, 1000, 1500, 2000, 2500}; // additional constructor CBluesMelodyTrack::CBluesMelodyTrack(uint32_t beginTm, uint32_t endTm, int scaleOffset, int chan, int patch, int vol, int pan, int startNote, int meas) : CInstrument(beginTm, endTm, scaleOffset, chan, patch, vol, pan, startNote, meas) {} void CBluesMelodyTrack::write_one_measure(int meas, int chan) { // kNOTE_DURATION has to be smaller than smallest delta time difference in vTime const int kNOTE_DURATION = 300; CMidiPacket mp; // use get_note_offset(meas, get_start_note()) to get the pitch offset based on the chord used in meas // use get_measure_start_time(meas) to get the starting time for the measure we're writing for (auto itr : vTime) { // get a random index into vBluesScale // set note to vBluesScale.at(index); // Use push_non() to write the Note On message // Use push_nof() to write the Note Off message } set_meas_num(get_meas_num() + 1); std::cout << "Write: CBluesMelodyTrack::write_one_measure(int meas, int chan)\n"; }
CBluesPianoTrack class
hw511_CBluesPianoTrack.h
Already copied from c512_pianoChords
hw511_CBluesPianoTrack.cpp
Already copied from c512_pianoChords
Note:
The CBluesPianoTrack code is complete and should not need changes.
CBluesBassTrack class
hw511_CBluesBassTrack.h
// hw511_CBluesBassTrack.h #ifndef HW511_CBLUESBASSTRACK_H_ #define HW511_CBLUESBASSTRACK_H_ #ifndef HW511_CINSTRUMENT_H_ #include "hw511_CInstrument.h" #endif // DO NOT MODIFY class CBluesBassTrack : public CInstrument { public: // constructor CBluesBassTrack(uint32_t beginTm, uint32_t endTm, int scaleOffset, int chan, int patch, int vol, int pan, int startNote, int meas); // This is the only function needed void write_one_measure(int meas, int chan) override; }; #endif // HW511_CBLUESBASSTRACK_H_
hw511_CBluesBassTrack.cpp
// hw511_CBluesBassTrack.cpp #ifndef HW511_CBLUESBASSTRACK_H_ #include "hw511_CBluesBassTrack.h" #endif // Store note and time together struct NoteTm { int note; int tm; }; // clang-format off const std::vector<NoteTm> one_meas_vec = { {7, 0}, {0, 500}, {9, 1000}, {0, 1500}, {10, 2000}, {0, 2500}, {9, 3000}, {0, 3500} }; // Alternate choice or write your own // const std::vector<NoteTm> one_meas_vec = { // {0, 0}, // {4, 500}, // {7, 1000}, // {9, 1500}, // {10, 2000}, // {9, 2500}, // {7, 3000}, // {4, 3500} // }; // clang-format on // additional constructor CBluesBassTrack::CBluesBassTrack(uint32_t beginTm, uint32_t endTm, int scaleOffset, int chan, int patch, int vol, int pan, int startNote, int meas) : CInstrument(beginTm, endTm, scaleOffset, chan, patch, vol, pan, startNote, meas) {} void CBluesBassTrack::write_one_measure(int meas, int chan) { CMidiPacket mp; // use get_note_offset(meas, get_start_note()) to get the pitch offset based on the chord used in meas // use get_measure_start_time(meas) to get the starting time for the measure we're writing for (auto itr : one_meas_vec) { // get a random index into vBluesScale // set note to vBluesScale.at(index); // Use push_non() to write the Note On message // Use push_nof() to write the Note Off message } set_meas_num(get_meas_num() + 1); }
main()
hw511_main.cpp
This is the complete code I used.
// hw511_main.cpp #ifndef HW511_CINSTRUMENT_H_ #include "hw511_CInstrument.h" #endif #ifndef HW511_CBLUEMELODYTRACk_H #include "hw511_CBluesMelodyTrack.h" #endif #ifndef HW511_CBLUESPIANOTRACK_H #include "hw511_CBluesPianoTrack.h" #endif #ifndef HW511_CBLUESBASSTRACK_H #include "hw511_CBluesBassTrack.h" #endif #ifndef hw431_CDRUMMACHINE_H_ #include "hw431_CDrumMachine.h" #endif #ifndef HW421_CDELAYMS_H_ #include "hw421_CDelayMs.h" #endif #ifndef HW422_CAPPLEMIDISYNTH_H_ #include "hw422_CAppleMidiSynth.h" #endif #include <vector> using namespace CMP33; std::vector<CMidiPacket> vplay; void write_melody() { // S A X uint32_t startTm = 0; int scaleOffset = 0; int chan = 0; int patch = 56; // Trumpet // Other atches I tried // Tenor Sax // Electric Guitar (jazz) // Drawbar organ // Lead 2 (sawtooth) // Clarinet int vol = 127; int pan = 32; // pan left -32 from center 64 int startNote = 60; int meas = 0; // create the saxophone track CBluesMelodyTrack sax_trk(startTm, kSONG_END_TIME, scaleOffset, chan, patch, vol, pan, startNote, meas); sax_trk.write_track(); // append to vplay std::copy(sax_trk.vtrk.begin(), sax_trk.vtrk.end(), std::back_inserter(vplay)); } void write_piano() { // P I A N O uint32_t startTm = 0; int scaleOffset = 0; int chan = 1; int patch = 0; // piano int vol = 127; int pan = 64; // pan center int startNote = 48; int meas = 0; CBluesPianoTrack pno_trk(startTm, kSONG_END_TIME, scaleOffset, chan, patch, vol, pan, startNote, meas); pno_trk.write_track(); // append to vplay std::copy(pno_trk.vtrk.begin(), pno_trk.vtrk.end(), std::back_inserter(vplay)); } void write_bass() { // B A S S uint32_t startTm = 0; int scaleOffset = 0; int chan = 2; int patch = 32; // acoustic bass int vol = 127; int pan = 92; // pan right +32 from center 64 int startNote = 36; int meas = 0; CBluesBassTrack bass_trk(startTm, kSONG_END_TIME, scaleOffset, chan, patch, vol, pan, startNote, meas); bass_trk.write_track(); // append to vplay std::copy(bass_trk.vtrk.begin(), bass_trk.vtrk.end(), std::back_inserter(vplay)); } void write_drums() { const uint8_t kRIDE_CYMBAL = 51; // create the drum machine // You do not have to use every row // This is the pattern I made up // You can make up your own. CDrumMachine dm; dm.make_track(kBASS_DRUM, {"1000", "1000", "1000", "1000"}); dm.make_track(kSNARE_DRUM, {"0001", "0010", "0001", "0010"}); // dm.make_track(kCLAP, {"0000", "0000", "0000", "0010"}); dm.make_track(kRIDE_CYMBAL, {"1000", "1000", "1000", "1000"}); dm.make_track(kCLOSED_HIHAT, {"0010", "0010", "0010", "0011"}); // dm.make_track(kLOW_TOM, {"0010", "0000", "0000", "0000"}); dm.make_track(kHIGH_TOM, {"0001", "0001", "0010", "0010"}); // dm.make_track(kWOOD_BLOCK, {"0010", "0100", "0100", "0101"}); int loops = kSONG_END_TIME / 4000; for (auto n = 0; n < dm.vDrumTracks.size(); ++n) { dm.vDrumTracks[n].write_track(loops); // append each drum track to vplay std::copy(dm.vDrumTracks[n].vtrk.begin(), dm.vDrumTracks[n].vtrk.end(), std::back_inserter(vplay)); } } int main() { // write tracks write_melody(); write_piano(); write_bass(); write_drums(); // sort the playback vector using CMidiPacket43::operator< std::sort(vplay.begin(), vplay.end()); // output for MIDIDisplay for (auto itr : vplay) std::cout << itr; CDelayMs::s_tempo = 125; CAppleMidiSynth ams; ams.send(vplay); }
CMakeLists.txt
cmake_minimum_required(VERSION 3.5) set(APP_NAME "blues51") project(${APP_NAME}) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -Wall ") # change this to your $HOME directory set(HOME "/Users/je/cs312/_cs312") set(COMMON "${HOME}/common") set(SOURCE_FILES ${COMMON}/hw332_CMidiPacket.cpp ${COMMON}/hw423_CMidiTrack.cpp ${COMMON}/hw411_rand_int.cpp ${COMMON}/hw421_CDelayMs.cpp ${COMMON}/hw422_CAppleMidiSynth.cpp hw511_CInstrument.cpp hw511_CBluesMelodyTrack.cpp hw511_CBluesBassTrack.cpp hw511_CBluesPianoTrack.cpp hw431_CDrumMachine.cpp hw431_CDrumTrack.cpp hw511_main.cpp ) include_directories(${COMMON}) add_executable(${APP_NAME} ${SOURCE_FILES}) target_link_libraries(${PROJECT_NAME} PRIVATE "-framework AudioToolbox") target_link_libraries(${PROJECT_NAME} PRIVATE "-framework CoreMIDI") target_link_libraries(${PROJECT_NAME} PRIVATE "-framework CoreAudio")
Build and Run
You'll get three warnings from the write_one_measure functions in the Melody and Bass tracks.
You'll get output telling you to write those functions.
On the plus side you'll get to hear the drums and piano duet following the blues form.
Assignment hw411_blues
- Implement the two missing write_one_measure functions in the Melody and Bass tracks.
- Feel free to change notes, durations, patterns to make it more interesting, but stay within the blues scale and form.
- You can simplify the drum machine by commenting out any combination of the eight drum tracks in void CDrumMachine::setup_patterns().
hw511_blues continued
This assignments adds Markov chain rules for changing the melody notes. The hard work has already been done in c513_markovChain.
Setup
cd $HOME312/cs312/hw51/hw511_blues cp $HOME312/cs312/hw51/c513_markovchain/c513_CMarkovBlues.cpp . cp $HOME312/cs312/hw51/c513_markovchain/c513_CMarkovBlues.h .
Changes
CMakeLists.txt
Add c513_CMarkovBlues to the SOURCE_FILES section
hw511_CBluesMelodyTrack.cpp
Add these defines above the constructor
// set to 1 to use markov // wet to 0 to use random #define USE_MARKOV 1 #if USE_MARKOV #ifndef C513_CMARKOVBLUES_H_ #include "c513_CMarkovBlues.h" #endif #endif // additional constructor CBluesMelodyTrack::CBluesMelodyTrack(uint32_t beginTm, uint32_t endTm, ...
Add these defines to the write_one_measure function
void CBluesMelodyTrack::write_one_measure(int meas, int chan) { // kNOTE_DURATION has to be smaller than smallest delta time difference in vTime const int kNOTE_DURATION = 300; CMidiPacket mp; #if USE_MARKOV CMarkovBlues mb; // choose starting notes only on 0,4,7 of the scale int n = get_rand_int(0, 2); int note{0}; if (meas % 12 == 0) note = vBluesScale.at(0); else if (n == 1) note = vBluesScale.at(4); else if (n == 3) note = vBluesScale.at(7); else note = vBluesScale.at(0); #endif
Also inside the for loop of write_one_measure add this
for (auto itr : vTime) { #if USE_MARKOV note = mb.chooseMarkovNote(note); #else int ni = get_rand_int(0, vBluesScale.size() - 1); int note = vBluesScale.at(ni); #endif push_non(meas_tm + itr, chan, note + offset, 100); push_nof(meas_tm + kNOTE_DURATION + itr, chan, note + offset); }
Build and run
cmake .. && make && ./blues51
Change the #define USE_MARKOV from 1 to 0 to hear the difference between random notes and Markov rule smoothing. Use whichever one you like better. Or rewrite the Markov rules.
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
Hand-in folder contents
$HOME312/common ├── hw332_CMidiPacket.cpp ├── hw332_CMidiPacket.h ├── hw411_rand_int.cpp ├── hw411_rand_int.h ├── hw421_CDelayMs.cpp ├── hw421_CDelayMs.h ├── hw422_CAppleMidiSynth.cpp ├── hw422_CAppleMidiSynth.h ├── hw423_CMidiTrack.cpp ├── hw423_CMidiTrack.h ├── hw511_CInstrument.cpp ├── hw511_CInstrument.h └── hw511_CInstrument.h $HOME312/cs312/hw51/hw511_blues ├── CMakeLists.txt ├── build ├── c513_CMarkovBlues.cpp ├── c513_CMarkovBlues.h ├── hw431_CDrumMachine.cpp ├── hw431_CDrumMachine.h ├── hw431_CDrumTrack.cpp ├── hw431_CDrumTrack.h ├── hw511_CBluesBassTrack.cpp ├── hw511_CBluesBassTrack.h ├── hw511_CBluesMelodyTrack.cpp ├── hw511_CBluesMelodyTrack.h ├── hw511_CBluesPianoTrack.cpp ├── hw511_CBluesPianoTrack.h └── hw511_main.cpp