Guest User

Untitled

a guest
Apr 22nd, 2018
104
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.96 KB | None | 0 0
  1. import InlineWorker from 'inline-worker';
  2.  
  3. export class Recorder {
  4. config = {
  5. sampleRate: 16000,
  6. bufferLen: 4096,
  7. numChannels: 2,
  8. mimeType: 'audio/pcm'
  9. };
  10.  
  11. _stream;
  12.  
  13. recording = false;
  14.  
  15. callbacks = {
  16. getBuffer: [],
  17. exportWAV: []
  18. };
  19.  
  20. constructor(source, stream, cfg) {
  21.  
  22. this._stream = stream;
  23.  
  24. Object.assign(this.config, cfg);
  25. this.context = source.context;
  26. this.node = (this.context.createScriptProcessor ||
  27. this.context.createJavaScriptNode).call(this.context,
  28. this.config.bufferLen, this.config.numChannels, this.config.numChannels);
  29.  
  30. this.node.onaudioprocess = (e) => {
  31. if (!this.recording) return;
  32.  
  33. var buffer = [];
  34. for (var channel = 0; channel < this.config.numChannels; channel++) {
  35. buffer.push(e.inputBuffer.getChannelData(channel));
  36. }
  37. this.worker.postMessage({
  38. command: 'record',
  39. buffer: buffer
  40. });
  41. };
  42.  
  43. source.connect(this.node);
  44. this.node.connect(this.context.destination); //this should not be necessary
  45.  
  46. let self = {};
  47. this.worker = new InlineWorker(function () {
  48. let recLength = 0,
  49. recBuffers = [],
  50. sampleRate,
  51. contextRate,
  52. numChannels;
  53.  
  54. this.onmessage = function (e) {
  55. switch (e.data.command) {
  56. case 'init':
  57. init(e.data.config);
  58. break;
  59. case 'record':
  60. record(e.data.buffer);
  61. break;
  62. case 'exportWAV':
  63. exportWAV(e.data.type);
  64. break;
  65. case 'getBuffer':
  66. getBuffer();
  67. break;
  68. case 'clear':
  69. clear();
  70. break;
  71. }
  72. };
  73.  
  74. function init(config) {
  75. sampleRate = config.sampleRate;
  76. contextRate = config.contextRate;
  77. numChannels = config.numChannels;
  78. initBuffers();
  79. }
  80.  
  81. function record(inputBuffer) {
  82. for (var channel = 0; channel < numChannels; channel++) {
  83. recBuffers[channel].push(inputBuffer[channel]);
  84. }
  85. recLength += inputBuffer[0].length;
  86. }
  87.  
  88. function getBuffer() {
  89. let buffers = [];
  90. for (let channel = 0; channel < numChannels; channel++) {
  91. buffers.push(mergeBuffers(recBuffers[channel], recLength));
  92. }
  93. this.postMessage({command: 'getBuffer', data: buffers});
  94. }
  95.  
  96. function clear() {
  97. recLength = 0;
  98. recBuffers = [];
  99. initBuffers();
  100. }
  101.  
  102. function initBuffers() {
  103. for (let channel = 0; channel < numChannels; channel++) {
  104. recBuffers[channel] = [];
  105. }
  106. }
  107.  
  108. function mergeBuffers(recBuffers, recLength) {
  109. let result = new Float32Array(recLength);
  110. let offset = 0;
  111. for (let i = 0; i < recBuffers.length; i++) {
  112. result.set(recBuffers[i], offset);
  113. offset += recBuffers[i].length;
  114. }
  115. return result;
  116. }
  117.  
  118. function interleave(inputL, inputR){
  119. var result = new Float32Array(inputL.length);
  120. for (var i = 0; i < inputL.length; ++i)
  121. result[i] = 0.5 * (inputL[i] + inputR[i]);
  122. return result;
  123. }
  124.  
  125. function floatTo16BitPCM(output, offset, input) {
  126. for (let i = 0; i < input.length; i++, offset += 2) {
  127. let s = Math.max(-1, Math.min(1, input[i]));
  128. output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
  129. }
  130. }
  131.  
  132. function writeString(view, offset, string) {
  133. for (let i = 0; i < string.length; i++) {
  134. view.setUint8(offset + i, string.charCodeAt(i));
  135. }
  136. }
  137.  
  138. function exportWAV(type) {
  139. let bufferL = mergeBuffers(recBuffers[0], recLength);
  140. let bufferR = mergeBuffers(recBuffers[1], recLength);
  141. let interleaved = interleave(bufferL, bufferR);
  142. let downsampledBuffer = downsampleBuffer(interleaved);
  143. let dataview = encodeWAV(downsampledBuffer);
  144. let audioBlob = new Blob([ dataview ], {
  145. type : type
  146. });
  147.  
  148. this.postMessage({command: 'exportWAV', data: audioBlob});
  149. }
  150.  
  151. function downsampleBuffer(buffer) {
  152. if (sampleRate === contextRate) {
  153. return buffer;
  154. }
  155. if (sampleRate > contextRate) {
  156. throw "downsampling rate show be smaller than original sample rate";
  157. }
  158. let sampleRateRatio = contextRate / sampleRate;
  159. let newLength = Math.round(buffer.length / sampleRateRatio);
  160. let result = new Float32Array(newLength);
  161. let offsetResult = 0;
  162. let offsetBuffer = 0;
  163. while (offsetResult < result.length) {
  164. let nextOffsetBuffer = Math.round((offsetResult + 1) * sampleRateRatio);
  165. let accum = 0, count = 0;
  166. for (let i = offsetBuffer; i < nextOffsetBuffer && i < buffer.length; i++) {
  167. accum += buffer[i];
  168. count++;
  169. }
  170. result[offsetResult] = accum / count;
  171. offsetResult++;
  172. offsetBuffer = nextOffsetBuffer;
  173. }
  174. return result;
  175. }
  176.  
  177. function encodeWAV(samples) {
  178. let buffer = new ArrayBuffer(44 + samples.length * 2);
  179. let view = new DataView(buffer);
  180.  
  181. /* RIFF identifier */
  182. writeString(view, 0, 'RIFF');
  183. /* RIFF chunk length */
  184. view.setUint32(4, 36 + samples.length * 2, true);
  185. /* RIFF type */
  186. writeString(view, 8, 'WAVE');
  187. /* format chunk identifier */
  188. writeString(view, 12, 'fmt ');
  189. /* format chunk length */
  190. view.setUint32(16, 16, true);
  191. /* sample format (raw) */
  192. view.setUint16(20, 1, true);
  193. /* channel count */
  194. view.setUint16(22, numChannels, true);
  195. /* sample rate */
  196. view.setUint32(24, sampleRate, true);
  197. /* byte rate (sample rate * block align) */
  198. view.setUint32(28, sampleRate * 4, true);
  199. /* block align (channel count * bytes per sample) */
  200. view.setUint16(32, numChannels * 2, true);
  201. /* bits per sample */
  202. view.setUint16(34, 16, true);
  203. /* data chunk identifier */
  204. writeString(view, 36, 'data');
  205. /* data chunk length */
  206. view.setUint32(40, samples.length * 2, true);
  207.  
  208. floatTo16BitPCM(view, 44, samples);
  209.  
  210. return view;
  211. }
  212. }, self);
  213.  
  214. this.worker.postMessage({
  215. command: 'init',
  216. config: {
  217. contextRate: this.context.sampleRate,
  218. sampleRate: this.config.sampleRate,
  219. numChannels: this.config.numChannels
  220. }
  221. });
  222.  
  223. this.worker.onmessage = (e) => {
  224. let cb = this.callbacks[e.data.command].pop();
  225. if (typeof cb == 'function') {
  226. cb(e.data.data);
  227. }
  228. };
  229. }
  230.  
  231.  
  232. record() {
  233. this.recording = true;
  234. }
  235.  
  236. stop() {
  237. this.recording = false;
  238. }
  239.  
  240. clear() {
  241. this.worker.postMessage({command: 'clear'});
  242. }
  243.  
  244. getBuffer(cb) {
  245. cb = cb || this.config.callback;
  246. if (!cb) throw new Error('Callback not set');
  247.  
  248. this.callbacks.getBuffer.push(cb);
  249.  
  250. this.worker.postMessage({command: 'getBuffer'});
  251. }
  252.  
  253. exportWAV(cb, mimeType) {
  254. mimeType = mimeType || this.config.mimeType;
  255. cb = cb || this.config.callback;
  256. if (!cb) throw new Error('Callback not set');
  257.  
  258. this.callbacks.exportWAV.push(cb);
  259.  
  260. this.worker.postMessage({
  261. command: 'exportWAV',
  262. type: mimeType
  263. });
  264. }
  265.  
  266. dispose(){
  267. try{
  268. if(this._stream){
  269. this._stream.getAudioTracks()[0].stop();
  270. }
  271. }catch(e){}
  272. }
  273.  
  274. static forceDownload(blob, filename) {
  275. let url = (window.URL || window.webkitURL).createObjectURL(blob);
  276. let link = window.document.createElement('a');
  277. link.href = url;
  278. link.download = filename || 'output.wav';
  279. let click = document.createEvent("Event");
  280. click.initEvent("click", true, true);
  281. link.dispatchEvent(click);
  282. }
  283. }
  284.  
  285.  
  286. // Run the stuff.
  287.  
  288. const lexruntime = new LexRuntime({
  289. apiVersion: '2016-11-28',
  290. accessKeyId: '##########',
  291. secretAccessKey: '##########',
  292. region: '##########'
  293. });
  294.  
  295. navigator.getUserMedia({audio: true}, stream => {
  296. window.AudioContext = window.AudioContext || window.webkitAudioContext;
  297. navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia;
  298. window.URL = window.URL || window.webkitURL;
  299. const audio_context = new AudioContext;
  300.  
  301. const input = audio_context.createMediaStreamSource(stream);
  302. input.connect(audio_context.destination);
  303.  
  304. const recorder = new Recorder(input, stream);
  305.  
  306. this._recorder.record();
  307.  
  308. setTimeout(() => {
  309.  
  310. try{
  311. this._recorder.stop();
  312.  
  313. await new Promise((resolve, reject){
  314.  
  315. this._recorder.exportWAV(buffer => {
  316. this._recorder.clear();
  317.  
  318. lexruntime.postContent({
  319. botAlias: 'your bot alias', /* required */
  320. botName: 'your bot name', /* required */
  321. contentType: 'audio/x-l16; sample-rate=16000; channel-count=1', /* required */
  322. inputStream: buffer, /* required */
  323. userId: `user-${Date.now()}`, /* required */
  324. accept: 'audio/*'
  325. }, (err, data) => {
  326. if(err) return reject(err);
  327. resolve(data);
  328. });
  329. });
  330.  
  331. });
  332. }catch(e){
  333. console.log(e);
  334. }
  335.  
  336. }, 3000);
  337.  
  338. }, err => console.error(err));
Add Comment
Please, Sign In to add comment