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.songchordentry; 7 8 import std.algorithm : map, joiner, sum; 9 import std.array : array, join; 10 import std..string : format; 11 12 import musicpulator.songnote; 13 import musicpulator.songchord; 14 import musicpulator.musicalscale; 15 import musicpulator.tools; 16 import musicpulator.core; 17 import musicpulator.constants; 18 19 /// Wrapper around a song chord entry. 20 final class SongChordEntry 21 { 22 private: 23 /// The notes. 24 InternalCollection!SongNote _notes; 25 /// The length. 26 size_t _length; 27 /// The bar. 28 size_t _bar; 29 /// The scales of the chord entry. 30 MusicalScale[] _scales; 31 /// Boolean determining whether the chord entry has found its scale or not yet. 32 bool _foundScale; 33 /// The parent chord. 34 SongChord _parentChord; 35 36 public: 37 final: 38 /** 39 * Creates a new chord entry. 40 * Params: 41 * length = The length. 42 * bar = The bar. 43 */ 44 this(size_t length, size_t bar) 45 { 46 _length = length; 47 _bar = bar; 48 } 49 50 /** 51 * Creates a new chord entry. 52 * Params: 53 * parentChord = The parent chord. 54 * length = The length. 55 * bar = The bar. 56 */ 57 this(size_t length, size_t bar, SongChord parentChord) 58 { 59 _length = length; 60 _bar = bar; 61 _parentChord = parentChord; 62 } 63 64 /** 65 * Creates a new chord entry. 66 * Params: 67 * length = The length. 68 * bar = The bar. 69 * notes = The notes. 70 */ 71 this(size_t length, size_t bar, SongNote[] notes) 72 { 73 _length = length; 74 _bar = bar; 75 76 if (notes && notes.length) 77 { 78 foreach (note; notes.map!(n => new SongNote(n.note, n.step, n.octave, this))) 79 { 80 _notes.add(note); 81 } 82 } 83 } 84 85 /** 86 * Creates a new chord entry. 87 * Params: 88 * length = The length. 89 * bar = The bar. 90 * notes = The notes. 91 * parentChord = The parent chord. 92 */ 93 this(size_t length, size_t bar, SongNote[] notes, SongChord parentChord) 94 { 95 _length = length; 96 _bar = bar; 97 _parentChord = parentChord; 98 99 if (notes && notes.length) 100 { 101 foreach (note; notes.map!(n => new SongNote(n.note, n.step, n.octave, this))) 102 { 103 _notes.add(note); 104 } 105 } 106 } 107 108 @property 109 { 110 /// Gets the notes of the chord entry. 111 InternalCollection!SongNote notes() { return _notes; } 112 113 /// Gets the scales of the chord entry. 114 const(MusicalScale[]) scales() 115 { 116 return scalesInternal(); 117 } 118 119 package(musicpulator) 120 { 121 /// Gets an array of the musical scales internally in the project. Used to escape const. 122 MusicalScale[] scalesInternal() 123 { 124 if (!_foundScale) 125 { 126 _foundScale = true; 127 128 _scales = findScales(_notes.map!(n => n.note).array); 129 } 130 131 return _scales; 132 } 133 } 134 135 /// Sets the length of the chord entry. 136 void length(size_t newLength) 137 { 138 size_t maxLength = songBarSize; 139 140 if (_parentChord && _parentChord.entries.length) 141 { 142 maxLength = songBarSize / _parentChord.entries.length; 143 } 144 145 if (newLength > maxLength) 146 { 147 newLength = maxLength; 148 } 149 else if (newLength == 0) 150 { 151 newLength = 1; 152 } 153 154 _length = newLength; 155 } 156 157 /// Gets the length of the chord entry. 158 size_t length() 159 { 160 size_t maxLength = songBarSize; 161 162 if (_parentChord && _parentChord.entries.length) 163 { 164 auto sumSize = _parentChord.entries.map!(e => e._length).sum; 165 166 if (sumSize >= 32) 167 { 168 maxLength = 0; 169 } 170 else 171 { 172 maxLength = songBarSize - sumSize; 173 } 174 } 175 176 if (_length > maxLength) 177 { 178 _length = maxLength; 179 } 180 else if (_length == 0) 181 { 182 _length = 1; 183 } 184 185 return _length; 186 } 187 188 /// Gets the bar of the chord entry. 189 size_t bar() 190 { 191 if (_parentChord) 192 { 193 return _parentChord.bar + _bar; 194 } 195 196 return _bar; 197 } 198 199 /// Sets the bar of the chord entry. 200 void bar(size_t newBar) 201 { 202 _bar = newBar; 203 } 204 205 /// Gets the parent chord. 206 SongChord parentChord() { return _parentChord; } 207 208 package(musicpulator) 209 { 210 /// Sets the parent chord. 211 void parentChord(SongChord chord) 212 { 213 _parentChord = chord; 214 } 215 } 216 } 217 218 /** 219 * Adds a note to the chord entry. 220 * Params: 221 * note = The note to add. 222 */ 223 void addNote(SongNote note) 224 { 225 if (!note) 226 { 227 return; 228 } 229 230 note.parentChordEntry = this; 231 232 _notes.add(note); 233 234 _foundScale = false; 235 } 236 237 /// Converts the musical notes to their relative flat notes. 238 void convertToFlat() 239 { 240 foreach (note; _notes) 241 { 242 note.convertToFlat; 243 } 244 245 _foundScale = false; 246 } 247 248 /// Converts the musical notes from flat notes to their relative notes. 249 void convertFromFlat() 250 { 251 foreach (note; _notes) 252 { 253 note.convertFromFlat(); 254 } 255 256 _foundScale = false; 257 } 258 259 /// Converts the chord entry to a string. This will call toJson(). 260 override string toString() 261 { 262 return toJson(); 263 } 264 265 /// Converts the chord entry to json. 266 string toJson() 267 { 268 auto notesJson = "["; 269 270 if (_notes.length) 271 { 272 foreach (note; _notes) 273 { 274 notesJson ~= note.toJson() ~ ","; 275 } 276 277 notesJson.length -= 1; 278 } 279 280 notesJson ~= "]"; 281 282 auto scalesJson = "["; 283 284 if (scales && _scales.length) 285 { 286 foreach (scale; _scales) 287 { 288 scalesJson ~= scale.toJson() ~ ","; 289 } 290 291 scalesJson.length -= 1; 292 } 293 294 scalesJson ~= "]"; 295 296 return `{"notes":%s,"scales":%s,"length":%d,"bar":%d}` 297 .format(notesJson, scalesJson, length, bar); 298 } 299 300 /// Converts the chord entry to xml. 301 string toXml() 302 { 303 auto noteXml = ""; 304 305 if (_notes.length) 306 { 307 noteXml = _notes.map!(n => n.toXml()).array.join; 308 } 309 310 auto scaleXml = ""; 311 312 if (scales && scales.length) 313 { 314 scaleXml = _scales.map!(s => s.toXml()).array.join; 315 } 316 317 return `<SongChordEntry length="%d" bar="%d"><SongNotes>%s</SongNotes><Scales>%s</Scales></SongChordEntry>` 318 .format(length, bar, noteXml, scaleXml); 319 } 320 }