demo.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. /*
  2. * Copyright (C) 2014-2017 Eitan Isaacson
  3. *
  4. * This program is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation; either version 3 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program; if not, see: <http://www.gnu.org/licenses/>.
  16. */
  17. /* An audio node that can have audio chunks pushed to it */
  18. function PushAudioNode(context, start_callback, end_callback, buffer_size) {
  19. this.context = context;
  20. this.start_callback = start_callback;
  21. this.end_callback = end_callback;
  22. this.buffer_size = buffer_size || 4096;
  23. this.samples_queue = [];
  24. this.scriptNode = context.createScriptProcessor(this.buffer_size, 1, 1);
  25. this.connected = false;
  26. this.sinks = [];
  27. this.startTime = 0;
  28. this.closed = false;
  29. this.track_callbacks = new Map();
  30. }
  31. PushAudioNode.prototype.push = function(chunk) {
  32. if (this.closed) {
  33. throw 'Cannot push more chunks after node was closed';
  34. }
  35. this.samples_queue.push(chunk);
  36. if (!this.connected) {
  37. if (!this.sinks.length) {
  38. throw 'No destination set for PushAudioNode';
  39. }
  40. this._do_connect();
  41. }
  42. }
  43. PushAudioNode.prototype.close = function() {
  44. this.closed = true;
  45. }
  46. PushAudioNode.prototype.connect = function(dest) {
  47. this.sinks.push(dest);
  48. if (this.samples_queue.length) {
  49. this._do_connect();
  50. }
  51. }
  52. PushAudioNode.prototype._do_connect = function() {
  53. if (this.connected) return;
  54. this.connected = true;
  55. for (var dest of this.sinks) {
  56. this.scriptNode.connect(dest);
  57. }
  58. this.scriptNode.onaudioprocess = this.handleEvent.bind(this);
  59. }
  60. PushAudioNode.prototype.disconnect = function() {
  61. this.scriptNode.onaudioprocess = null;
  62. this.scriptNode.disconnect();
  63. this.connected = false;
  64. }
  65. PushAudioNode.prototype.addTrackCallback = function(aTimestamp, aCallback) {
  66. var callbacks = this.track_callbacks.get(aTimestamp) || [];
  67. callbacks.push(aCallback);
  68. this.track_callbacks.set(aTimestamp, callbacks);
  69. }
  70. PushAudioNode.prototype.handleEvent = function(evt) {
  71. if (!this.startTime) {
  72. this.startTime = evt.playbackTime;
  73. if (this.start_callback) {
  74. this.start_callback();
  75. }
  76. }
  77. var currentTime = evt.playbackTime - this.startTime;
  78. var playbackDuration = this.scriptNode.bufferSize / this.context.sampleRate;
  79. for (var entry of this.track_callbacks) {
  80. var timestamp = entry[0];
  81. var callbacks = entry[1];
  82. if (timestamp < currentTime) {
  83. this.track_callbacks.delete(timestamp);
  84. } else if (timestamp < currentTime + playbackDuration) {
  85. for (var cb of callbacks) {
  86. cb();
  87. }
  88. this.track_callbacks.delete(timestamp);
  89. }
  90. }
  91. var offset = 0;
  92. while (this.samples_queue.length && offset < evt.target.bufferSize) {
  93. var chunk = this.samples_queue[0];
  94. var to_copy = chunk.subarray(0, evt.target.bufferSize - offset);
  95. if (evt.outputBuffer.copyToChannel) {
  96. evt.outputBuffer.copyToChannel(to_copy, 0, offset);
  97. } else {
  98. evt.outputBuffer.getChannelData(0).set(to_copy, offset);
  99. }
  100. offset += to_copy.length;
  101. chunk = chunk.subarray(to_copy.length);
  102. if (chunk.length)
  103. this.samples_queue[0] = chunk;
  104. else
  105. this.samples_queue.shift();
  106. }
  107. if (!this.samples_queue.length && this.closed) {
  108. if (this.end_callback) {
  109. this.end_callback(evt.playbackTime - this.startTime);
  110. }
  111. this.disconnect();
  112. }
  113. }
  114. /* Code specific to the demo */
  115. var ctx = new (window.AudioContext || window.webkitAudioContext)();
  116. var tts;
  117. var pusher;
  118. var pusher_buffer_size = 4096;
  119. var chunkID = 0;
  120. function stop() {
  121. console.log('Inside stop()');
  122. if (pusher) {
  123. console.log(' Calling pusher.disconnect...');
  124. pusher.disconnect();
  125. console.log(' Calling pusher.disconnect... done');
  126. pusher = null;
  127. }
  128. console.log('Leaving stop()');
  129. } // end of stop()
  130. function speak() {
  131. console.log('Inside speak()');
  132. console.log(' Stopping...');
  133. stop();
  134. console.log(' Stopping... done');
  135. console.log(' Setting rate...');
  136. tts.set_rate(Number(document.getElementById('rate').value));
  137. console.log(' Setting rate... done');
  138. console.log(' Setting pitch...');
  139. tts.set_pitch(Number(document.getElementById('pitch').value));
  140. console.log(' Setting pitch... done');
  141. console.log(' Setting voice...');
  142. tts.set_voice(document.getElementById('voice').value);
  143. console.log(' Setting voice... done');
  144. var now = Date.now();
  145. chunkID = 0;
  146. console.log(' Creating pusher...');
  147. pusher = new PushAudioNode(
  148. ctx,
  149. function() {
  150. //console.log('PushAudioNode started!', ctx.currentTime, pusher.startTime);
  151. },
  152. function() {
  153. //console.log('PushAudioNode ended!', ctx.currentTime - pusher.startTime);
  154. },
  155. pusher_buffer_size
  156. );
  157. pusher.connect(ctx.destination);
  158. console.log(' Creating pusher... done');
  159. var user_text = document.getElementById('texttospeak').value;
  160. // actual synthesis
  161. console.log(' Calling synthesize...');
  162. tts.synthesize(
  163. user_text,
  164. function cb(samples, events) {
  165. //console.log(' Inside synt cb');
  166. if (!samples) {
  167. if (pusher) {
  168. pusher.close();
  169. }
  170. return;
  171. }
  172. if (pusher) {
  173. //console.log(' Pushing chunk ' + chunkID, Date.now());
  174. pusher.push(new Float32Array(samples));
  175. ++chunkID;
  176. }
  177. if (now) {
  178. //console.log(' Latency:', Date.now() - now);
  179. now = 0;
  180. }
  181. //console.log(' Leaving synt cb');
  182. } // end of function cb
  183. ); // end of tts.synthesize()
  184. console.log(' Calling synthesize... done');
  185. console.log('Leaving speak()');
  186. } // end of speak()
  187. function ipa() {
  188. console.log("Synthesizing ipa ... ");
  189. var ts = new Date();
  190. var user_text = document.getElementById('texttospeak').value;
  191. //user_text = user_text.repeat(50);
  192. tts.set_voice(document.getElementById('voice').value);
  193. tts.synthesize_ipa(user_text, function(result) {
  194. var te = new Date();
  195. document.getElementById('ipaarea').value = result.ipa;
  196. console.log("Ipa synthesis done in " + (te-ts) + " ms.")
  197. });
  198. }
  199. function speakAndIpa() {
  200. speak();
  201. ipa();
  202. }
  203. function resetPitch() {
  204. document.getElementById('pitch').value = 50;
  205. }
  206. function resetRate() {
  207. document.getElementById('rate').value = 175;
  208. }
  209. function resetVoice() {
  210. document.getElementById('default-voice').selected = true;
  211. }
  212. function initializeDemo() {
  213. console.log('Creating eSpeakNG instance...');
  214. tts = new eSpeakNG(
  215. 'js/espeakng.worker.js',
  216. function cb1() {
  217. console.log('Inside cb1');
  218. tts.list_voices(
  219. function cb2(result) {
  220. console.log('Inside cb2');
  221. var sel = document.getElementById('voice');
  222. var index = 0;
  223. for (voice of result) {
  224. var opt = document.createElement('option');
  225. var languages = voice.languages.map(function(lang) {
  226. return lang.name;
  227. }).join(", ");
  228. opt.text = voice.name + ' (' + languages + ')';
  229. opt.value = voice.identifier;
  230. console.log('Adding voice: ' + opt.text);
  231. sel.add(opt);
  232. if (voice.name === 'English (Great Britain)') {
  233. opt.id = 'default-voice';
  234. opt.selected = true;
  235. }
  236. }
  237. console.log('Leaving cb2');
  238. } // end of function cb2
  239. );
  240. console.log('Removing loading class...');
  241. document.body.classList.remove('loading');
  242. console.log('Removing loading class... done');
  243. console.log('Leaving cb1');
  244. } // end of function cb1
  245. );
  246. console.log('Creating eSpeakNG instance... done');
  247. }