Last active
July 25, 2018 06:32
-
-
Save jgcoded/5a0b83b5e90c1f099ec4b17e008366bb to your computer and use it in GitHub Desktop.
Code that uses music theory to play scales and make chords
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MusicBeep.cpp : music theory code to make scales and chords | |
// | |
#include "stdafx.h" | |
#include <Windows.h> | |
#include <cmath> | |
#include <iostream> | |
#include <vector> | |
#include <string> | |
#include <algorithm> | |
#include <cctype> | |
using namespace std; | |
// Equal temperament tuning | |
enum class note { | |
C, | |
Csharp, | |
D, | |
Eflat, | |
E, | |
F, | |
Fsharp, | |
G, | |
Aflat, | |
A, | |
Bflat, | |
B, | |
}; | |
enum class intervals { | |
unison = 0, | |
minorSecond = 1, | |
majorSecond = 2, | |
minorThird = 3, | |
majorThird = 4, | |
fourth = 5, | |
diminishedFifth = 6, | |
fifth = 7, | |
minorSixth = 8, | |
majorSixth = 9, | |
minorSeventh = 10, | |
majorSeventh = 11, | |
octave = 12, | |
}; | |
constexpr float middleC = 261.63f; | |
float raiseByInterval(float freq, intervals interval) { | |
// constexpr float semitoneFactor = 1.0594630944f; 2 to the twelfth root | |
return freq * std::pow(2.0f, (int)interval / 12.0f); | |
} | |
auto intervalFactory(intervals interval) { | |
return [interval](float freq) { return raiseByInterval(freq, interval); }; | |
} | |
const auto unison = intervalFactory(intervals::unison); | |
const auto minorSecond = intervalFactory(intervals::minorSecond); | |
const auto majorSecond = intervalFactory(intervals::majorSecond); | |
const auto minorThird = intervalFactory(intervals::minorThird); | |
const auto majorThird = intervalFactory(intervals::majorThird); | |
const auto fourth = intervalFactory(intervals::fourth); | |
const auto diminishedFifth = intervalFactory(intervals::diminishedFifth); | |
const auto fifth = intervalFactory(intervals::fifth); | |
const auto minorSixth = intervalFactory(intervals::minorSixth); | |
const auto majorSixth = intervalFactory(intervals::majorSixth); | |
const auto minorSeventh = intervalFactory(intervals::minorSeventh); | |
const auto majorSeventh = intervalFactory(intervals::majorSeventh); | |
const auto octave = intervalFactory(intervals::octave); | |
float raiseBySemitone(float freq) { | |
return minorSecond(freq); | |
} | |
float raiseByTone(float freq) { | |
return majorSecond(freq); | |
} | |
vector<float> makeTetrachordPattern(float freq) { | |
vector<float> result; | |
result.push_back(freq); | |
freq = raiseByTone(freq); | |
result.push_back(freq); | |
freq = raiseByTone(freq); | |
result.push_back(freq); | |
freq = raiseBySemitone(freq); | |
result.push_back(freq); | |
return result; | |
} | |
vector<float> makeMajorScale(float freq) { | |
vector<float> result; | |
// tetrachord pattern | |
auto tetraChordOne = makeTetrachordPattern(freq); | |
freq = tetraChordOne.back(); | |
freq = raiseByTone(freq); | |
auto tetraChordTwo = makeTetrachordPattern(freq); | |
result.insert(result.end(), tetraChordOne.cbegin(), tetraChordOne.cend()); | |
result.insert(result.end(), tetraChordTwo.cbegin(), tetraChordTwo.cend()); | |
return result; | |
} | |
float getSemitonesFromC0(float freq) { | |
constexpr float C0 = 16.35f; | |
return 12.0f * log2(freq / C0); | |
} | |
int getOctave(float freq) { | |
return (int)(getSemitonesFromC0(freq) / 12); | |
} | |
int getSemitonesFromCx(float freq) { | |
return (int)getSemitonesFromC0(freq) % 12; | |
} | |
note frequencyToNote(float freq) { | |
auto semitones = getSemitonesFromCx(freq); | |
return (note)semitones; | |
} | |
string noteToString(note note) { | |
switch (note) { | |
case note::A: return "A"; | |
case note::Aflat: return "Ab"; | |
case note::B: return "B"; | |
case note::Bflat: return "Bb"; | |
case note::C: return "C"; | |
case note::Csharp: return "C#"; | |
case note::D: return "D"; | |
case note::E: return "E"; | |
case note::Eflat: return "Eb"; | |
case note::F: return "F"; | |
case note::Fsharp: return "F#"; | |
case note::G: return "G"; | |
default: | |
return "Not Available"; | |
}; | |
} | |
string frequencyToNoteName(float freq) { | |
return noteToString(frequencyToNote(freq)); | |
} | |
void printFrequencyAsNoteName(float freq) { | |
cout << frequencyToNoteName(freq); | |
} | |
void printFrequencyAsNoteNameWithOctave(float freq) { | |
printFrequencyAsNoteName(freq); | |
cout << getOctave(freq); | |
} | |
void printFrequenciesAsNoteNames(vector<float> freqs) { | |
for (const auto& freq : freqs) { | |
printFrequencyAsNoteName(freq); | |
cout << " "; | |
} | |
} | |
void playFrequencies(vector<float> freqs) { | |
constexpr int noteDurationMs = 300; | |
for (const auto& freq : freqs) { | |
Beep((DWORD)freq, noteDurationMs); | |
Sleep(noteDurationMs); | |
} | |
} | |
note nameToNote(string root) { | |
std::transform(root.begin(), root.end(), root.begin(), [](char c) { return std::toupper(c); }); | |
if (root == "A") { | |
return note::A; | |
} else if (root == "Ab" || root == "G#") { | |
return note::Aflat; | |
} else if (root == "B") { | |
return note::B; | |
} else if (root == "Bb" || root == "A#") { | |
return note::Bflat; | |
} | |
else if (root == "C") { | |
return note::C; | |
} | |
else if (root == "C#" || root == "Db") { | |
return note::Csharp; | |
} | |
else if (root == "D") { | |
return note::D; | |
} | |
else if (root == "E") { | |
return note::E; | |
} | |
else if (root == "Eb" || root == "D#") { | |
return note::Eflat; | |
} | |
else if (root == "F") { | |
return note::F; | |
} | |
else if (root == "F#" || root == "Gb") { | |
return note::Fsharp; | |
} | |
else if (root == "G") { | |
return note::G; | |
} | |
throw std::exception("Invalid root note provided as input"); | |
} | |
float noteToFrequency(note targetNote) { | |
auto currentNote = note::C; | |
auto frequency = middleC; | |
while (currentNote != targetNote) { | |
frequency = raiseBySemitone(frequency); | |
currentNote = (note)(((int)currentNote + 1) % (int)note::B); | |
} | |
return frequency; | |
} | |
float nameToFrequency(string name) { | |
return noteToFrequency(nameToNote(name)); | |
} | |
vector<float> makeTriad(float root) { | |
vector<float> result; | |
result.push_back(root); | |
result.push_back(majorThird(root)); | |
result.push_back(fifth(root)); | |
return result; | |
} | |
vector<float> makeDominantSeventhChord(float root) { | |
auto result = makeTriad(root); | |
result.push_back(minorSeventh(root)); | |
return result; | |
} | |
int main(int argc, const char** argv) | |
{ | |
if (argc < 2) { | |
cout << "usage: " << argv[0] << " mode" << endl; | |
return -1; | |
} | |
string mode = argv[1]; | |
if (mode == "circle") { | |
cout << "Circle of fifths: " << endl; | |
cout << "Major: " << endl; | |
auto freq = middleC; | |
do { | |
printFrequencyAsNoteName(freq); | |
cout << " "; | |
freq = fifth(freq); | |
} while (frequencyToNote(freq) != note::C); | |
} else if (mode == "scale") { | |
if (argc < 3) { | |
cout << "usage" << argv[0] << " mode " << " <root note>" << endl; | |
return -1; | |
} | |
string root = argv[2]; | |
if (root.length() > 2) { | |
cout << "root note name is too long. Valid values are C, C#, Ab, etc"; | |
} | |
auto frequency = nameToFrequency(root); | |
auto scale = makeMajorScale(frequency); | |
auto i = scale[0]; | |
auto iv = scale[3]; | |
auto v = scale[4]; | |
auto iChord = makeTriad(i); | |
auto ivChord = makeTriad(iv); | |
auto vChord = makeTriad(v); | |
auto dominantSeventh = makeDominantSeventhChord(v); | |
cout << root << " major scale: " << endl; | |
printFrequenciesAsNoteNames(scale); | |
cout << endl; | |
cout << endl; | |
cout << "primary chords: " << endl; | |
cout << "I: "; | |
printFrequenciesAsNoteNames(iChord); | |
cout << endl; | |
cout << endl; | |
cout << "IV: "; | |
printFrequenciesAsNoteNames(ivChord); | |
cout << endl; | |
cout << endl; | |
cout << "V: "; | |
printFrequenciesAsNoteNames(vChord); | |
cout << endl; | |
cout << endl; | |
cout << "Dominant Seventh chord: "; | |
printFrequencyAsNoteName(v); | |
cout << "7" << endl; | |
printFrequenciesAsNoteNames(dominantSeventh); | |
cout << endl; | |
playFrequencies(scale); | |
std::reverse(scale.begin(), scale.end()); | |
playFrequencies(scale); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment