SFXR.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. "use strict";
  2. // Copyright (c) 2007 Tomas Pettersson (Original C version)
  3. // Copyright (c) 2015 Jay Sistar (JavaScript port)
  4. //
  5. // Permission is hereby granted, free of charge, to any person
  6. // obtaining a copy of this software and associated documentation
  7. // files (the "Software"), to deal in the Software without
  8. // restriction, including without limitation the rights to use,
  9. // copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. // copies of the Software, and to permit persons to whom the
  11. // Software is furnished to do so, subject to the following
  12. // conditions:
  13. //
  14. // The above copyright notice and this permission notice shall be
  15. // included in all copies or substantial portions of the Software.
  16. //
  17. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  18. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  19. // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  20. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  21. // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  22. // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  23. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  24. // OTHER DEALINGS IN THE SOFTWARE.
  25. function frnd(range) {
  26. return Math.random() * range;
  27. }
  28. function SFXR() {
  29. this.ResetParams();
  30. this.master_vol = 0.05;
  31. this.sound_vol = 0.5;
  32. this.playing_sample = false;
  33. this.phase = 0;
  34. this.fperiod = 0.0;
  35. this.fmaxperiod = 0.0;
  36. this.fslide = 0.0;
  37. this.fdslide = 0.0;
  38. this.period = 0;
  39. this.square_duty = 0.0;
  40. this.square_slide = 0.0;
  41. this.env_stage = 0;
  42. this.env_time = 0;
  43. this.env_length = new Int32Array(3);
  44. this.env_vol = 0.0;
  45. this.fphase = 0.0;
  46. this.fdphase = 0.0;
  47. this.iphase = 0;
  48. this.phaser_buffer = new Float32Array(1024);
  49. this.ipp = 0;
  50. this.noise_buffer = new Float32Array(32);
  51. this.fltp = 0.0;
  52. this.fltdp = 0.0;
  53. this.fltw = 0.0;
  54. this.fltw_d = 0.0;
  55. this.fltdmp = 0.0;
  56. this.fltphp = 0.0;
  57. this.flthp = 0.0;
  58. this.flthp_d = 0.0;
  59. this.vib_phase = 0.0;
  60. this.vib_speed = 0.0;
  61. this.vib_amp = 0.0;
  62. this.rep_time = 0;
  63. this.rep_limit = 0;
  64. this.arp_time = 0;
  65. this.arp_limit = 0;
  66. this.arp_mod = 0.0;
  67. this.vcurbutton = -1;
  68. this.wav_bits = 16;
  69. this.wav_freq = 44100;
  70. }
  71. SFXR.prototype.ResetParams = function() {
  72. this.wave_type = 0;
  73. this.p_base_freq = 0.3;
  74. this.p_freq_limit = 0.0;
  75. this.p_freq_ramp = 0.0;
  76. this.p_freq_dramp = 0.0;
  77. this.p_duty = 0.0;
  78. this.p_duty_ramp = 0.0;
  79. this.p_vib_strength = 0.0;
  80. this.p_vib_speed = 0.0;
  81. this.p_vib_delay = 0.0;
  82. this.p_env_attack = 0.0;
  83. this.p_env_sustain = 0.3;
  84. this.p_env_decay = 0.4;
  85. this.p_env_punch = 0.0;
  86. this.filter_on = false;
  87. this.p_lpf_resonance = 0.0;
  88. this.p_lpf_freq = 1.0;
  89. this.p_lpf_ramp = 0.0;
  90. this.p_hpf_freq = 0.0;
  91. this.p_hpf_ramp = 0.0;
  92. this.p_pha_offset = 0.0;
  93. this.p_pha_ramp = 0.0;
  94. this.p_repeat_speed = 0.0;
  95. this.p_arp_speed = 0.0;
  96. this.p_arp_mod = 0.0;
  97. }
  98. SFXR.prototype.LoadSettings = function(settings) {
  99. var i = 0;
  100. var data = new DataView(settings.buffer);
  101. var version = data.getInt32(i, true); i += 4;
  102. if(version != 100 && version != 101 && version != 102)
  103. return false;
  104. this.wave_type = data.getInt32(i, true); i += 4;
  105. this.sound_vol = 0.5;
  106. if(version == 102) {
  107. this.sound_vol = data.getFloat32(i, true); i += 4;
  108. }
  109. this.p_base_freq = data.getFloat32(i, true); i += 4;
  110. this.p_freq_limit = data.getFloat32(i, true); i += 4;
  111. this.p_freq_ramp = data.getFloat32(i, true); i += 4;
  112. if(version >= 101) {
  113. this.p_freq_dramp = data.getFloat32(i, true); i += 4;
  114. }
  115. this.p_duty = data.getFloat32(i, true); i += 4;
  116. this.p_duty_ramp = data.getFloat32(i, true); i += 4;
  117. this.p_vib_strength = data.getFloat32(i, true); i += 4;
  118. this.p_vib_speed = data.getFloat32(i, true); i += 4;
  119. this.p_vib_delay = data.getFloat32(i, true); i += 4;
  120. this.p_env_attack = data.getFloat32(i, true); i += 4;
  121. this.p_env_sustain = data.getFloat32(i, true); i += 4;
  122. this.p_env_decay = data.getFloat32(i, true); i += 4;
  123. this.p_env_punch = data.getFloat32(i, true); i += 4;
  124. this.filter_on = data.getUint8(i) != 0; i += 1;
  125. this.p_lpf_resonance = data.getFloat32(i, true); i += 4;
  126. this.p_lpf_freq = data.getFloat32(i, true); i += 4;
  127. this.p_lpf_ramp = data.getFloat32(i, true); i += 4;
  128. this.p_hpf_freq = data.getFloat32(i, true); i += 4;
  129. this.p_hpf_ramp = data.getFloat32(i, true); i += 4;
  130. this.p_pha_offset = data.getFloat32(i, true); i += 4;
  131. this.p_pha_ramp = data.getFloat32(i, true); i += 4;
  132. this.p_repeat_speed = data.getFloat32(i, true); i += 4;
  133. if(version >= 101) {
  134. this.p_arp_speed = data.getFloat32(i, true); i += 4;
  135. this.p_arp_mod = data.getFloat32(i, true); i += 4;
  136. }
  137. return true;
  138. }
  139. SFXR.prototype.SaveSettings = function() {
  140. var i = 0;
  141. var settings = new Uint8Array(105);
  142. var data = new DataView(settings.buffer);
  143. data.setInt32(i, 102, true); i += 4;
  144. data.setInt32(i, this.wave_type, true); i += 4;
  145. data.setFloat32(i, this.sound_vol, true); i += 4;
  146. data.setFloat32(i, this.p_base_freq, true); i += 4;
  147. data.setFloat32(i, this.p_freq_limit, true); i += 4;
  148. data.setFloat32(i, this.p_freq_ramp, true); i += 4;
  149. data.setFloat32(i, this.p_freq_dramp, true); i += 4;
  150. data.setFloat32(i, this.p_duty, true); i += 4;
  151. data.setFloat32(i, this.p_duty_ramp, true); i += 4;
  152. data.setFloat32(i, this.p_vib_strength, true); i += 4;
  153. data.setFloat32(i, this.p_vib_speed, true); i += 4;
  154. data.setFloat32(i, this.p_vib_delay, true); i += 4;
  155. data.setFloat32(i, this.p_env_attack, true); i += 4;
  156. data.setFloat32(i, this.p_env_sustain, true); i += 4;
  157. data.setFloat32(i, this.p_env_decay, true); i += 4;
  158. data.setFloat32(i, this.p_env_punch, true); i += 4;
  159. data.setUint8(i, this.filter_on ? 1 : 0); i += 1;
  160. data.setFloat32(i, this.p_lpf_resonance, true); i += 4;
  161. data.setFloat32(i, this.p_lpf_freq, true); i += 4;
  162. data.setFloat32(i, this.p_lpf_ramp, true); i += 4;
  163. data.setFloat32(i, this.p_hpf_freq, true); i += 4;
  164. data.setFloat32(i, this.p_hpf_ramp, true); i += 4;
  165. data.setFloat32(i, this.p_pha_offset, true); i += 4;
  166. data.setFloat32(i, this.p_pha_ramp, true); i += 4;
  167. data.setFloat32(i, this.p_repeat_speed, true); i += 4;
  168. data.setFloat32(i, this.p_arp_speed, true); i += 4;
  169. data.setFloat32(i, this.p_arp_mod, true); i += 4;
  170. return settings;
  171. }
  172. SFXR.prototype.ResetSample = function(restart) {
  173. if(!restart)
  174. this.phase = 0;
  175. this.fperiod = 100.0 / (this.p_base_freq * this.p_base_freq + 0.001);
  176. this.period = Math.floor(this.fperiod);
  177. this.fmaxperiod = 100.0 / (this.p_freq_limit * this.p_freq_limit+0.001);
  178. this.fslide = 1.0 - Math.pow(this.p_freq_ramp, 3.0) * 0.01;
  179. this.fdslide = -Math.pow(this.p_freq_dramp, 3.0) * 0.000001;
  180. this.square_duty = 0.5 - this.p_duty * 0.5;
  181. this.square_slide= -this.p_duty_ramp*0.00005;
  182. if(this.p_arp_mod >= 0.0)
  183. this.arp_mod = 1.0 - Math.pow(this.p_arp_mod, 2.0) * 0.9;
  184. else
  185. this.arp_mod = 1.0 + Math.pow(this.p_arp_mod, 2.0) * 10.0;
  186. this.arp_time = 0;
  187. this.arp_limit = Math.floor(Math.pow(1.0 - this.p_arp_speed, 2.0) * 20000 + 32);
  188. if(this.p_arp_speed == 1.0)
  189. this.arp_limit=0;
  190. if(!restart) {
  191. // reset filter
  192. this.fltp = 0.0;
  193. this.fltdp = 0.0;
  194. this.fltw = Math.pow(this.p_lpf_freq, 3.0) * 0.1;
  195. this.fltw_d = 1.0 + this.p_lpf_ramp * 0.0001;
  196. this.fltdmp = 5.0 / (1.0 + Math.pow(this.p_lpf_resonance, 2.0) * 20.0) * (0.01 + this.fltw);
  197. if(this.fltdmp > 0.8) {
  198. this.fltdmp = 0.8;
  199. }
  200. this.fltphp = 0.0;
  201. this.flthp = Math.pow(this.p_hpf_freq, 2.0) * 0.1;
  202. this.flthp_d = 1.0 + this.p_hpf_ramp * 0.0003;
  203. // reset vibrato
  204. this.vib_phase = 0.0;
  205. this.vib_speed = Math.pow(this.p_vib_speed, 2.0) * 0.01;
  206. this.vib_amp = this.p_vib_strength * 0.5;
  207. // reset envelope
  208. this.env_vol = 0.0;
  209. this.env_stage = 0;
  210. this.env_time = 0;
  211. this.env_length[0] = Math.floor(this.p_env_attack * this.p_env_attack * 100000.0);
  212. this.env_length[1] = Math.floor(this.p_env_sustain * this.p_env_sustain * 100000.0);
  213. this.env_length[2] = Math.floor(this.p_env_decay * this.p_env_decay * 100000.0);
  214. this.fphase = Math.pow(this.p_pha_offset, 2.0) * 1020.0;
  215. if(this.p_pha_offset < 0.0) this.fphase = -this.fphase;
  216. this.fdphase = Math.pow(this.p_pha_ramp, 2.0) * 1.0;
  217. if(this.p_pha_ramp < 0.0) this.fdphase = -this.fdphase;
  218. this.iphase = Math.floor(Math.abs(this.fphase));
  219. this.ipp = 0;
  220. var i;
  221. for (i = 0; i < this.phaser_buffer.length; ++i) {
  222. this.phaser_buffer[i] = 0.0;
  223. }
  224. for (i = 0; i < this.noise_buffer.length; ++i) {
  225. this.noise_buffer[i] = frnd(2.0) - 1.0;
  226. }
  227. this.rep_time = 0;
  228. this.rep_limit = Math.floor(Math.pow(1.0 - this.p_repeat_speed, 2.0) * 20000 + 32);
  229. if(this.p_repeat_speed == 0.0) {
  230. this.rep_limit = 0;
  231. }
  232. }
  233. }
  234. SFXR.prototype.PlaySample = function() {
  235. this.ResetSample(false);
  236. this.playing_sample = true;
  237. }
  238. SFXR.prototype.SynthSamples = function(buffer, numSamples, byteOffset) {
  239. if (!buffer) {
  240. return;
  241. }
  242. if (!byteOffset) {
  243. byteOffset = 0;
  244. }
  245. if (!numSamples) {
  246. return;
  247. }
  248. var byteLength = numSamples * 2;
  249. var data = new Int16Array(buffer, byteOffset, numSamples);
  250. var i;
  251. for(i = 0; i < numSamples; ++i) {
  252. if(!this.playing_sample)
  253. break;
  254. this.rep_time++;
  255. if(this.rep_limit != 0 && this.rep_time >= this.rep_limit) {
  256. this.rep_time = 0;
  257. this.ResetSample(true);
  258. }
  259. // frequency envelopes/arpeggios
  260. this.arp_time++;
  261. if(this.arp_limit != 0 && this.arp_time >= this.arp_limit) {
  262. this.arp_limit = 0;
  263. this.fperiod *= this.arp_mod;
  264. }
  265. this.fslide += this.fdslide;
  266. this.fperiod *= this.fslide;
  267. if(this.fperiod > this.fmaxperiod) {
  268. this.fperiod = this.fmaxperiod;
  269. if(this.p_freq_limit > 0.0) {
  270. this.playing_sample = false;
  271. }
  272. }
  273. var rfperiod = this.fperiod;
  274. if(this.vib_amp > 0.0) {
  275. this.vib_phase += this.vib_speed;
  276. rfperiod = this.fperiod * (1.0 + Math.sin(this.vib_phase) * this.vib_amp);
  277. }
  278. this.period = Math.floor(rfperiod);
  279. if(this.period < 8) this.period = 8;
  280. this.square_duty += this.square_slide;
  281. if(this.square_duty < 0.0) this.square_duty = 0.0;
  282. if(this.square_duty > 0.5) this.square_duty = 0.5;
  283. // volume envelope
  284. this.env_time++;
  285. if(this.env_time > this.env_length[this.env_stage]) {
  286. this.env_time = 0;
  287. this.env_stage++;
  288. if(this.env_stage == 3) {
  289. this.playing_sample = false;
  290. }
  291. }
  292. if(this.env_stage == 0) {
  293. this.env_vol = this.env_time / this.env_length[0];
  294. }
  295. if(this.env_stage == 1) {
  296. this.env_vol = 1.0 + Math.pow(1.0 - this.env_time / this.env_length[1], 1.0) * 2.0 * this.p_env_punch;
  297. }
  298. if(this.env_stage == 2) {
  299. this.env_vol = 1.0 - this.env_time / this.env_length[2];
  300. }
  301. // phaser step
  302. this.fphase += this.fdphase;
  303. this.iphase = Math.floor(Math.abs(this.fphase));
  304. if(this.iphase > 1023) {
  305. this.iphase = 1023;
  306. }
  307. if(this.flthp_d != 0.0) {
  308. this.flthp *= this.flthp_d;
  309. if(this.flthp < 0.00001) {
  310. this.flthp = 0.00001;
  311. }
  312. if(this.flthp > 0.1) {
  313. this.flthp = 0.1;
  314. }
  315. }
  316. var ssample = 0.0;
  317. for(var si = 0; si < 8; ++si) { // 8x supersampling
  318. var sample = 0.0;
  319. this.phase++;
  320. if(this.phase >= this.period) {
  321. this.phase %= this.period;
  322. if(this.wave_type == 3) {
  323. for(var ni = 0; ni < 32; ++ni) {
  324. this.noise_buffer[ni] = frnd(2.0) - 1.0;
  325. }
  326. }
  327. }
  328. // base waveform
  329. var fp = this.phase / this.period;
  330. switch(this.wave_type) {
  331. case 0: // square
  332. if(fp < this.square_duty) {
  333. sample = 0.5;
  334. } else {
  335. sample = -0.5;
  336. }
  337. break;
  338. case 1: // sawtooth
  339. sample = 1.0 - fp * 2;
  340. break;
  341. case 2: // sine
  342. sample = Math.sin(fp * 2 * Math.PI);
  343. break;
  344. case 3: // noise
  345. sample = this.noise_buffer[this.phase * 32 / this.period];
  346. break;
  347. }
  348. // lp filter
  349. var pp = this.fltp;
  350. this.fltw *= this.fltw_d;
  351. if(this.fltw < 0.0) {
  352. this.fltw = 0.0;
  353. }
  354. if(this.fltw > 0.1) {
  355. this.fltw = 0.1;
  356. }
  357. if(this.p_lpf_freq != 1.0) {
  358. this.fltdp += (sample - this.fltp) * this.fltw;
  359. this.fltdp -= this.fltdp * this.fltdmp;
  360. } else {
  361. this.fltp = sample;
  362. this.fltdp = 0.0;
  363. }
  364. this.fltp += this.fltdp;
  365. // hp filter
  366. this.fltphp += this.fltp - pp;
  367. this.fltphp -= this.fltphp * this.flthp;
  368. sample = this.fltphp;
  369. // phaser
  370. this.phaser_buffer[this.ipp & 1023] = sample;
  371. sample += this.phaser_buffer[(this.ipp - this.iphase + 1024) & 1023];
  372. this.ipp = (this.ipp + 1) & 1023;
  373. // final accumulation and envelope application
  374. ssample += sample * this.env_vol;
  375. }
  376. ssample = ssample / 8 * this.master_vol;
  377. ssample *= 2.0 * this.sound_vol;
  378. // clamp to [-1.0, 1.0]
  379. if(ssample > 1.0) ssample = 1.0;
  380. if(ssample < -1.0) ssample = -1.0;
  381. // prevent invalid values
  382. if(isNaN(ssample) || !isFinite(ssample)) {
  383. ssample = 0.0;
  384. }
  385. // write sample
  386. data[i] = Math.floor(ssample * 32767.0);
  387. }
  388. return i;
  389. }
  390. SFXR.prototype.NumSamples = function() {
  391. // Find the number of samples and byte length.
  392. var numSamples = 0;
  393. numSamples += Math.floor(this.p_env_attack * this.p_env_attack * 100000.0);
  394. numSamples += Math.floor(this.p_env_sustain * this.p_env_sustain * 100000.0);
  395. numSamples += Math.floor(this.p_env_decay * this.p_env_decay * 100000.0);
  396. return numSamples;
  397. }
  398. SFXR.prototype.ByteLength = function(numSamples) {
  399. if (numSamples === undefined) {
  400. numSamples = this.NumSamples();
  401. }
  402. return numSamples * this.wav_bits / 8;
  403. }
  404. SFXR.prototype.GenerateWAV = function() {
  405. // Find the number of samples and byte length.
  406. var numSamples = this.NumSamples();
  407. var length = this.ByteLength(numSamples);
  408. // Write a WAV header to a buffer.
  409. var headerBuf = new ArrayBuffer(44);
  410. var header = new DataView(headerBuf);
  411. var i = 0;
  412. header.setUint32(i, 0x46464952, true); i += 4; // "RIFF"
  413. header.setUint32(i, 36 + length, true); i += 4; // remaining file size
  414. header.setUint32(i, 0x45564157, true); i += 4; // "WAVE"
  415. header.setUint32(i, 0x20746d66, true); i += 4; // "fmt "
  416. header.setUint32(i, 16, true); i += 4; // chunk size
  417. header.setUint16(i, 1, true); i += 2; // format (1:short, 3:float (very rare))
  418. header.setUint16(i, 1, true); i += 2; // number of channels
  419. header.setUint32(i, this.wav_freq, true); i += 4; // sample rate
  420. header.setUint32(i, this.wav_freq * this.wav_bits / 8, true); i += 4; // bytes/sec
  421. header.setUint16(i, this.wav_bits / 8, true); i += 2; // block align
  422. header.setUint16(i, this.wav_bits, true); i += 2; // bits per sample
  423. header.setUint32(i, 0x61746164, true); i += 4; // "data"
  424. header.setUint32(i, length); i += 4; // chunk size
  425. // Synth all the samples, and prepend the WAV header.
  426. return this.SynthAllSamples(header);
  427. }
  428. SFXR.prototype.SynthAllSamples = function(prepend, append) {
  429. // write sample data
  430. this.PlaySample();
  431. // create the output buffer
  432. var byteOffset = 0;
  433. var numSamples = 0;
  434. numSamples += this.env_length[0];
  435. numSamples += this.env_length[1];
  436. numSamples += this.env_length[2];
  437. var length = numSamples * 2;
  438. if (!!prepend) {
  439. length += prepend.byteLength;
  440. byteOffset = prepend.byteLength;
  441. }
  442. if (!!append) {
  443. length += append.byteLength;
  444. }
  445. var data = new Uint8Array(length);
  446. // write prepended data
  447. if (!!prepend) {
  448. data.set(prepend);
  449. }
  450. // synth the samples
  451. this.SynthSamples(data.buffer, numSamples, byteOffset);
  452. // write appended data
  453. if (!!append) {
  454. data.set(append, byteOffset + (numSamples * 2));
  455. }
  456. return new Int16Array(data.buffer);
  457. }
  458. module.exports = SFXR;