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 }