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