eSpeak NG is an open source speech synthesizer that supports more than hundred languages and accents.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

demo.js 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  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. if (ctx.state === 'suspended') {
  133. console.log('Resuming AudioContext...');
  134. ctx.resume();
  135. console.log('Resuming AudioContext... done');
  136. }
  137. console.log(' Stopping...');
  138. stop();
  139. console.log(' Stopping... done');
  140. console.log(' Setting rate...');
  141. tts.set_rate(Number(document.getElementById('rate').value));
  142. console.log(' Setting rate... done');
  143. console.log(' Setting pitch...');
  144. tts.set_pitch(Number(document.getElementById('pitch').value));
  145. console.log(' Setting pitch... done');
  146. console.log(' Setting voice...');
  147. tts.set_voice(document.getElementById('voice').value);
  148. console.log(' Setting voice... done');
  149. var now = Date.now();
  150. chunkID = 0;
  151. console.log(' Creating pusher...');
  152. pusher = new PushAudioNode(
  153. ctx,
  154. function() {
  155. //console.log('PushAudioNode started!', ctx.currentTime, pusher.startTime);
  156. },
  157. function() {
  158. //console.log('PushAudioNode ended!', ctx.currentTime - pusher.startTime);
  159. },
  160. pusher_buffer_size
  161. );
  162. pusher.connect(ctx.destination);
  163. console.log(' Creating pusher... done');
  164. var user_text = document.getElementById('texttospeak').value;
  165. // actual synthesis
  166. console.log(' Calling synthesize...');
  167. tts.synthesize(
  168. user_text,
  169. function cb(samples, events) {
  170. console.log(' Receiving synthesis samples...');
  171. if (!samples) {
  172. if (pusher) {
  173. pusher.close();
  174. }
  175. return;
  176. }
  177. if (pusher) {
  178. //console.log(' Pushing chunk ' + chunkID, Date.now());
  179. pusher.push(new Float32Array(samples));
  180. ++chunkID;
  181. }
  182. if (now) {
  183. //console.log(' Latency:', Date.now() - now);
  184. now = 0;
  185. }
  186. //console.log(' Leaving synt cb');
  187. } // end of function cb
  188. ); // end of tts.synthesize()
  189. console.log(' Calling synthesize... done');
  190. console.log('Leaving speak()');
  191. } // end of speak()
  192. function ipa() {
  193. console.log("Synthesizing ipa ... ");
  194. var ts = new Date();
  195. var user_text = document.getElementById('texttospeak').value;
  196. //user_text = user_text.repeat(50);
  197. tts.set_voice(document.getElementById('voice').value);
  198. tts.synthesize_ipa(user_text, function(result) {
  199. var te = new Date();
  200. document.getElementById('ipaarea').value = result.ipa;
  201. console.log("Ipa synthesis done in " + (te-ts) + " ms.")
  202. });
  203. }
  204. function speakAndIpa() {
  205. speak();
  206. ipa();
  207. }
  208. function resetPitch() {
  209. document.getElementById('pitch').value = 50;
  210. }
  211. function resetRate() {
  212. document.getElementById('rate').value = 175;
  213. }
  214. function resetVoice() {
  215. document.getElementById('default-voice').selected = true;
  216. }
  217. function initializeDemo() {
  218. console.log('Creating eSpeakNG instance...');
  219. tts = new eSpeakNG(
  220. 'js/espeakng.worker.js',
  221. function cb1() {
  222. console.log('Inside cb1');
  223. tts.list_voices(
  224. function cb2(result) {
  225. console.log('Inside cb2');
  226. var sel = document.getElementById('voice');
  227. var index = 0;
  228. for (voice of result) {
  229. var opt = document.createElement('option');
  230. var languages = voice.languages.map(function(lang) {
  231. return lang.name;
  232. }).join(", ");
  233. opt.text = voice.name + ' (' + languages + ')';
  234. opt.value = voice.identifier;
  235. console.log('Adding voice: ' + opt.text);
  236. sel.add(opt);
  237. if (voice.name === 'English (Great Britain)') {
  238. opt.id = 'default-voice';
  239. opt.selected = true;
  240. }
  241. }
  242. console.log('Leaving cb2');
  243. } // end of function cb2
  244. );
  245. console.log('Removing loading class...');
  246. document.body.classList.remove('loading');
  247. console.log('Removing loading class... done');
  248. console.log('Leaving cb1');
  249. } // end of function cb1
  250. );
  251. console.log('Creating eSpeakNG instance... done');
  252. }