1 /* B.Scale
2  * Basic music note operation tools
3  *
4  * Copyright (C) 2018, 2019 by Sven Jähnichen
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3, or (at your option)
9  * any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  */
20 
21 #ifndef BSCALE_HPP_
22 #define BSCALE_HPP_
23 
24 #include <cstdint>
25 #include <array>
26 #include <string>
27 #include <cmath>
28 #include <cstring>
29 
30 #define ENOTE -128
31 
32 #define CROMATICSCALE 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
33 #define MAJORSCALE 0, 2, 4, 5, 7, 9, 11, ENOTE, ENOTE, ENOTE, ENOTE, ENOTE
34 #define MINORSCALE 0, 2, 3, 5, 7, 8, 10, ENOTE, ENOTE, ENOTE, ENOTE, ENOTE
35 #define HARMONICMAJORSCALE 0, 2, 4, 5, 7, 8, 11, ENOTE, ENOTE, ENOTE, ENOTE, ENOTE
36 #define HARMONICMINORSCALE 0, 2, 3, 5, 7, 8, 11, ENOTE, ENOTE, ENOTE, ENOTE, ENOTE
37 #define MELODICMINORSCALE 0, 2, 3, 5, 7, 9, 10, ENOTE, ENOTE, ENOTE, ENOTE, ENOTE
38 #define DORIANSCALE 0, 2, 3, 5, 7, 9, 10, ENOTE, ENOTE, ENOTE, ENOTE, ENOTE
39 #define PHRYGIANSCALE 0, 1, 3, 5, 7, 8, 10, ENOTE, ENOTE, ENOTE, ENOTE, ENOTE
40 #define LYDIANSCALE 0, 2, 4, 6, 7, 9, 11, ENOTE, ENOTE, ENOTE, ENOTE, ENOTE
41 #define MIXOLYDIANSCALE 0, 2, 4, 5, 7, 9, 10, ENOTE, ENOTE, ENOTE, ENOTE, ENOTE
42 #define LOKRIANSCALE 0, 1, 3, 5, 6, 8, 10, ENOTE, ENOTE, ENOTE, ENOTE, ENOTE
43 #define HUNGARIANMINORSCALE 0, 2, 3, 6, 7, 8, 11, ENOTE, ENOTE, ENOTE, ENOTE, ENOTE
44 #define MAJORPENTATONICSCALE 0, 2, 4, 7, 9, ENOTE, ENOTE, ENOTE, ENOTE, ENOTE, ENOTE, ENOTE
45 #define MINORPENTATONICSCALE 0, 3, 5, 7, 10, ENOTE, ENOTE, ENOTE, ENOTE, ENOTE, ENOTE, ENOTE
46 
47 typedef std::array<int, 12> BScaleNotes;
48 
49 typedef enum {
50 	FLAT			= -1,
51 	NATURAL			= 0,
52 	SHARP			= 1
53 } SignatureIndex;
54 
55 const BScaleNotes defaultScale = {CROMATICSCALE};
56 
57 const char noteSymbols[12] = {'C', 0, 'D', 0, 'E', 'F', 0, 'G', 0, 'A', 0, 'B'};
58 
59 class BScale {
60 public:
61 	BScale (const int root, const BScaleNotes& elementarray);
62 	BScale (const int root, const SignatureIndex signature, const BScaleNotes& elementarray);
63 	void setRoot (int root);
64 	int getRoot ();
65 	void setScale (BScaleNotes& elementarray);
66 	BScaleNotes getScale ();
67 	int getMIDInote (int element);
68 	int getElement (int midiNote);
69 	int getSize ();
70 	std::string getSymbol (int element);
71 
72 protected:
73 	void createSymbols ();
74 	int rootNote;
75 	SignatureIndex signature;
76 	BScaleNotes scale;
77 	char symbols[12][6];
78 };
79 
BScale(const int root,const BScaleNotes & elementarray)80 BScale::BScale (const int root, const BScaleNotes& elementarray) : BScale (root, NATURAL, elementarray) {}
BScale(const int root,const SignatureIndex signature,const BScaleNotes & elementarray)81 BScale::BScale (const int root, const SignatureIndex signature, const BScaleNotes& elementarray) :
82 rootNote (root), signature (signature), scale (elementarray)
83 {
84 	memset (symbols, 0, sizeof symbols);
85 	createSymbols ();
86 }
87 
createSymbols()88 void BScale::createSymbols ()
89 {
90 	// Build a flat scale and a sharp scale
91 	char flatSymbols[12][6];
92 	char sharpSymbols[12][6];
93 	memset (flatSymbols, 0, sizeof flatSymbols);
94 	memset (sharpSymbols, 0, sizeof sharpSymbols);
95 	for (int i = 0; (i < 12) && (scale[i] != ENOTE); ++i)
96 	{
97 		int midiNote = getMIDInote (i);
98 		if ((midiNote >= 0) && (midiNote <= 127))
99 		{
100 			// Note without signature => take this symbol
101 			if (noteSymbols[midiNote % 12])
102 			{
103 				flatSymbols[i][0] = noteSymbols[midiNote % 12];
104 				sharpSymbols[i][0] = noteSymbols[midiNote % 12];
105 			}
106 
107 			// Or with signature => build from neighbor
108 			else
109 			{
110 				flatSymbols[i][0] = noteSymbols[(midiNote + 1) % 12];
111 				strcat (flatSymbols[i], "b");
112 				sharpSymbols[i][0] = noteSymbols[(midiNote + 11) % 12];
113 				strcat (sharpSymbols[i], "#");
114 			}
115 		}
116 
117 		// Note out of range => break
118 		else break;
119 	}
120 
121 	switch (signature)
122 	{
123 		case FLAT:	memcpy (symbols, flatSymbols, sizeof symbols);
124 					break;
125 
126 		case SHARP:	memcpy (symbols, sharpSymbols, sizeof symbols);
127 					break;
128 
129 		default:	{
130 						// Count redundant symbols
131 						int flatRedunds = 0;
132 						int sharpRedunds = 0;
133 						for (int i = 1; (i < 12) && (flatSymbols[i][0]); ++i)
134 						{
135 							if (flatSymbols[i][0] == flatSymbols[i - 1][0]) ++flatRedunds;
136 							if (sharpSymbols[i][0] == sharpSymbols[i - 1][0]) ++sharpRedunds;
137 						}
138 
139 						// Store the more relevant scale
140 						if (flatRedunds < sharpRedunds) memcpy (symbols, flatSymbols, sizeof symbols);
141 						else memcpy (symbols, sharpSymbols, sizeof symbols);
142 					}
143 	}
144 }
145 
setRoot(int root)146 void BScale::setRoot (int root)
147 {
148 	rootNote = root;
149 	createSymbols ();
150 }
151 
getRoot()152 int BScale::getRoot () {return rootNote;}
153 
setScale(BScaleNotes & elementarray)154 void BScale::setScale (BScaleNotes& elementarray)
155 {
156 	int i = 0;
157 	for (; (i < 12) && (elementarray[i] != ENOTE); ++i) scale[i] = elementarray[i] % 12;
158 	for (; i < 12; ++i) scale[i] = ENOTE;
159 	createSymbols ();
160 }
161 
getScale()162 BScaleNotes BScale::getScale () {return scale;}
163 
164 /* Calculates a MIDI note for an element within a BScale
165  * @param element: note position relative to root note in number of (scale-specific) notes,
166  *                 e.g. note F in C major will be element 3
167  * Returns MIDI note (00 .. 7F) or ENOTE if out of range
168  */
getMIDInote(int element)169 int BScale::getMIDInote (int element)
170 {
171 	int size = getSize ();
172 	int octave = (int) floor ((float) element / size);
173 	int midiNote = octave * 12 + rootNote + scale [element - octave * size];
174 	if ((midiNote >=0) && (midiNote <= 127)) return midiNote;
175 	else return ENOTE;
176 }
177 
178 /* Calculates the number of an element (note) within a BScale
179  * @param midiNote: MIDI note (00 .. 7F)
180  * Returns the element (note) relative to the root of the scale, e. g.
181  *         returns 3 for the note F4  in a C(4) major scale
182  */
getElement(int midiNote)183 int BScale::getElement (int midiNote)
184 {
185 	int ssize = getSize ();
186 	int octDiff = (int) floor (((float)midiNote - rootNote) / 12);
187 	int noteDiff = (midiNote - rootNote) - octDiff * 12;
188 
189 	for (int i = 0; i < ssize; ++i)
190 	{
191 		if (scale[i] == noteDiff) return i + octDiff * ssize;
192 	}
193 
194 	return ENOTE;
195 }
196 
197 /* Returns number of elements of the BScale object, max. 12
198  *
199  */
getSize()200 int BScale::getSize()
201 {
202 	for (int8_t i = 0; i < 12; ++i)
203 	{
204 		if (scale[i] == ENOTE) return i;
205 	}
206 	return 12;
207 
208 }
209 
210 /* Returns the note symbol for an element (note) within a BScale.
211  * !!! Don't use this method in a realtime process !!!
212  * @param element: note position relative to root note in number of (scale-specific) notes,
213  *                 e.g. note F in C major will be element 3
214  * @return	Note symbol string
215  *
216  */
getSymbol(int element)217 std::string BScale::getSymbol (int element)
218 {
219 	if (element < 0) return "";
220 
221 	int ssize = getSize ();
222 	return std::string (symbols[element % ssize]);
223 }
224 
225 
226 #endif /* BSCALE_HPP_ */
227