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.songmelody; 7 8 import std.algorithm : map, joiner; 9 import std.array : array, join; 10 import std..string : format; 11 12 import musicpulator.songnote; 13 import musicpulator.musicalscale; 14 import musicpulator.musicalprogression; 15 import musicpulator.tools; 16 import musicpulator.core; 17 18 /// Alias for the note collection. 19 private alias NoteCollection = InternalMaxSizeCollection!(SongNote, 32); 20 21 /// Wrapper around a song melody. 22 final class SongMelody 23 { 24 private: 25 /// The scales of the melody. 26 MusicalScale[] _scales; 27 /// Boolean determining whether the melody has found its scale or not yet. 28 bool _foundScale; 29 /// The progression of the melody. 30 MusicalProgression _progression; 31 /// Boolean determining whether the melody has found its progression or not yet. 32 bool _foundProgression; 33 /// The notes of the melody. 34 NoteCollection _notes; 35 36 public: 37 final: 38 /** 39 * Creates a new song melody. 40 * Params: 41 * notes = The notes of the melody. 42 */ 43 this(SongNote[] notes) 44 { 45 if (notes && notes.length) 46 { 47 foreach (note; notes.map!(n => new SongNote(n.note, n.length, n.step, n.octave, n.bar))) 48 { 49 _notes.add(note); 50 } 51 } 52 } 53 54 /// Creates a new song melody. 55 this() 56 { 57 58 } 59 60 @property 61 { 62 /// Gets the notes of the melody. 63 NoteCollection notes() { return _notes; } 64 65 /// Gets the scales of the melody. 66 const(MusicalScale[]) scales() 67 { 68 if (!_foundScale) 69 { 70 _foundScale = true; 71 72 _scales = findScales(_notes.map!(n => n.note).array); 73 } 74 75 return _scales; 76 } 77 78 /// Gets the progression of the melody. 79 MusicalProgression progression() 80 { 81 import musicpulator.notes; 82 import musicpulator.direction; 83 import musicpulator.melodies; 84 85 if (!_foundProgression) 86 { 87 _foundProgression = true; 88 89 NoteId[] noteIds; 90 91 foreach (note; _notes) 92 { 93 noteIds ~= getNoteId(note.note, note.octave); 94 } 95 96 if (noteIds && noteIds.length) 97 { 98 auto directions = getNoteDirections(noteIds); 99 100 if (directions) 101 { 102 _progression = getProgression(directions); 103 } 104 } 105 } 106 107 return _progression; 108 } 109 } 110 111 /** 112 * Appends a note to the melody. 113 * Params: 114 * note = The note to append. 115 */ 116 void appendNote(SongNote note) 117 { 118 if (!note) 119 { 120 return; 121 } 122 123 _notes.add(note); 124 125 _foundScale = false; 126 _foundProgression = false; 127 } 128 129 /// Converts the musical notes to their relative flat notes. 130 void convertToFlat() 131 { 132 foreach (note; _notes) 133 { 134 note.convertToFlat; 135 } 136 137 _foundScale = false; 138 _foundProgression = false; 139 } 140 141 /// Converts the musical notes from flat notes to their relative notes. 142 void convertFromFlat() 143 { 144 foreach (note; _notes) 145 { 146 note.convertFromFlat(); 147 } 148 149 _foundScale = false; 150 _foundProgression = false; 151 } 152 153 /// Converts the melody to a string. This will call toJson(). 154 override string toString() 155 { 156 return toJson(); 157 } 158 159 /// Converts the melody to json. 160 string toJson() 161 { 162 auto notesJson = "["; 163 164 if (_notes.length) 165 { 166 foreach (note; _notes) 167 { 168 notesJson ~= note.toJson() ~ ","; 169 } 170 171 notesJson.length -= 1; 172 } 173 174 notesJson ~= "]"; 175 176 auto scalesJson = "["; 177 178 if (scales && _scales.length) 179 { 180 foreach (scale; _scales) 181 { 182 scalesJson ~= scale.toJson() ~ ","; 183 } 184 185 scalesJson.length -= 1; 186 } 187 188 scalesJson ~= "]"; 189 190 return `{"notes":%s,"scales":%s,"progression":%s}` 191 .format(notesJson, scalesJson, progression.toJson()); 192 } 193 194 /// Converts the melody to xml. 195 string toXml() 196 { 197 auto noteXml = ""; 198 199 if (_notes.length) 200 { 201 noteXml = _notes.map!(n => n.toXml()).array.join; 202 } 203 204 auto scaleXml = ""; 205 206 if (scales && scales.length) 207 { 208 scaleXml = _scales.map!(s => s.toXml()).array.join; 209 } 210 211 return `<SongMelody progression="%s"><SongNotes>%s</SongNotes><Scales>%s</Scales></SongMelody>` 212 .format(progression.toXml(), noteXml, scaleXml); 213 } 214 }