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 }