1 module game.audio;
2 
3 import game.math;
4 import spasm.rt.memory;
5 import std.algorithm : move;
6 
7 nothrow:
8 @safe:
9 
10 enum WAVE_SPS = 44100;					// Samples per second
11 enum MAX_TIME = 33; // maximum time, in millis, that the generator can use consecutively
12 
13 // Oscillators
14 auto osc_sin(float value) {
15 	return sin(value * 6.283184);
16 }
17 
18 float osc_square(float value) {
19 	return osc_sin(value) < 0 ? -1.0 : 1.0;
20 }
21 
22 float osc_saw(float value) {
23 	return (value % 1) - 0.5;
24 }
25 
26 auto osc_tri(float value) {
27 	auto v2 = (value % 1) * 4;
28 	return v2 < 2 ? v2 - 1 : 3 - v2;
29 }
30 
31 auto getnotefreq(int n)
32 {
33 	return 0.00390625 * pow(1.059463094, n - 128);
34 }
35 
36 __gshared SoundPlayer* gSoundPlayer;
37 
38 alias WaveForm = float function(float);
39 
40 struct Instrument {
41   nothrow:
42   ubyte osc1_oct;
43   ubyte osc1_det;
44   ubyte osc1_detune;
45   ubyte osc1_xenv;
46   ubyte osc1_vol;
47   WaveForm osc1_waveform;
48   ubyte osc2_oct;
49   ubyte osc2_det;
50   ubyte osc2_detune;
51   ubyte osc2_xenv;
52   ubyte osc2_vol;
53   WaveForm osc2_waveform;
54   ubyte noise_fader;
55   uint env_attack;
56   uint env_sustain;
57   uint env_release;
58   uint env_master;
59   ubyte fx_filter;
60   uint fx_freq;
61   ubyte fx_resonance;
62   ubyte fx_delay_time;
63   ubyte fx_delay_amt;
64   ubyte fx_pan_freq;
65   ubyte fx_pan_amt;
66   ubyte lfo_osc1_freq;
67   ubyte lfo_fx_freq;
68   ubyte lfo_freq;
69   ubyte lfo_amt;
70   WaveForm lfo_waveform;
71 }
72 
73 struct Buffer {
74   nothrow:
75   float[] left;
76   float[] right;
77 }
78 
79 auto generateBuffer(size_t samples) @trusted {
80   return Buffer(
81                 allocator.make!(float[])(samples),
82                 allocator.make!(float[])(samples));
83 }
84 
85 auto applyDelay(Buffer chnBuf, uint waveSamples, immutable Instrument* instr, uint rowLen) {
86 	auto p1 = (instr.fx_delay_time * rowLen) >> 1;
87 	auto t1 = instr.fx_delay_amt / 255;
88 
89 	auto n1 = 0;
90 	while(n1 < waveSamples - p1) {
91 		auto l = (n1 + p1);
92 		chnBuf.left[l] += chnBuf.right[n1] * t1;
93 		chnBuf.right[l] += chnBuf.left[n1] * t1;
94 		n1++;
95 	}
96 }
97 
98 import spasm.types;
99 
100 struct BaseAudioContext {
101   nothrow:
102   JsHandle handle;
103   alias handle this;
104   this(Handle h) {
105     handle = JsHandle(h);
106   }
107   @property AudioDestinationNode destination() @trusted {
108     return AudioDestinationNode(baseAudioContextDestination(*handle.ptr));
109   }
110 }
111 
112 struct AudioContext {
113   nothrow:
114   BaseAudioContext base;
115   this(Handle handle) {
116     base = BaseAudioContext(handle);
117   }
118   alias base this;
119 }
120 
121 struct Float32Array {
122   nothrow:
123   JsHandle handle;
124   alias handle this;
125   this(Handle h) { handle = JsHandle(h); }
126 }
127 
128 struct AudioBuffer {
129   nothrow:
130   JsHandle handle;
131   alias handle this;
132   this(Handle h) { handle = JsHandle(h); }
133 }
134 
135 struct AudioBufferSourceNode {
136   nothrow:
137   JsHandle handle;
138   alias handle this;
139   this(Handle h) { handle = JsHandle(h); }
140   @property void loop(bool l) @trusted {
141     audioBufferSourceNodeLoopSet(*handle.ptr, l);
142   }
143   @property void buffer(scope ref AudioBuffer buffer) @trusted {
144     audioBufferSourceNodeBuffer(*handle.ptr, *buffer.ptr);
145   }
146 }
147 
148 struct AudioDestinationNode {
149   nothrow:
150   JsHandle handle;
151   alias handle this;
152   this(Handle h) { handle = JsHandle(h); }
153 }
154 
155 extern(C) Handle baseAudioContextDestination(Handle ctx);
156 extern(C) void audioBufferSourceNodeLoopSet(Handle ctx, bool loop);
157 extern(C) Handle windowNewAudioContext();
158 extern(C) void audioBufferSourceNodeConnect(Handle node, Handle destination);
159 extern(C) void audioBufferSourceNodeStart(Handle node);
160 extern(C) void audioBufferSourceNodeBuffer(Handle node, Handle buffer);
161 
162 void connect(ref scope AudioBufferSourceNode node, scope AudioDestinationNode destination) @trusted {
163   audioBufferSourceNodeConnect(*node.handle.ptr, *destination.handle.ptr);
164 }
165 
166 void start(ref AudioBufferSourceNode node) @trusted {
167   audioBufferSourceNodeStart(*node.handle.ptr);
168 }
169 
170 AudioContext newAudioContext() {
171   return AudioContext(windowNewAudioContext());
172 }
173 // TODO: can we combine both functions into one with pragma mangle and implicit conversions?
174 extern(C) Handle baseAudioContextCreateBuffer(Handle ctx, uint numberOfChannels, uint length, float sampleRate);
175 
176 AudioBuffer createBuffer(ref BaseAudioContext ctx, uint numberOfChannels, uint length, float sampleRate) @trusted {
177   return AudioBuffer(baseAudioContextCreateBuffer(*ctx.handle.ptr, numberOfChannels, length, sampleRate));
178 }
179 
180 extern(C) Handle baseAudioContextCreateBufferSource(Handle ctx);
181 
182 AudioBufferSourceNode createBufferSource(ref BaseAudioContext ctx) @trusted {
183   return AudioBufferSourceNode(baseAudioContextCreateBufferSource(*ctx.handle.ptr));
184 }
185 
186 extern(C) Handle audioBufferGetChannelData(Handle ctx, uint channel);
187 
188 Float32Array getChannelData(ref AudioBuffer buffer, uint channel) @trusted {
189   return Float32Array(audioBufferGetChannelData(*buffer.handle.ptr, channel));
190 }
191 
192 extern(C) void float32ArraySet(Handle ctx, float[] array);
193 
194 void set(Float32Array arr, float[] array) @trusted {
195   float32ArraySet(*arr.handle.ptr, array);
196 }
197 
198 auto getAudioBuffer(ref AudioContext ctx, ref Buffer mixBuf) @trusted {
199 	auto buffer = ctx.createBuffer(*ctx.handle.ptr, mixBuf.left.length, WAVE_SPS); // Create Mono Source Buffer from Raw Binary
200 	buffer.getChannelData(0).set(mixBuf.left);
201 	buffer.getChannelData(1).set(mixBuf.right);
202 	return buffer;
203 }
204 
205 auto createAudioBuffer(scope ref AudioContext ctx, immutable Instrument* instr, uint rowLen, uint n) {
206   rowLen = rowLen || 5605;
207   float panFreq = (cast(float)pow(2, instr.fx_pan_freq - 8)) / rowLen;
208   float lfoFreq = (cast(float)pow(2, instr.lfo_freq - 8)) / rowLen;
209   auto genSound(int n, ref Buffer chnBuf, uint currentpos) {
210     float c1 = 0;
211     float c2 = 0;
212 
213     // Precalculate frequencues
214     auto o1t = getnotefreq(n + (cast(int)instr.osc1_oct - 8) * 12 + instr.osc1_det) * (1.0 + 0.0008 * instr.osc1_detune);
215     auto o2t = getnotefreq(n + (cast(int)instr.osc2_oct - 8) * 12 + instr.osc2_det) * (1.0 + 0.0008 * instr.osc2_detune);
216 
217     // State variable init
218     float q = cast(float)instr.fx_resonance / 255;
219     float low = 0;
220     float band = 0;
221     auto chnbufLength = chnBuf.left.length;
222     auto numSamples = instr.env_attack + instr.env_sustain + instr.env_release - 1;
223     for (uint j = numSamples; j >= 0; --j) {
224       uint k = j + currentpos;
225 
226       // LFO
227       auto lfor = instr.lfo_waveform(cast(float)k * lfoFreq) * (cast(float)instr.lfo_amt) / 512 + 0.5;
228 
229       // Envelope
230       float e = 1;
231       if (j < instr.env_attack) {
232         e = (cast(float)j) / instr.env_attack;
233       } else if (j >= instr.env_attack + instr.env_sustain) {
234         e -= (cast(float)(j - instr.env_attack - instr.env_sustain)) / instr.env_release;
235       }
236 
237       // Oscillator 1
238       float t = o1t;
239       if (instr.lfo_osc1_freq) {
240         t += lfor;
241       }
242       if (instr.osc1_xenv) {
243         t *= e * e;
244       }
245       c1 += t;
246       float rsample = instr.osc1_waveform(c1) * instr.osc1_vol;
247       // Oscillator 2
248       t = o2t;
249       if (instr.osc2_xenv) {
250         t *= e * e;
251       }
252       c2 += t;
253       rsample += instr.osc2_waveform(c2) * instr.osc2_vol;
254       // Noise oscillator
255       if (instr.noise_fader) {
256         rsample += (2.0*random()-1.0) * instr.noise_fader * e;
257       }
258       rsample *= e / 255;
259 
260       // State variable filter
261       float f = cast(float)instr.fx_freq;
262       if (instr.lfo_fx_freq) {
263         f *= lfor;
264       }
265       f = 1.5 * sin(f * 3.141592 / WAVE_SPS);
266       low += f * band;
267       float high = q * (rsample - band) - low;
268       band += f * high;
269       switch (instr.fx_filter) {
270       case 1: // Hipass
271         rsample = high;
272         break;
273       case 2: // Lopass
274         rsample = low;
275         break;
276       case 3: // Bandpass
277         rsample = band;
278         break;
279       case 4: // Notch
280         rsample = low + high;
281         break;
282       default:
283       }
284       // Panning & master volume
285       t = osc_sin((cast(float)k) * panFreq) * (cast(float)instr.fx_pan_amt) / 512 + 0.5;
286       rsample *= 0.00476 * instr.env_master; // 39 / 8192 = 0.00476
287 
288       // Add to 16-bit channel buffer
289       // k = k * 2;
290       if (k < chnbufLength) {
291         chnBuf.left[k] = rsample * (1.0-t) ;
292         chnBuf.right[k] = rsample * t;
293       }
294       if (j == 0)
295         break;
296     }
297   }
298 
299   size_t bufferSize = (instr.env_attack + instr.env_sustain + instr.env_release - 1) + (32 * rowLen);
300   auto buffer = generateBuffer(bufferSize);
301   genSound(n, buffer, 0);
302   applyDelay(buffer, bufferSize, instr, rowLen);
303 
304   return getAudioBuffer(ctx, buffer);
305 }
306 
307 struct SoundPlayer {
308   nothrow:
309   AudioContext ctx;
310   AudioBuffer terminal;
311   AudioBuffer shoot;
312   AudioBuffer hit;
313   AudioBuffer explode;
314   private void play(ref AudioBuffer buffer, bool loop) {
315     auto source = ctx.createBufferSource();
316     source.buffer = buffer;//.move();
317     source.loop = loop;
318     source.connect(ctx.destination);
319     source.start();
320   }
321   void playTerminal() {
322     play(terminal, false);
323   }
324   void playShoot() {
325     play(shoot, false);
326   }
327   void playHit() {
328     play(hit, false);
329   }
330   void playExplode() {
331     play(explode, false);
332   }
333 }
334 
335 static immutable Instrument terminalFx = {
336 		osc1_oct: 6,
337 		osc1_det: 0,
338 		osc1_detune: 0,
339 		osc1_xenv: 0,
340 		osc1_vol: 0,
341 		osc1_waveform: &osc_sin,
342 		osc2_oct: 10,
343 		osc2_det: 0,
344 		osc2_detune: 0,
345 		osc2_xenv: 0,
346 		osc2_vol: 168,
347 		osc2_waveform: &osc_tri,
348 		noise_fader: 0,
349 		env_attack: 351,
350 		env_sustain: 0,
351 		env_release: 444,
352 		env_master: 192,
353 		fx_filter: 2,
354 		fx_freq: 7355,
355 		fx_resonance: 130,
356 		fx_delay_time: 3,
357 		fx_delay_amt: 36,
358 		fx_pan_freq: 0,
359 		fx_pan_amt: 0,
360 		lfo_osc1_freq: 0,
361 		lfo_fx_freq: 0,
362 		lfo_freq: 0,
363 		lfo_amt: 0,
364 		lfo_waveform: &osc_sin
365 };
366 
367 static immutable Instrument shootFx = {
368   osc1_oct: 7,
369   osc1_det: 0,
370   osc1_detune: 0,
371   osc1_xenv: 0,
372   osc1_vol: 192,
373   osc1_waveform: &osc_sin,
374   osc2_oct: 2,
375   osc2_det: 0,
376   osc2_detune: 0,
377   osc2_xenv: 0,
378   osc2_vol: 192,
379   osc2_waveform: &osc_sin,
380   noise_fader: 28,
381   env_attack: 269,
382   env_sustain: 0,
383   env_release: 444,
384   env_master: 255,
385   fx_filter: 0,
386   fx_freq: 272,
387   fx_resonance: 25,
388   fx_delay_time: 5,
389   fx_delay_amt: 29,
390   fx_pan_freq: 0,
391   fx_pan_amt: 47,
392   lfo_osc1_freq: 0,
393   lfo_fx_freq: 0,
394   lfo_freq: 0,
395   lfo_amt: 0,
396   lfo_waveform: &osc_sin
397 };
398 
399 static immutable Instrument hitFx = {
400   osc1_oct: 8,
401 		osc1_det: 0,
402 		osc1_detune: 0,
403 		osc1_xenv: 1,
404 		osc1_vol: 160,
405 		osc1_waveform: &osc_tri,
406 		osc2_oct: 8,
407 		osc2_det: 0,
408 		osc2_detune: 0,
409 		osc2_xenv: 1,
410 		osc2_vol: 99,
411 		osc2_waveform: &osc_saw,
412 		noise_fader: 60,
413 		env_attack: 50,
414 		env_sustain: 200,
415 		env_release: 6800,
416 		env_master: 125,
417 		fx_filter: 4,
418 		fx_freq: 11025,
419 		fx_resonance: 254,
420 		fx_delay_time: 0,
421 		fx_delay_amt: 13,
422 		fx_pan_freq: 5,
423 		fx_pan_amt: 0,
424 		lfo_osc1_freq: 0,
425 		lfo_fx_freq: 1,
426 		lfo_freq: 4,
427 		lfo_amt: 60,
428 		lfo_waveform: &osc_sin
429 };
430 
431 static immutable Instrument explodeFx ={
432   		osc1_oct: 8,
433 		osc1_det: 0,
434 		osc1_detune: 0,
435 		osc1_xenv: 1,
436 		osc1_vol: 147,
437 		osc1_waveform: &osc_square,
438 		osc2_oct: 6,
439 		osc2_det: 0,
440 		osc2_detune: 0,
441 		osc2_xenv: 1,
442 		osc2_vol: 159,
443 		osc2_waveform: &osc_square,
444 		noise_fader: 255,
445 		env_attack: 197,
446 		env_sustain: 1234,
447 		env_release: 21759,
448 		env_master: 232,
449 		fx_filter: 2,
450 		fx_freq: 1052,
451 		fx_resonance: 255,
452 		fx_delay_time: 4,
453 		fx_delay_amt: 73,
454 		fx_pan_freq: 3,
455 		fx_pan_amt: 25,
456 		lfo_osc1_freq: 0,
457 		lfo_fx_freq: 0,
458 		lfo_freq: 0,
459 		lfo_amt: 0,
460 		lfo_waveform: &osc_sin
461 };
462 auto createSfx(scope ref AudioContext ctx, immutable Instrument* instrument, uint note) {
463   return createAudioBuffer(ctx, instrument, 5606, note);
464 }
465 auto createSoundPlayer() {
466   auto ctx = newAudioContext();
467   auto terminal = ctx.createSfx(&terminalFx, 156);
468   auto shoot = ctx.createSfx(&shootFx, 140);
469   auto hit = ctx.createSfx(&hitFx, 134);
470   auto explode = ctx.createSfx(&explodeFx, 114);
471   return SoundPlayer(ctx.move, terminal.move, shoot.move, hit.move, explode.move);
472 }
473