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.songtrack;
7 
8 import std.string : format;
9 import std.conv : to;
10 
11 import musicpulator.songautomation;
12 import musicpulator.songchord;
13 import musicpulator.songmelody;
14 import musicpulator.songsequence;
15 import musicpulator.songpart;
16 import musicpulator.musicalscale;
17 import musicpulator.musicalprogression;
18 
19 /// Alias for a float automation.
20 private alias FloatAutomation = SongAutomation!float;
21 
22 /// Wrapper around a song track.
23 final class SongTrack
24 {
25   private:
26   /// The chord of the track.
27   SongChord _chord;
28   /// The melody of the track.
29   SongMelody _melody;
30   /// The sequence of the track.
31   SongSequence _sequence;
32 
33   /// The name of the track.
34   string _name;
35   /// The bar of the track.
36   size_t _bar;
37   /// The volume of the track.
38   FloatAutomation _volume;
39   /// The velocity of the track.
40   FloatAutomation _velocity;
41   /// The dryness of the track.
42   FloatAutomation _dry;
43   /// The wetness of the track.
44   FloatAutomation _wet;
45   /// The metadata.
46   string[string] _metaData;
47   /// The meta-automation.
48   FloatAutomation[string] _metaAutomation;
49   /// The parent part.
50   SongPart _parentPart;
51 
52   this()
53   {
54     _volume = new FloatAutomation("Volume", 1);
55     _velocity = new FloatAutomation("Velocity", 0.8);
56     _dry = new FloatAutomation("Dry", 0);
57     _wet = new FloatAutomation("Wet", 0);
58   }
59 
60   public:
61   final:
62   /**
63   * Creates a new track.
64   * Params:
65   *   chord = The chord.
66   */
67   this(SongChord chord)
68   {
69     this();
70 
71     _chord = chord;
72   }
73 
74   /**
75   * Creates a new track.
76   * Params:
77   *   melody = The melody.
78   */
79   this(SongMelody melody)
80   {
81     this();
82 
83     _melody = melody;
84   }
85 
86   /**
87   * Creates a new track.
88   * Params:
89   *   sequence = The sequence.
90   */
91   this(SongSequence sequence)
92   {
93     this();
94 
95     _sequence = sequence;
96   }
97 
98   @property
99   {
100     /// Gets the chord.
101     SongChord chord() { return _chord; }
102 
103     /// Sets the chord. This will nullify the melody and sequence if any.
104     void chord(SongChord newChord)
105     {
106       if (!newChord)
107       {
108         return;
109       }
110 
111       _melody = null;
112       _sequence = null;
113 
114       _chord = newChord;
115     }
116 
117     /// Gets the melody.
118     SongMelody melody() { return _melody; }
119 
120     /// Sets the melody. This will nullify the chord and sequence if any.
121     void melody(SongMelody newMelody)
122     {
123       if (!newMelody)
124       {
125         return;
126       }
127 
128       _chord = null;
129       _sequence = null;
130 
131       _melody = newMelody;
132     }
133 
134     /// Gets the sequence.
135     SongSequence sequence() { return _sequence; }
136 
137     /// Sets the sequence. This will nullify the chord and melody if any.
138     void sequence(SongSequence newSequence)
139     {
140       if (!newSequence)
141       {
142         return;
143       }
144 
145       _chord = null;
146       _melody = null;
147 
148       _sequence = newSequence;
149     }
150 
151     /// Gets the name of the track.
152     string name() { return _name; }
153 
154     /// Sets the name of the track.
155     void name(string newName)
156     {
157       _name = newName;
158     }
159 
160     /// Gets the bar of the track.
161     size_t bar() { return _bar; }
162 
163     /// Gets the relative bar.
164     size_t relativeBar()
165     {
166       if (_parentPart)
167       {
168         return _parentPart.bar + _bar;
169       }
170 
171       return _bar;
172     }
173 
174     /// Sets the bar of the track.
175     void bar(size_t newBar)
176     {
177       _bar = newBar;
178     }
179 
180     /// Gets the volume of the track.
181     FloatAutomation volume() { return _volume; }
182 
183     /// Gets the velocity of the track.
184     FloatAutomation velocity() { return _velocity; }
185 
186     /// Gets the dryness of the track.
187     FloatAutomation dry() { return _dry; }
188 
189     /// Gets the wetness of the track.
190     FloatAutomation wet() { return _wet; }
191 
192     /// Gets the scales of the track.
193     const(MusicalScale[][]) scales()
194     {
195       if (_chord) return _chord.scales;
196       else if (_melody) return [_melody.scales];
197       else return null;
198     }
199 
200     /// Gets the progression of the track.
201     MusicalProgression progression()
202     {
203       if (!_melody) return MusicalProgression.none;
204 
205       return _melody.progression;
206     }
207 
208     /// Gets the parent part.
209     SongPart parentPart() { return _parentPart; }
210 
211     package(musicpulator)
212     {
213       /// Sets the parent part.
214       void parentPart(SongPart newPart)
215       {
216         _parentPart = newPart;
217       }
218     }
219   }
220 
221   /**
222   * Sets metadata of the track.
223   * Params:
224   *   key =   The key of the metadata.
225   *   value = The value of the metadata.
226   */
227   void setMetaData(T)(string key, T value)
228   {
229     _metaData[key] = to!string(value);
230   }
231 
232   /**
233   * Gets metadata from the track.
234   * Params:
235   *   key = The key of the metadata to retrieve.
236   * Returns:
237   *   The metadata if existing, null otherwise.
238   */
239   T getMetaData(T = string)(string key)
240   {
241     if (key !in _metaData)
242     {
243       return T.init;
244     }
245 
246     auto value = _metaData.get(key, T.init);
247 
248     return to!T(value);
249   }
250 
251   /**
252   * Sets the meta-automation of  the track.
253   * Params:
254   *   key =             The key of the meta-automation.
255   *   value =           The value of the automation point.
256   *   automationPoint = The automation point to the set the value at.
257   */
258   void setMetaAutomation(string key, float value, size_t automationPoint)
259   {
260     if (automationPoint >= 32)
261     {
262       return;
263     }
264 
265     auto automation = _metaAutomation.get(key, null);
266 
267     if (!automation)
268     {
269       automation = new FloatAutomation(key, value);
270       _metaAutomation[key] = automation;
271     }
272     else
273     {
274       automation.modifyValue(value, automationPoint);
275     }
276   }
277 
278   /**
279   * Gets a meta automation.
280   * Params:
281   *   key = The key of the meta-automation to retrieve.
282   * Returns:
283   *   The meta-automation if found, null otherwise.
284   */
285   FloatAutomation getMetaAutomation(string key)
286   {
287     return _metaAutomation.get(key, null);
288   }
289 
290   /// Converts the song track to a string. Calls toJson().
291   override string toString()
292   {
293     return toJson();
294   }
295 
296   /// Converts the song track to json.
297   string toJson()
298   {
299     string metaDataEntry = "null";
300 
301     if (_metaData && _metaData.length)
302     {
303       metaDataEntry = "{";
304 
305       foreach (k,v; _metaData)
306       {
307         metaDataEntry ~= `"%s":"%s",`.format(k,v);
308       }
309 
310       metaDataEntry.length -= 1;
311       metaDataEntry ~= "}";
312     }
313 
314     string metaAutomationEntry = "null";
315 
316     if (_metaAutomation && _metaAutomation.length)
317     {
318       metaAutomationEntry = "{";
319 
320       foreach (k,v; _metaAutomation)
321       {
322         metaAutomationEntry ~= `"%s":%s,`.format(k,v.toJson());
323       }
324 
325       metaAutomationEntry.length -= 1;
326       metaAutomationEntry ~= "}";
327     }
328 
329     return `{"chord":%s,"melody":%s,"sequence":%s,"name":%s,"bar":%d,"relativeBar":%d,"volume":%s,"velocity":%s,"dry":%s,"wet":%s,"metaData":%s,"metaAutomation":%s}`
330       .format(
331         _chord ? _chord.toJson() : "null",
332         _melody ? _melody.toJson() : "null",
333         _sequence ? _sequence.toJson() : "null",
334         _name ? ("\"" ~ _name ~ "\"") : "null", _bar, relativeBar,
335         _volume.toJson(), _velocity.toJson(),
336         _dry.toJson(), _wet.toJson(),
337         metaDataEntry, metaAutomationEntry
338       );
339   }
340 
341   /// Converts the song track to xml.
342   string toXml()
343   {
344     string metaDataEntry = "";
345 
346     if (_metaData && _metaData.length)
347     {
348       metaDataEntry = "<SongMetaData>";
349 
350       foreach (k,v; _metaData)
351       {
352         metaDataEntry ~= `<SongMetaDataEntry key="%s" value="%s" />`.format(k,v);
353       }
354 
355       metaDataEntry ~= "</SongMetaData>";
356     }
357 
358     string metaAutomationEntry = "";
359 
360     if (_metaAutomation && _metaAutomation.length)
361     {
362       metaAutomationEntry = "<SongMetaAutomation>";
363 
364       foreach (k,v; _metaAutomation)
365       {
366         metaAutomationEntry ~= v.toXml();
367       }
368 
369       metaAutomationEntry ~= "</SongMetaAutomation>";
370     }
371 
372     return `<SongTrack name="%s" bar="%d" relativeBar="%d">%s%s%s %s%s%s%s %s %s</SongTrack>`
373       .format(
374         _name ? _name : "", _bar, relativeBar,
375         _chord ? _chord.toXml() : "",
376         _melody ? _melody.toXml() : "",
377         _sequence ? _sequence.toXml() : "",
378         _volume.toXml(), _velocity.toXml(),
379         _dry.toXml(), _wet.toXml(),
380         metaDataEntry, metaAutomationEntry
381       );
382   }
383 }