1 /**
2 * Copyright © Underground Rekordz 2019
3 * License: MIT (https://github.com/UndergroundRekordz/Musicpulator/blob/master/LICENSE)
4 * Author: Jacob Jensen (bausshf)
5 */
6 module musicpulator.tools;
7 
8 import musicpulator.musicalnote;
9 import musicpulator.musicalscale;
10 
11 /**
12 * Converts a note into its respective flat note with a proper scale.
13 * Params:
14 *   note = The note to convert.
15 * Returns:
16 *   The respective flat note if any, otherwise the note itself.
17 */
18 MusicalNote convertToFlatNote(MusicalNote note)
19 {
20   switch (note)
21   {
22     case MusicalNote.cSharp:
23       return MusicalNote.dFlat;
24 
25     case MusicalNote.dSharp:
26       return MusicalNote.eFlat;
27 
28     case MusicalNote.fSharp:
29       return MusicalNote.gFlat;
30 
31     case MusicalNote.gSharp:
32       return MusicalNote.aFlat;
33 
34     case MusicalNote.aSharp:
35       return MusicalNote.bFlat;
36 
37     case MusicalNote.b:
38       return MusicalNote.cFlat;
39 
40     default:
41       return note;
42   }
43 }
44 
45 /**
46 * Converts a note from its respective flat note with a proper scale.
47 * Params:
48 *   note = The note to convert.
49 * Returns:
50 *   The note converted from its respective flat note if any, otherwise the note itself.
51 */
52 MusicalNote convertFromFlatNote(MusicalNote note)
53 {
54   switch (note)
55   {
56     case MusicalNote.dFlat:
57       return MusicalNote.cSharp;
58 
59     case MusicalNote.eFlat:
60       return MusicalNote.dSharp;
61 
62     case MusicalNote.gFlat:
63       return MusicalNote.fSharp;
64 
65     case MusicalNote.aFlat:
66       return MusicalNote.gSharp;
67 
68     case MusicalNote.bFlat:
69       return MusicalNote.aSharp;
70 
71     case MusicalNote.cFlat:
72       return MusicalNote.b;
73 
74     default:
75       return note;
76   }
77 }
78 
79 /**
80 * Translate a set of notes into their respective flat notes.
81 * Params:
82 *   notes = The notes.
83 * Returns:
84 *   A new set of notes with the respective flat notes.
85 */
86 MusicalNote[] translateToFlatNotes(MusicalNote[] notes)
87 {
88   MusicalNote[] newNotes;
89 
90   if (notes && notes.length)
91   {
92     foreach (note; notes)
93     {
94       newNotes ~= convertToFlatNote(note);
95     }
96   }
97 
98   return newNotes;
99 }
100 
101 /**
102 * Translate a set of notes from their respective flat notes.
103 * Params:
104 *   notes = The notes.
105 * Returns:
106 *   A new set of notes translated from their respective flat notes.
107 */
108 MusicalNote[] translateFromFlatNotes(MusicalNote[] notes)
109 {
110   MusicalNote[] newNotes;
111 
112   if (notes && notes.length)
113   {
114     foreach (note; notes)
115     {
116       newNotes ~= convertFromFlatNote(note);
117     }
118   }
119 
120   return newNotes;
121 }
122 
123 /**
124 * Converts a note into its respective flat note with a proper scale.
125 * Params:
126 *   note = The note to convert.
127 * Returns:
128 *   The respective flat note if any, otherwise the note itself.
129 */
130 MusicalScaleNote convertToFlatNote(MusicalScaleNote note)
131 {
132   switch (note.note)
133   {
134     case MusicalNote.cSharp:
135       return new MusicalScaleNote(MusicalNote.dFlat, note.octaveIncrementer);
136 
137     case MusicalNote.dSharp:
138       return new MusicalScaleNote(MusicalNote.eFlat, note.octaveIncrementer);
139 
140     case MusicalNote.fSharp:
141       return new MusicalScaleNote(MusicalNote.gFlat, note.octaveIncrementer);
142 
143     case MusicalNote.gSharp:
144       return new MusicalScaleNote(MusicalNote.aFlat, note.octaveIncrementer);
145 
146     case MusicalNote.aSharp:
147       return new MusicalScaleNote(MusicalNote.bFlat, note.octaveIncrementer);
148 
149     case MusicalNote.b:
150       return new MusicalScaleNote(MusicalNote.cFlat, note.octaveIncrementer);
151 
152     default:
153       return new MusicalScaleNote(note.note, note.octaveIncrementer);
154   }
155 }
156 
157 /**
158 * Converts a note from its respective flat note with a proper scale.
159 * Params:
160 *   note = The note to convert.
161 * Returns:
162 *   The note converted from its respective flat note if any, otherwise the note itself.
163 */
164 MusicalScaleNote convertFromFlatNote(MusicalScaleNote note)
165 {
166   switch (note.note)
167   {
168     case MusicalNote.dFlat:
169       return new MusicalScaleNote(MusicalNote.cSharp, note.octaveIncrementer);
170 
171     case MusicalNote.eFlat:
172       return new MusicalScaleNote(MusicalNote.dSharp, note.octaveIncrementer);
173 
174     case MusicalNote.gFlat:
175       return new MusicalScaleNote(MusicalNote.fSharp, note.octaveIncrementer);
176 
177     case MusicalNote.aFlat:
178       return new MusicalScaleNote(MusicalNote.gSharp, note.octaveIncrementer);
179 
180     case MusicalNote.bFlat:
181       return new MusicalScaleNote(MusicalNote.aSharp, note.octaveIncrementer);
182 
183     case MusicalNote.cFlat:
184       return new MusicalScaleNote(MusicalNote.b, note.octaveIncrementer);
185 
186     default:
187       return new MusicalScaleNote(note.note, note.octaveIncrementer);
188   }
189 }
190 
191 /**
192 * Translate a set of notes into their respective flat notes.
193 * Params:
194 *   notes = The notes.
195 * Returns:
196 *   A new set of notes with the respective flat notes.
197 */
198 MusicalScaleNote[] translateToFlatNotes(MusicalScaleNote[] notes)
199 {
200   MusicalScaleNote[] newNotes;
201 
202   if (notes && notes.length)
203   {
204     foreach (note; notes)
205     {
206       newNotes ~= convertToFlatNote(note);
207     }
208   }
209 
210   return newNotes;
211 }
212 
213 /**
214 * Translate a set of notes from their respective flat notes.
215 * Params:
216 *   notes = The notes.
217 * Returns:
218 *   A new set of notes translated from their respective flat notes.
219 */
220 MusicalScaleNote[] translateFromFlatNotes(MusicalScaleNote[] notes)
221 {
222   MusicalScaleNote[] newNotes;
223 
224   if (notes && notes.length)
225   {
226     foreach (note; notes)
227     {
228       newNotes ~= convertFromFlatNote(note);
229     }
230   }
231 
232   return newNotes;
233 }
234 
235 /**
236 * Translates a scale note into a natural note.
237 * Params:
238 *   scaleNote = The scale note to translate.
239 * Returns:
240 *   The natural note relative to the scale note.
241 */
242 MusicalNote translateToNaturalNote(MusicalScaleNote scaleNote)
243 {
244   return scaleNote.note;
245 }
246 
247 /**
248 * Translates a set of scale notes into a set of natural notes.
249 * Params:
250 *   scaleNotes = The scale notes to translate.
251 * Returns:
252 *   The set of natural notes relative to the scale notes.
253 */
254 MusicalNote[] translateToNaturalNotes(MusicalScaleNote[] scaleNotes)
255 {
256   MusicalNote[] notes;
257 
258   if (scaleNotes && scaleNotes.length)
259   {
260     foreach (scaleNote; scaleNotes)
261     {
262       notes ~= translateToNaturalNote(scaleNote);
263     }
264   }
265 
266   return notes;
267 }
268 
269 /**
270 * Gets the musical scale based on its scale name.
271 */
272 MusicalScale getScale(MusicalScaleName scaleName)
273 {
274   import std.array : split;
275 
276   auto data = (cast(string)scaleName).split(" ");
277   auto note = cast(MusicalNote)data[0];
278   auto type = cast(MusicalScaleType)data[1];
279 
280   return new MusicalScale(scaleName, note, type);
281 }
282 
283 /**
284 * Finds the best matching scales based on a set of given notes.
285 * Params:
286 *   notes =          The notes to match into a scale.
287 *   unorderedMatch = Boolean determining whether the matching is unordered or not. This is false by default.
288 */
289 MusicalScale[] findScales(MusicalNote[] notes, bool unorderedMatch = false)
290 {
291   import std.traits : EnumMembers;
292   import std.algorithm : canFind;
293 
294   if (!notes || !notes.length)
295   {
296     return null;
297   }
298 
299   bool[MusicalNote] noteMap;
300   MusicalNote[] uniqueNotes;
301 
302   foreach (note; notes)
303   {
304     if (!noteMap || note !in noteMap)
305     {
306       uniqueNotes ~= note;
307 
308       noteMap[note] = true;
309     }
310   }
311 
312   notes = uniqueNotes;
313 
314   MusicalScale[] currentMatch;
315   size_t currentMatchingNotes;
316 
317   static foreach (scaleName; [EnumMembers!MusicalScaleName])
318   {{
319     auto scale = getScale(scaleName);
320 
321     if (scale)
322     {
323       auto scaleNotes = scale.getNaturalNotes();
324       size_t matching;
325 
326       if (unorderedMatch)
327       {
328         foreach (note; notes)
329         {
330           if (scaleNotes.canFind(note))
331           {
332             matching++;
333           }
334         }
335       }
336       else
337       {
338         auto length = notes.length > scaleNotes.length ? scaleNotes.length : notes.length;
339 
340         foreach (i; 0 .. length)
341         {
342           if (scaleNotes[i] != notes[i])
343           {
344             break;
345           }
346           else
347           {
348             matching++;
349           }
350         }
351       }
352 
353       if (matching > currentMatchingNotes)
354       {
355         currentMatch = [scale];
356         currentMatchingNotes = matching;
357       }
358       else if (matching == currentMatchingNotes)
359       {
360         currentMatch ~= scale;
361       }
362     }
363   }}
364 
365   return currentMatch;
366 }