1 /**
2 Copyright: Elias Batek 2018.
3 License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
4 */
5 module synthesis;
6 
7 import std.math;
8 import dplug.core.nogc;
9 import dplug.core.math;
10 
11 enum TAU = 2 * PI;
12 
13 enum WaveForm
14 {
15     saw,
16     sine,
17     square,
18 }
19 
20 final class Oscillator
21 {
22 @safe pure nothrow @nogc:
23 
24     private
25     {
26         float _frequency;
27         float _phase = 0;
28         float _phaseSummand;
29         float _sampleRate;
30         WaveForm _waveForm;
31     }
32 
33     public
34     {
35         @property
36         {
37             void frequency(float value)
38             {
39                 this._frequency = value;
40                 this.updatePhaseSummand();
41             }
42         }
43 
44         @property
45         {
46             void sampleRate(float value)
47             {
48                 this._sampleRate = value;
49                 this.updatePhaseSummand();
50             }
51         }
52 
53         @property
54         {
55             WaveForm waveForm()
56             {
57                 return this._waveForm;
58             }
59 
60             void waveForm(WaveForm value)
61             {
62                 this._waveForm = value;
63             }
64         }
65     }
66 
67     public this(WaveForm waveForm)
68     {
69         this._waveForm = waveForm;
70         this.updatePhaseSummand();
71     }
72 
73     public
74     {
75         float synthesizeNext()
76         {
77             float sample = void;
78 
79             final switch (this._waveForm) with (WaveForm)
80             {
81             case saw:
82                 sample = 1.0 - (this._phase / PI);
83                 break;
84 
85             case sine:
86                 sample = sin(this._phase);
87                 break;
88 
89             case square:
90                 sample = (this._phase <= PI) ? 1.0 : -1.0;
91                 break;
92             }
93 
94             this._phase += this._phaseSummand;
95             while (this._phase >= TAU)
96             {
97                 this._phase -= TAU;
98             }
99 
100             return sample;
101         }
102     }
103 
104     private
105     {
106         void updatePhaseSummand()
107         {
108             pragma(inline, true);
109             this._phaseSummand = (this._frequency * TAU / this._sampleRate);
110             //this._phase = 0;
111         }
112     }
113 }
114 
115 final class VoiceStatus
116 {
117 @safe nothrow @nogc:
118 
119     private
120     {
121         Oscillator _osc;
122         bool _isPlaying;
123         int _note;
124     }
125 
126     public pure
127     {
128         @property
129         {
130             bool isPlaying()
131             {
132                 return this._isPlaying;
133             }
134         }
135 
136         @property
137         {
138             int note()
139             {
140                 return this._note;
141             }
142         }
143 
144         @property
145         {
146             void waveForm(WaveForm value)
147             {
148                 this._osc.waveForm = value;
149             }
150 
151             WaveForm waveForm()
152             {
153                 return this._osc.waveForm;
154             }
155         }
156     }
157 
158     public this(WaveForm waveForm) @system
159     {
160         this._osc = mallocNew!Oscillator(waveForm);
161         this._note = -1;
162     }
163 
164     public pure
165     {
166         void play(int note) @trusted
167         {
168             this._note = note;
169             this._osc.frequency = convertMIDINoteToFrequency(note);
170             this._isPlaying = true;
171         }
172 
173         void release()
174         {
175             this._isPlaying = false;
176         }
177 
178         void reset(float sampleRate)
179         {
180             this.release();
181             this._osc.sampleRate = sampleRate;
182         }
183 
184         float synthesizeNext()
185         {
186             if (!this._isPlaying)
187             {
188                 return 0;
189             }
190 
191             return this._osc.synthesizeNext();
192         }
193     }
194 }
195 
196 final class Synth(size_t voicesCount)
197 {
198     static assert(voicesCount > 0, "A synth must have at least 1 voice.");
199 
200 @safe nothrow:
201 
202     private
203     {
204         VoiceStatus[voicesCount] _voices;
205     }
206 
207     public pure @nogc
208     {
209         @property bool isPlaying()
210         {
211             foreach(v; this._voices)
212             {
213                 if (v.isPlaying())
214                 {
215                     return true;
216                 }
217             }
218             return false;
219         }
220 
221         @property
222         {
223             WaveForm waveForm()
224             {
225                 return this._voices[0].waveForm;
226             }
227 
228             void waveForm(WaveForm value)
229             {
230                 foreach (v; this._voices)
231                 {
232                     v.waveForm = value;
233                 }
234             }
235         }
236     }
237 
238     public this(WaveForm waveForm) @system
239     {
240         foreach (ref v; this._voices)
241         {
242             v = mallocNew!VoiceStatus(waveForm);
243         }
244     }
245 
246     public pure @nogc
247     {
248         void markNoteOn(int note)
249         {
250             VoiceStatus v = this.getUnusedVoice();
251             if (v is null)
252             {
253                 /+
254                     No voice available
255 
256                     well, one could override one, but:
257                      - always overriding the 1st one is lame
258                      - a smart algorithm would make this example more complicated
259                 +/
260                 return;
261             }
262 
263             v.play(note);
264         }
265 
266         void markNoteOff(int note)
267         {
268             foreach (v; this._voices)
269             {
270                 if (v.isPlaying && (v.note == note))
271                 {
272                     v.release();
273                 }
274             }
275         }
276 
277         void reset(float sampleRate)
278         {
279             foreach (v; this._voices)
280             {
281                 v.reset(sampleRate);
282             }
283         }
284 
285         float synthesizeNext()
286         {
287             float sample = 0;
288 
289             foreach (v; this._voices)
290             {
291                 sample += (v.synthesizeNext() / voicesCount); // synth + lower volume
292             }
293 
294             return sample;
295         }
296     }
297 
298     private
299     {
300         VoiceStatus getUnusedVoice()
301         {
302             foreach (v; this._voices)
303             {
304                 if (!v.isPlaying)
305                 {
306                     return v;
307                 }
308             }
309             return null;
310         }
311     }
312 }