|
|
@@ -0,0 +1,529 @@
|
|
|
+"use strict";
|
|
|
+// Copyright (c) 2007 Tomas Pettersson (Original C version)
|
|
|
+// Copyright (c) 2015 Jay Sistar (JavaScript port)
|
|
|
+//
|
|
|
+// Permission is hereby granted, free of charge, to any person
|
|
|
+// obtaining a copy of this software and associated documentation
|
|
|
+// files (the "Software"), to deal in the Software without
|
|
|
+// restriction, including without limitation the rights to use,
|
|
|
+// copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
+// copies of the Software, and to permit persons to whom the
|
|
|
+// Software is furnished to do so, subject to the following
|
|
|
+// conditions:
|
|
|
+//
|
|
|
+// The above copyright notice and this permission notice shall be
|
|
|
+// included in all copies or substantial portions of the Software.
|
|
|
+//
|
|
|
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
|
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
|
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
|
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
|
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
|
+// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
|
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
|
+// OTHER DEALINGS IN THE SOFTWARE.
|
|
|
+
|
|
|
+
|
|
|
+function frnd(range) {
|
|
|
+ return Math.random() * range;
|
|
|
+}
|
|
|
+
|
|
|
+function SFXR() {
|
|
|
+ this.ResetParams();
|
|
|
+
|
|
|
+ this.master_vol = 0.05;
|
|
|
+ this.sound_vol = 0.5;
|
|
|
+
|
|
|
+ this.playing_sample = false;
|
|
|
+ this.phase = 0;
|
|
|
+ this.fperiod = 0.0;
|
|
|
+ this.fmaxperiod = 0.0;
|
|
|
+ this.fslide = 0.0;
|
|
|
+ this.fdslide = 0.0;
|
|
|
+ this.period = 0;
|
|
|
+ this.square_duty = 0.0;
|
|
|
+ this.square_slide = 0.0;
|
|
|
+ this.env_stage = 0;
|
|
|
+ this.env_time = 0;
|
|
|
+ this.env_length = new Int32Array(3);
|
|
|
+ this.env_vol = 0.0;
|
|
|
+ this.fphase = 0.0;
|
|
|
+ this.fdphase = 0.0;
|
|
|
+ this.iphase = 0;
|
|
|
+ this.phaser_buffer = new Float32Array(1024);
|
|
|
+ this.ipp = 0;
|
|
|
+ this.noise_buffer = new Float32Array(32);
|
|
|
+ this.fltp = 0.0;
|
|
|
+ this.fltdp = 0.0;
|
|
|
+ this.fltw = 0.0;
|
|
|
+ this.fltw_d = 0.0;
|
|
|
+ this.fltdmp = 0.0;
|
|
|
+ this.fltphp = 0.0;
|
|
|
+ this.flthp = 0.0;
|
|
|
+ this.flthp_d = 0.0;
|
|
|
+ this.vib_phase = 0.0;
|
|
|
+ this.vib_speed = 0.0;
|
|
|
+ this.vib_amp = 0.0;
|
|
|
+ this.rep_time = 0;
|
|
|
+ this.rep_limit = 0;
|
|
|
+ this.arp_time = 0;
|
|
|
+ this.arp_limit = 0;
|
|
|
+ this.arp_mod = 0.0;
|
|
|
+
|
|
|
+ this.vcurbutton = -1;
|
|
|
+
|
|
|
+ this.wav_bits = 16;
|
|
|
+ this.wav_freq = 44100;
|
|
|
+}
|
|
|
+
|
|
|
+SFXR.prototype.ResetParams = function() {
|
|
|
+ this.wave_type = 0;
|
|
|
+
|
|
|
+ this.p_base_freq = 0.3;
|
|
|
+ this.p_freq_limit = 0.0;
|
|
|
+ this.p_freq_ramp = 0.0;
|
|
|
+ this.p_freq_dramp = 0.0;
|
|
|
+ this.p_duty = 0.0;
|
|
|
+ this.p_duty_ramp = 0.0;
|
|
|
+
|
|
|
+ this.p_vib_strength = 0.0;
|
|
|
+ this.p_vib_speed = 0.0;
|
|
|
+ this.p_vib_delay = 0.0;
|
|
|
+
|
|
|
+ this.p_env_attack = 0.0;
|
|
|
+ this.p_env_sustain = 0.3;
|
|
|
+ this.p_env_decay = 0.4;
|
|
|
+ this.p_env_punch = 0.0;
|
|
|
+
|
|
|
+ this.filter_on = false;
|
|
|
+ this.p_lpf_resonance = 0.0;
|
|
|
+ this.p_lpf_freq = 1.0;
|
|
|
+ this.p_lpf_ramp = 0.0;
|
|
|
+ this.p_hpf_freq = 0.0;
|
|
|
+ this.p_hpf_ramp = 0.0;
|
|
|
+
|
|
|
+ this.p_pha_offset = 0.0;
|
|
|
+ this.p_pha_ramp = 0.0;
|
|
|
+
|
|
|
+ this.p_repeat_speed = 0.0;
|
|
|
+
|
|
|
+ this.p_arp_speed = 0.0;
|
|
|
+ this.p_arp_mod = 0.0;
|
|
|
+}
|
|
|
+
|
|
|
+SFXR.prototype.LoadSettings = function(settings) {
|
|
|
+ var i = 0;
|
|
|
+ var data = new DataView(settings.buffer);
|
|
|
+
|
|
|
+ var version = data.getInt32(i, true); i += 4;
|
|
|
+ if(version != 100 && version != 101 && version != 102)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ this.wave_type = data.getInt32(i, true); i += 4;
|
|
|
+
|
|
|
+ this.sound_vol = 0.5;
|
|
|
+ if(version == 102) {
|
|
|
+ this.sound_vol = data.getFloat32(i, true); i += 4;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.p_base_freq = data.getFloat32(i, true); i += 4;
|
|
|
+ this.p_freq_limit = data.getFloat32(i, true); i += 4;
|
|
|
+ this.p_freq_ramp = data.getFloat32(i, true); i += 4;
|
|
|
+ if(version >= 101) {
|
|
|
+ this.p_freq_dramp = data.getFloat32(i, true); i += 4;
|
|
|
+ }
|
|
|
+ this.p_duty = data.getFloat32(i, true); i += 4;
|
|
|
+ this.p_duty_ramp = data.getFloat32(i, true); i += 4;
|
|
|
+
|
|
|
+ this.p_vib_strength = data.getFloat32(i, true); i += 4;
|
|
|
+ this.p_vib_speed = data.getFloat32(i, true); i += 4;
|
|
|
+ this.p_vib_delay = data.getFloat32(i, true); i += 4;
|
|
|
+
|
|
|
+ this.p_env_attack = data.getFloat32(i, true); i += 4;
|
|
|
+ this.p_env_sustain = data.getFloat32(i, true); i += 4;
|
|
|
+ this.p_env_decay = data.getFloat32(i, true); i += 4;
|
|
|
+ this.p_env_punch = data.getFloat32(i, true); i += 4;
|
|
|
+
|
|
|
+ this.filter_on = data.getUint8(i) != 0; i += 1;
|
|
|
+ this.p_lpf_resonance = data.getFloat32(i, true); i += 4;
|
|
|
+ this.p_lpf_freq = data.getFloat32(i, true); i += 4;
|
|
|
+ this.p_lpf_ramp = data.getFloat32(i, true); i += 4;
|
|
|
+ this.p_hpf_freq = data.getFloat32(i, true); i += 4;
|
|
|
+ this.p_hpf_ramp = data.getFloat32(i, true); i += 4;
|
|
|
+
|
|
|
+ this.p_pha_offset = data.getFloat32(i, true); i += 4;
|
|
|
+ this.p_pha_ramp = data.getFloat32(i, true); i += 4;
|
|
|
+
|
|
|
+ this.p_repeat_speed = data.getFloat32(i, true); i += 4;
|
|
|
+
|
|
|
+ if(version >= 101) {
|
|
|
+ this.p_arp_speed = data.getFloat32(i, true); i += 4;
|
|
|
+ this.p_arp_mod = data.getFloat32(i, true); i += 4;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+SFXR.prototype.SaveSettings = function() {
|
|
|
+ var i = 0;
|
|
|
+
|
|
|
+ var settings = new Uint8Array(105);
|
|
|
+ var data = new DataView(settings.buffer);
|
|
|
+
|
|
|
+ data.setInt32(i, 102, true); i += 4;
|
|
|
+
|
|
|
+ data.setInt32(i, this.wave_type, true); i += 4;
|
|
|
+
|
|
|
+ data.setFloat32(i, this.sound_vol, true); i += 4;
|
|
|
+
|
|
|
+ data.setFloat32(i, this.p_base_freq, true); i += 4;
|
|
|
+ data.setFloat32(i, this.p_freq_limit, true); i += 4;
|
|
|
+ data.setFloat32(i, this.p_freq_ramp, true); i += 4;
|
|
|
+ data.setFloat32(i, this.p_freq_dramp, true); i += 4;
|
|
|
+ data.setFloat32(i, this.p_duty, true); i += 4;
|
|
|
+ data.setFloat32(i, this.p_duty_ramp, true); i += 4;
|
|
|
+
|
|
|
+ data.setFloat32(i, this.p_vib_strength, true); i += 4;
|
|
|
+ data.setFloat32(i, this.p_vib_speed, true); i += 4;
|
|
|
+ data.setFloat32(i, this.p_vib_delay, true); i += 4;
|
|
|
+
|
|
|
+ data.setFloat32(i, this.p_env_attack, true); i += 4;
|
|
|
+ data.setFloat32(i, this.p_env_sustain, true); i += 4;
|
|
|
+ data.setFloat32(i, this.p_env_decay, true); i += 4;
|
|
|
+ data.setFloat32(i, this.p_env_punch, true); i += 4;
|
|
|
+
|
|
|
+ data.setUint8(i, this.filter_on ? 1 : 0); i += 1;
|
|
|
+ data.setFloat32(i, this.p_lpf_resonance, true); i += 4;
|
|
|
+ data.setFloat32(i, this.p_lpf_freq, true); i += 4;
|
|
|
+ data.setFloat32(i, this.p_lpf_ramp, true); i += 4;
|
|
|
+ data.setFloat32(i, this.p_hpf_freq, true); i += 4;
|
|
|
+ data.setFloat32(i, this.p_hpf_ramp, true); i += 4;
|
|
|
+
|
|
|
+ data.setFloat32(i, this.p_pha_offset, true); i += 4;
|
|
|
+ data.setFloat32(i, this.p_pha_ramp, true); i += 4;
|
|
|
+
|
|
|
+ data.setFloat32(i, this.p_repeat_speed, true); i += 4;
|
|
|
+
|
|
|
+ data.setFloat32(i, this.p_arp_speed, true); i += 4;
|
|
|
+ data.setFloat32(i, this.p_arp_mod, true); i += 4;
|
|
|
+
|
|
|
+ return settings;
|
|
|
+}
|
|
|
+
|
|
|
+SFXR.prototype.ResetSample = function(restart) {
|
|
|
+ if(!restart)
|
|
|
+ this.phase = 0;
|
|
|
+ this.fperiod = 100.0 / (this.p_base_freq * this.p_base_freq + 0.001);
|
|
|
+ this.period = Math.floor(this.fperiod);
|
|
|
+ this.fmaxperiod = 100.0 / (this.p_freq_limit * this.p_freq_limit+0.001);
|
|
|
+ this.fslide = 1.0 - Math.pow(this.p_freq_ramp, 3.0) * 0.01;
|
|
|
+ this.fdslide = -Math.pow(this.p_freq_dramp, 3.0) * 0.000001;
|
|
|
+ this.square_duty = 0.5 - this.p_duty * 0.5;
|
|
|
+ this.square_slide= -this.p_duty_ramp*0.00005;
|
|
|
+ if(this.p_arp_mod >= 0.0)
|
|
|
+ this.arp_mod = 1.0 - Math.pow(this.p_arp_mod, 2.0) * 0.9;
|
|
|
+ else
|
|
|
+ this.arp_mod = 1.0 + Math.pow(this.p_arp_mod, 2.0) * 10.0;
|
|
|
+ this.arp_time = 0;
|
|
|
+ this.arp_limit = Math.floor(Math.pow(1.0 - this.p_arp_speed, 2.0) * 20000 + 32);
|
|
|
+ if(this.p_arp_speed == 1.0)
|
|
|
+ this.arp_limit=0;
|
|
|
+ if(!restart) {
|
|
|
+ // reset filter
|
|
|
+ this.fltp = 0.0;
|
|
|
+ this.fltdp = 0.0;
|
|
|
+ this.fltw = Math.pow(this.p_lpf_freq, 3.0) * 0.1;
|
|
|
+ this.fltw_d = 1.0 + this.p_lpf_ramp * 0.0001;
|
|
|
+ this.fltdmp = 5.0 / (1.0 + Math.pow(this.p_lpf_resonance, 2.0) * 20.0) * (0.01 + this.fltw);
|
|
|
+ if(this.fltdmp > 0.8) {
|
|
|
+ this.fltdmp = 0.8;
|
|
|
+ }
|
|
|
+ this.fltphp = 0.0;
|
|
|
+ this.flthp = Math.pow(this.p_hpf_freq, 2.0) * 0.1;
|
|
|
+ this.flthp_d = 1.0 + this.p_hpf_ramp * 0.0003;
|
|
|
+ // reset vibrato
|
|
|
+ this.vib_phase = 0.0;
|
|
|
+ this.vib_speed = Math.pow(this.p_vib_speed, 2.0) * 0.01;
|
|
|
+ this.vib_amp = this.p_vib_strength * 0.5;
|
|
|
+ // reset envelope
|
|
|
+ this.env_vol = 0.0;
|
|
|
+ this.env_stage = 0;
|
|
|
+ this.env_time = 0;
|
|
|
+ this.env_length[0] = Math.floor(this.p_env_attack * this.p_env_attack * 100000.0);
|
|
|
+ this.env_length[1] = Math.floor(this.p_env_sustain * this.p_env_sustain * 100000.0);
|
|
|
+ this.env_length[2] = Math.floor(this.p_env_decay * this.p_env_decay * 100000.0);
|
|
|
+
|
|
|
+ this.fphase = Math.pow(this.p_pha_offset, 2.0) * 1020.0;
|
|
|
+ if(this.p_pha_offset < 0.0) this.fphase = -this.fphase;
|
|
|
+ this.fdphase = Math.pow(this.p_pha_ramp, 2.0) * 1.0;
|
|
|
+ if(this.p_pha_ramp < 0.0) this.fdphase = -this.fdphase;
|
|
|
+ this.iphase = Math.floor(Math.abs(this.fphase));
|
|
|
+ this.ipp = 0;
|
|
|
+
|
|
|
+ var i;
|
|
|
+
|
|
|
+ for (i = 0; i < this.phaser_buffer.length; ++i) {
|
|
|
+ this.phaser_buffer[i] = 0.0;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i < this.noise_buffer.length; ++i) {
|
|
|
+ this.noise_buffer[i] = frnd(2.0) - 1.0;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.rep_time = 0;
|
|
|
+ this.rep_limit = Math.floor(Math.pow(1.0 - this.p_repeat_speed, 2.0) * 20000 + 32);
|
|
|
+ if(this.p_repeat_speed == 0.0) {
|
|
|
+ this.rep_limit = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+SFXR.prototype.PlaySample = function() {
|
|
|
+ this.ResetSample(false);
|
|
|
+ this.playing_sample = true;
|
|
|
+}
|
|
|
+
|
|
|
+SFXR.prototype.SynthSamples = function(buffer, numSamples, byteOffset) {
|
|
|
+ if (!buffer) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!byteOffset) {
|
|
|
+ byteOffset = 0;
|
|
|
+ }
|
|
|
+ if (!numSamples) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ var byteLength = numSamples * 2;
|
|
|
+ var data = new Int16Array(buffer, byteOffset, numSamples);
|
|
|
+ var i;
|
|
|
+ for(i = 0; i < numSamples; ++i) {
|
|
|
+ if(!this.playing_sample)
|
|
|
+ break;
|
|
|
+
|
|
|
+ this.rep_time++;
|
|
|
+ if(this.rep_limit != 0 && this.rep_time >= this.rep_limit) {
|
|
|
+ this.rep_time = 0;
|
|
|
+ this.ResetSample(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ // frequency envelopes/arpeggios
|
|
|
+ this.arp_time++;
|
|
|
+ if(this.arp_limit != 0 && this.arp_time >= this.arp_limit) {
|
|
|
+ this.arp_limit = 0;
|
|
|
+ this.fperiod *= this.arp_mod;
|
|
|
+ }
|
|
|
+ this.fslide += this.fdslide;
|
|
|
+ this.fperiod *= this.fslide;
|
|
|
+ if(this.fperiod > this.fmaxperiod) {
|
|
|
+ this.fperiod = this.fmaxperiod;
|
|
|
+ if(this.p_freq_limit > 0.0) {
|
|
|
+ this.playing_sample = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ var rfperiod = this.fperiod;
|
|
|
+ if(this.vib_amp > 0.0) {
|
|
|
+ this.vib_phase += this.vib_speed;
|
|
|
+ rfperiod = this.fperiod * (1.0 + Math.sin(this.vib_phase) * this.vib_amp);
|
|
|
+ }
|
|
|
+ this.period = Math.floor(rfperiod);
|
|
|
+ if(this.period < 8) this.period = 8;
|
|
|
+ this.square_duty += this.square_slide;
|
|
|
+ if(this.square_duty < 0.0) this.square_duty = 0.0;
|
|
|
+ if(this.square_duty > 0.5) this.square_duty = 0.5;
|
|
|
+ // volume envelope
|
|
|
+ this.env_time++;
|
|
|
+ if(this.env_time > this.env_length[this.env_stage]) {
|
|
|
+ this.env_time = 0;
|
|
|
+ this.env_stage++;
|
|
|
+ if(this.env_stage == 3) {
|
|
|
+ this.playing_sample = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if(this.env_stage == 0) {
|
|
|
+ this.env_vol = this.env_time / this.env_length[0];
|
|
|
+ }
|
|
|
+ if(this.env_stage == 1) {
|
|
|
+ this.env_vol = 1.0 + Math.pow(1.0 - this.env_time / this.env_length[1], 1.0) * 2.0 * this.p_env_punch;
|
|
|
+ }
|
|
|
+ if(this.env_stage == 2) {
|
|
|
+ this.env_vol = 1.0 - this.env_time / this.env_length[2];
|
|
|
+ }
|
|
|
+
|
|
|
+ // phaser step
|
|
|
+ this.fphase += this.fdphase;
|
|
|
+ this.iphase = Math.floor(Math.abs(this.fphase));
|
|
|
+ if(this.iphase > 1023) {
|
|
|
+ this.iphase = 1023;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(this.flthp_d != 0.0) {
|
|
|
+ this.flthp *= this.flthp_d;
|
|
|
+ if(this.flthp < 0.00001) {
|
|
|
+ this.flthp = 0.00001;
|
|
|
+ }
|
|
|
+ if(this.flthp > 0.1) {
|
|
|
+ this.flthp = 0.1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var ssample = 0.0;
|
|
|
+ for(var si = 0; si < 8; ++si) { // 8x supersampling
|
|
|
+ var sample = 0.0;
|
|
|
+ this.phase++;
|
|
|
+ if(this.phase >= this.period) {
|
|
|
+ this.phase %= this.period;
|
|
|
+ if(this.wave_type == 3) {
|
|
|
+ for(var ni = 0; ni < 32; ++ni) {
|
|
|
+ this.noise_buffer[ni] = frnd(2.0) - 1.0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // base waveform
|
|
|
+ var fp = this.phase / this.period;
|
|
|
+ switch(this.wave_type) {
|
|
|
+ case 0: // square
|
|
|
+ if(fp < this.square_duty) {
|
|
|
+ sample = 0.5;
|
|
|
+ } else {
|
|
|
+ sample = -0.5;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 1: // sawtooth
|
|
|
+ sample = 1.0 - fp * 2;
|
|
|
+ break;
|
|
|
+ case 2: // sine
|
|
|
+ sample = Math.sin(fp * 2 * Math.PI);
|
|
|
+ break;
|
|
|
+ case 3: // noise
|
|
|
+ sample = this.noise_buffer[this.phase * 32 / this.period];
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ // lp filter
|
|
|
+ var pp = this.fltp;
|
|
|
+ this.fltw *= this.fltw_d;
|
|
|
+ if(this.fltw < 0.0) {
|
|
|
+ this.fltw = 0.0;
|
|
|
+ }
|
|
|
+ if(this.fltw > 0.1) {
|
|
|
+ this.fltw = 0.1;
|
|
|
+ }
|
|
|
+ if(this.p_lpf_freq != 1.0) {
|
|
|
+ this.fltdp += (sample - this.fltp) * this.fltw;
|
|
|
+ this.fltdp -= this.fltdp * this.fltdmp;
|
|
|
+ } else {
|
|
|
+ this.fltp = sample;
|
|
|
+ this.fltdp = 0.0;
|
|
|
+ }
|
|
|
+ this.fltp += this.fltdp;
|
|
|
+ // hp filter
|
|
|
+ this.fltphp += this.fltp - pp;
|
|
|
+ this.fltphp -= this.fltphp * this.flthp;
|
|
|
+ sample = this.fltphp;
|
|
|
+ // phaser
|
|
|
+ this.phaser_buffer[this.ipp & 1023] = sample;
|
|
|
+ sample += this.phaser_buffer[(this.ipp - this.iphase + 1024) & 1023];
|
|
|
+ this.ipp = (this.ipp + 1) & 1023;
|
|
|
+ // final accumulation and envelope application
|
|
|
+ ssample += sample * this.env_vol;
|
|
|
+ }
|
|
|
+ ssample = ssample / 8 * this.master_vol;
|
|
|
+
|
|
|
+ ssample *= 2.0 * this.sound_vol;
|
|
|
+
|
|
|
+
|
|
|
+ // clamp to [-1.0, 1.0]
|
|
|
+ if(ssample > 1.0) ssample = 1.0;
|
|
|
+ if(ssample < -1.0) ssample = -1.0;
|
|
|
+
|
|
|
+ // prevent invalid values
|
|
|
+ if(isNaN(ssample) || !isFinite(ssample)) {
|
|
|
+ ssample = 0.0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // write sample
|
|
|
+ data[i] = Math.floor(ssample * 32767.0);
|
|
|
+ }
|
|
|
+ return i;
|
|
|
+}
|
|
|
+
|
|
|
+SFXR.prototype.NumSamples = function() {
|
|
|
+ // Find the number of samples and byte length.
|
|
|
+ var numSamples = 0;
|
|
|
+ numSamples += Math.floor(this.p_env_attack * this.p_env_attack * 100000.0);
|
|
|
+ numSamples += Math.floor(this.p_env_sustain * this.p_env_sustain * 100000.0);
|
|
|
+ numSamples += Math.floor(this.p_env_decay * this.p_env_decay * 100000.0);
|
|
|
+ return numSamples;
|
|
|
+}
|
|
|
+
|
|
|
+SFXR.prototype.ByteLength = function(numSamples) {
|
|
|
+ if (numSamples === undefined) {
|
|
|
+ numSamples = this.NumSamples();
|
|
|
+ }
|
|
|
+ return numSamples * this.wav_bits / 8;
|
|
|
+}
|
|
|
+
|
|
|
+SFXR.prototype.GenerateWAV = function() {
|
|
|
+ // Find the number of samples and byte length.
|
|
|
+ var numSamples = this.NumSamples();
|
|
|
+ var length = this.ByteLength(numSamples);
|
|
|
+
|
|
|
+ // Write a WAV header to a buffer.
|
|
|
+ var headerBuf = new ArrayBuffer(44);
|
|
|
+ var header = new DataView(headerBuf);
|
|
|
+ var i = 0;
|
|
|
+ header.setUint32(i, 0x46464952, true); i += 4; // "RIFF"
|
|
|
+ header.setUint32(i, 36 + length, true); i += 4; // remaining file size
|
|
|
+ header.setUint32(i, 0x45564157, true); i += 4; // "WAVE"
|
|
|
+ header.setUint32(i, 0x20746d66, true); i += 4; // "fmt "
|
|
|
+ header.setUint32(i, 16, true); i += 4; // chunk size
|
|
|
+ header.setUint16(i, 1, true); i += 2; // format (1:short, 3:float (very rare))
|
|
|
+ header.setUint16(i, 1, true); i += 2; // number of channels
|
|
|
+ header.setUint32(i, this.wav_freq, true); i += 4; // sample rate
|
|
|
+ header.setUint32(i, this.wav_freq * this.wav_bits / 8, true); i += 4; // bytes/sec
|
|
|
+ header.setUint16(i, this.wav_bits / 8, true); i += 2; // block align
|
|
|
+ header.setUint16(i, this.wav_bits, true); i += 2; // bits per sample
|
|
|
+ header.setUint32(i, 0x61746164, true); i += 4; // "data"
|
|
|
+ header.setUint32(i, length); i += 4; // chunk size
|
|
|
+
|
|
|
+ // Synth all the samples, and prepend the WAV header.
|
|
|
+ return this.SynthAllSamples(header);
|
|
|
+}
|
|
|
+
|
|
|
+SFXR.prototype.SynthAllSamples = function(prepend, append) {
|
|
|
+ // write sample data
|
|
|
+ this.PlaySample();
|
|
|
+
|
|
|
+ // create the output buffer
|
|
|
+ var byteOffset = 0;
|
|
|
+ var numSamples = 0;
|
|
|
+ numSamples += this.env_length[0];
|
|
|
+ numSamples += this.env_length[1];
|
|
|
+ numSamples += this.env_length[2];
|
|
|
+ var length = numSamples * 2;
|
|
|
+ if (!!prepend) {
|
|
|
+ length += prepend.byteLength;
|
|
|
+ byteOffset = prepend.byteLength;
|
|
|
+ }
|
|
|
+ if (!!append) {
|
|
|
+ length += append.byteLength;
|
|
|
+ }
|
|
|
+ var data = new Uint8Array(length);
|
|
|
+
|
|
|
+ // write prepended data
|
|
|
+ if (!!prepend) {
|
|
|
+ data.set(prepend);
|
|
|
+ }
|
|
|
+
|
|
|
+ // synth the samples
|
|
|
+ this.SynthSamples(data.buffer, numSamples, byteOffset);
|
|
|
+
|
|
|
+ // write appended data
|
|
|
+ if (!!append) {
|
|
|
+ data.set(append, byteOffset + (numSamples * 2));
|
|
|
+ }
|
|
|
+
|
|
|
+ return new Int16Array(data.buffer);
|
|
|
+}
|
|
|
+
|
|
|
+module.exports = SFXR;
|