ggerganov commited on
Commit
363140f
·
1 Parent(s): ca4986c

wip : polishing WASM example

Browse files
bindings/javascript/CMakeLists.txt CHANGED
@@ -21,15 +21,13 @@ if (WHISPER_WASM_SINGLE_FILE)
21
  )
22
  endif()
23
 
24
- #-s TOTAL_MEMORY=536870912 \
25
  set_target_properties(${TARGET} PROPERTIES LINK_FLAGS " \
26
  --bind \
27
- -s MODULARIZE=1 \
28
- -s ASSERTIONS=1 \
29
  -s USE_PTHREADS=1 \
30
- -s PTHREAD_POOL_SIZE=9 \
31
- -s ALLOW_MEMORY_GROWTH=1 \
 
32
  -s FORCE_FILESYSTEM=1 \
33
- -s EXPORT_NAME=\"'whisper_factory'\" \
34
  ${EXTRA_FLAGS} \
35
  ")
 
21
  )
22
  endif()
23
 
 
24
  set_target_properties(${TARGET} PROPERTIES LINK_FLAGS " \
25
  --bind \
 
 
26
  -s USE_PTHREADS=1 \
27
+ -s PTHREAD_POOL_SIZE=8 \
28
+ -s INITIAL_MEMORY=1610612736 \
29
+ -s TOTAL_MEMORY=1610612736 \
30
  -s FORCE_FILESYSTEM=1 \
31
+ -s EXPORTED_RUNTIME_METHODS=\"['print', 'printErr', 'ccall', 'cwrap']\" \
32
  ${EXTRA_FLAGS} \
33
  ")
bindings/javascript/emscripten.cpp CHANGED
@@ -13,7 +13,11 @@ EMSCRIPTEN_BINDINGS(whisper) {
13
  for (size_t i = 0; i < g_contexts.size(); ++i) {
14
  if (g_contexts[i] == nullptr) {
15
  g_contexts[i] = whisper_init(path_model.c_str());
16
- return i + 1;
 
 
 
 
17
  }
18
  }
19
 
@@ -29,7 +33,7 @@ EMSCRIPTEN_BINDINGS(whisper) {
29
  }
30
  }));
31
 
32
- emscripten::function("full_default", emscripten::optional_override([](size_t index, const emscripten::val & audio) {
33
  --index;
34
 
35
  if (index >= g_contexts.size()) {
@@ -42,15 +46,20 @@ EMSCRIPTEN_BINDINGS(whisper) {
42
 
43
  struct whisper_full_params params = whisper_full_default_params(whisper_sampling_strategy::WHISPER_SAMPLING_GREEDY);
44
 
 
 
45
  params.print_realtime = true;
46
  params.print_progress = false;
47
  params.print_timestamps = true;
48
  params.print_special_tokens = false;
49
- params.translate = false;
50
- params.language = "en";
51
  params.n_threads = std::min(8, (int) std::thread::hardware_concurrency());
52
  params.offset_ms = 0;
53
 
 
 
 
54
  std::vector<float> pcmf32;
55
  const int n = audio["length"].as<int>();
56
 
 
13
  for (size_t i = 0; i < g_contexts.size(); ++i) {
14
  if (g_contexts[i] == nullptr) {
15
  g_contexts[i] = whisper_init(path_model.c_str());
16
+ if (g_contexts[i] != nullptr) {
17
+ return i + 1;
18
+ } else {
19
+ return (size_t) 0;
20
+ }
21
  }
22
  }
23
 
 
33
  }
34
  }));
35
 
36
+ emscripten::function("full_default", emscripten::optional_override([](size_t index, const emscripten::val & audio, const std::string & lang, bool translate) {
37
  --index;
38
 
39
  if (index >= g_contexts.size()) {
 
46
 
47
  struct whisper_full_params params = whisper_full_default_params(whisper_sampling_strategy::WHISPER_SAMPLING_GREEDY);
48
 
49
+ printf("full_default: available threads %d\n", std::thread::hardware_concurrency());
50
+
51
  params.print_realtime = true;
52
  params.print_progress = false;
53
  params.print_timestamps = true;
54
  params.print_special_tokens = false;
55
+ params.translate = translate;
56
+ params.language = whisper_is_multilingual(g_contexts[index]) ? lang.c_str() : "en";
57
  params.n_threads = std::min(8, (int) std::thread::hardware_concurrency());
58
  params.offset_ms = 0;
59
 
60
+ printf("full_default: using %d threads\n", params.n_threads);
61
+ printf("full_default: language '%s'\n", params.language);
62
+
63
  std::vector<float> pcmf32;
64
  const int n = audio["length"].as<int>();
65
 
bindings/javascript/whisper.js CHANGED
The diff for this file is too large to render. See raw diff
 
examples/whisper.wasm/index-tmpl.html CHANGED
@@ -2,22 +2,170 @@
2
  <html lang="en-us">
3
  <head>
4
  <title>whisper.cpp : WASM example</title>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  </head>
6
  <body>
7
  <div id="main-container">
8
- Minimal <b>whisper.cpp</b> example using Javascript bindings
9
 
10
  <br><br>
11
 
12
- Model:
13
- <input type="file" id="file" name="file" onchange="loadFile(event, 'ggml.bin')" />
14
- <br><br>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- WAV:
17
- <input type="file" id="file" name="file" onchange="loadAudio(event)" />
18
  <br><br>
19
 
20
- <button onclick="onTranscribe();">Transcribe</button>
 
21
 
22
  <br><br>
23
 
@@ -32,8 +180,60 @@
32
  </div>
33
  </div>
34
 
35
- <script type="text/javascript" src="whisper.js"></script>
36
  <script type='text/javascript'>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  window.AudioContext = window.AudioContext || window.webkitAudioContext;
38
  window.OfflineAudioContext = window.OfflineAudioContext || window.webkitOfflineAudioContext;
39
 
@@ -43,15 +243,9 @@
43
  // audio data
44
  var audio = null;
45
 
46
- // the whisper module instance
47
- var whisper = null;
48
  var instance = null;
49
-
50
- // instantiate the whisper instance
51
- // whisper_factory comes from the whisper.js module
52
- whisper_factory().then(function(obj) {
53
- whisper = obj;
54
- });
55
 
56
  // helper function
57
  function convertTypedArray(src, type) {
@@ -60,47 +254,29 @@
60
  return new type(buffer);
61
  }
62
 
63
- // initialize whisper
64
- function init() {
65
- if (!instance) {
66
- instance = whisper.init('ggml.bin');
67
- if (instance) {
68
- console.log('whisper instance initialized');
69
- }
70
- }
71
-
72
- if (!instance) {
73
- console.log('whisper instance initialization failed');
74
- return;
75
- }
76
-
77
- if (instance) {
78
- var ret = whisper.full_default(instance, audio);
79
- if (ret) {
80
- console.log('whisper full_default returned: ' + ret);
81
- }
82
- }
83
- }
84
-
85
  function loadFile(event, fname) {
86
  var file = event.target.files[0] || null;
87
  if (file == null) {
88
  return;
89
  }
90
 
91
- console.log(
92
- "<p>File information: <strong>" + file.name +
93
- "</strong> type: <strong>" + file.type +
94
- "</strong> size: <strong>" + file.size +
95
- "</strong> bytes</p>"
96
- );
97
 
98
  var reader = new FileReader();
99
  reader.onload = function(event) {
100
  var buf = new Uint8Array(reader.result);
101
 
102
  // write to WASM file using whisper.FS_createDataFile
103
- whisper.FS_createDataFile("/", fname, buf, true, true);
 
 
 
 
 
 
 
 
104
  }
105
  reader.readAsArrayBuffer(file);
106
  }
@@ -115,12 +291,8 @@
115
  return;
116
  }
117
 
118
- console.log(
119
- "<p>Audio information: <strong>" + file.name +
120
- "</strong> type: <strong>" + file.type +
121
- "</strong> size: <strong>" + file.size +
122
- "</strong> bytes</p>"
123
- );
124
 
125
  var reader = new FileReader();
126
  reader.onload = function(event) {
@@ -135,19 +307,172 @@
135
 
136
  offlineContext.startRendering().then(function(renderedBuffer) {
137
  audio = renderedBuffer.getChannelData(0);
138
- //var audio16 = convertTypedArray(data, Int16Array);
 
 
 
 
 
 
 
 
139
  });
 
 
 
 
140
  });
141
  }
142
  reader.readAsArrayBuffer(file);
143
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  //
145
  // Transcribe
146
  //
147
 
148
- function onTranscribe() {
149
- init();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  }
151
  </script>
 
152
  </body>
153
  </html>
 
2
  <html lang="en-us">
3
  <head>
4
  <title>whisper.cpp : WASM example</title>
5
+
6
+ <style>
7
+ #output {
8
+ width: 100%;
9
+ height: 100%;
10
+ margin: 0 auto;
11
+ margin-top: 10px;
12
+ border-left: 0px;
13
+ border-right: 0px;
14
+ padding-left: 0px;
15
+ padding-right: 0px;
16
+ display: block;
17
+ background-color: black;
18
+ color: white;
19
+ font-size: 10px;
20
+ font-family: 'Lucida Console', Monaco, monospace;
21
+ outline: none;
22
+ white-space: pre;
23
+ overflow-wrap: normal;
24
+ overflow-x: scroll;
25
+ }
26
+ </style>
27
  </head>
28
  <body>
29
  <div id="main-container">
30
+ <b>Minimal <a href="https://github.com/ggerganov/whisper.cpp">whisper.cpp</a> example running fully in the browser</b>
31
 
32
  <br><br>
33
 
34
+ Usage instructions:<br>
35
+ <ul>
36
+ <li>Load a ggml model file (you can obtain one from <a href="https://ggml.ggerganov.com/">here</a>, recommended: <b>tiny</b> or <b>base</b>)</li>
37
+ <li>Select audio file to transcribe or record audio from the microphone (sample: <a href="https://whisper.ggerganov.com/jfk.wav">jfk.wav</a>)</li>
38
+ <li>Click on the "Transcribe" button to start the transcription</li>
39
+ </ul>
40
+
41
+ Note that the computation is quite heavy and may take a few seconds to complete.<br>
42
+ The transcription results will be displayed in the text area below.<br><br>
43
+ <b>Important: your browser must support WASM SIMD instructions for this to work.</b>
44
+
45
+ <br><br><hr>
46
+
47
+ <div id="model">
48
+ Model:
49
+ <input type="file" id="file" name="file" onchange="loadFile(event, 'ggml.bin')" />
50
+ </div>
51
+
52
+ <br>
53
+
54
+ <!-- radio button to select between file upload or microphone -->
55
+ <div id="input">
56
+ Input:
57
+ <input type="radio" id="file" name="input" value="file" checked="checked" onchange="changeInput('file')" /> File
58
+ <input type="radio" id="mic" name="input" value="mic" onchange="changeInput('mic')" /> Microphone
59
+ </div>
60
+
61
+ <br>
62
+
63
+ <div id="input_file">
64
+ Audio file:
65
+ <input type="file" id="file" name="file" onchange="loadAudio(event)" />
66
+ </div>
67
+
68
+ <div id="input_mic" style="display: none;">
69
+ Microphone:
70
+ <button id="start" onclick="startRecording()">Start</button>
71
+ <button id="stop" onclick="stopRecording()" disabled>Stop</button>
72
+
73
+ <!-- progress bar to show recording progress -->
74
+ <br><br>
75
+ <div id="progress" style="display: none;">
76
+ <div id="progress-bar" style="width: 0%; height: 10px; background-color: #4CAF50;"></div>
77
+ <div id="progress-text">0%</div>
78
+ </div>
79
+ </div>
80
+
81
+ <audio controls="controls" id="audio" loop hidden>
82
+ Your browser does not support the &lt;audio&gt; tag.
83
+ <source id="source" src="" type="audio/wav" />
84
+ </audio>
85
+
86
+ <hr><br>
87
+
88
+ <table>
89
+ <tr>
90
+ <td>
91
+ Language:
92
+ <select id="language" name="language">
93
+ <option value="en">English</option>
94
+ <option value="ar">Arabic</option>
95
+ <option value="hy">Armenian</option>
96
+ <option value="az">Azerbaijani</option>
97
+ <option value="eu">Basque</option>
98
+ <option value="be">Belarusian</option>
99
+ <option value="bn">Bengali</option>
100
+ <option value="bg">Bulgarian</option>
101
+ <option value="ca">Catalan</option>
102
+ <option value="zh">Chinese</option>
103
+ <option value="hr">Croatian</option>
104
+ <option value="cs">Czech</option>
105
+ <option value="da">Danish</option>
106
+ <option value="nl">Dutch</option>
107
+ <option value="en">English</option>
108
+ <option value="et">Estonian</option>
109
+ <option value="tl">Filipino</option>
110
+ <option value="fi">Finnish</option>
111
+ <option value="fr">French</option>
112
+ <option value="gl">Galician</option>
113
+ <option value="ka">Georgian</option>
114
+ <option value="de">German</option>
115
+ <option value="el">Greek</option>
116
+ <option value="gu">Gujarati</option>
117
+ <option value="iw">Hebrew</option>
118
+ <option value="hi">Hindi</option>
119
+ <option value="hu">Hungarian</option>
120
+ <option value="is">Icelandic</option>
121
+ <option value="id">Indonesian</option>
122
+ <option value="ga">Irish</option>
123
+ <option value="it">Italian</option>
124
+ <option value="ja">Japanese</option>
125
+ <option value="kn">Kannada</option>
126
+ <option value="ko">Korean</option>
127
+ <option value="la">Latin</option>
128
+ <option value="lv">Latvian</option>
129
+ <option value="lt">Lithuanian</option>
130
+ <option value="mk">Macedonian</option>
131
+ <option value="ms">Malay</option>
132
+ <option value="mt">Maltese</option>
133
+ <option value="no">Norwegian</option>
134
+ <option value="fa">Persian</option>
135
+ <option value="pl">Polish</option>
136
+ <option value="pt">Portuguese</option>
137
+ <option value="ro">Romanian</option>
138
+ <option value="ru">Russian</option>
139
+ <option value="sr">Serbian</option>
140
+ <option value="sk">Slovak</option>
141
+ <option value="sl">Slovenian</option>
142
+ <option value="es">Spanish</option>
143
+ <option value="sw">Swahili</option>
144
+ <option value="sv">Swedish</option>
145
+ <option value="ta">Tamil</option>
146
+ <option value="te">Telugu</option>
147
+ <option value="th">Thai</option>
148
+ <option value="tr">Turkish</option>
149
+ <option value="uk">Ukrainian</option>
150
+ <option value="ur">Urdu</option>
151
+ <option value="vi">Vietnamese</option>
152
+ <option value="cy">Welsh</option>
153
+ <option value="yi">Yiddish</option>
154
+ </select>
155
+ </td>
156
+ <td>
157
+ <button onclick="onProcess(false);">Transcribe</button>
158
+ </td>
159
+ <td>
160
+ <button onclick="onProcess(true);">Translate</button>
161
+ </td>
162
+ </tr>
163
+ </table>
164
 
 
 
165
  <br><br>
166
 
167
+ <!-- textarea with height filling the rest of the page -->
168
+ <textarea id="output" rows="20"></textarea>
169
 
170
  <br><br>
171
 
 
180
  </div>
181
  </div>
182
 
 
183
  <script type='text/javascript'>
184
+ // TODO: convert audio buffer to WAV
185
+ function setAudio(audio) {
186
+ //if (audio) {
187
+ // // convert to 16-bit PCM
188
+ // var blob = new Blob([audio], { type: 'audio/wav' });
189
+ // var url = URL.createObjectURL(blob);
190
+ // document.getElementById('source').src = url;
191
+ // document.getElementById('audio').hidden = false;
192
+ // document.getElementById('audio').loop = false;
193
+ // document.getElementById('audio').load();
194
+ //} else {
195
+ // document.getElementById('audio').hidden = true;
196
+ //}
197
+ }
198
+
199
+ function changeInput(input) {
200
+ if (input == 'file') {
201
+ document.getElementById('input_file').style.display = 'block';
202
+ document.getElementById('input_mic').style.display = 'none';
203
+ document.getElementById('progress').style.display = 'none';
204
+ } else {
205
+ document.getElementById('input_file').style.display = 'none';
206
+ document.getElementById('input_mic').style.display = 'block';
207
+ document.getElementById('progress').style.display = 'block';
208
+ }
209
+ }
210
+
211
+ var printTextarea = (function() {
212
+ var element = document.getElementById('output');
213
+ if (element) element.alue = ''; // clear browser cache
214
+ return function(text) {
215
+ if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
216
+ console.log(text);
217
+ if (element) {
218
+ element.value += text + "\n";
219
+ element.scrollTop = element.scrollHeight; // focus on bottom
220
+ }
221
+ };
222
+ })();
223
+
224
+ var Module = {
225
+ print: printTextarea,
226
+ printErr: printTextarea,
227
+ setStatus: function(text) {
228
+ printTextarea('js: ' + text);
229
+ },
230
+ monitorRunDependencies: function(left) {
231
+ }
232
+ };
233
+
234
+ const kMaxAudio_s = 120;
235
+ const kSampleRate = 16000;
236
+
237
  window.AudioContext = window.AudioContext || window.webkitAudioContext;
238
  window.OfflineAudioContext = window.OfflineAudioContext || window.webkitOfflineAudioContext;
239
 
 
243
  // audio data
244
  var audio = null;
245
 
246
+ // the whisper instance
 
247
  var instance = null;
248
+ var model_fname = '';
 
 
 
 
 
249
 
250
  // helper function
251
  function convertTypedArray(src, type) {
 
254
  return new type(buffer);
255
  }
256
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
  function loadFile(event, fname) {
258
  var file = event.target.files[0] || null;
259
  if (file == null) {
260
  return;
261
  }
262
 
263
+ printTextarea("js: loading model: " + file.name + ", size: " + file.size + " bytes");
264
+ printTextarea('js: please wait ...');
 
 
 
 
265
 
266
  var reader = new FileReader();
267
  reader.onload = function(event) {
268
  var buf = new Uint8Array(reader.result);
269
 
270
  // write to WASM file using whisper.FS_createDataFile
271
+ // if the file exists, delete it
272
+ try {
273
+ Module.FS_unlink(fname);
274
+ } catch (e) {
275
+ }
276
+ Module.FS_createDataFile("/", fname, buf, true, true);
277
+
278
+ model_fname = file.name;
279
+ printTextarea('js: loaded model: ' + model_fname + ' size: ' + buf.length);
280
  }
281
  reader.readAsArrayBuffer(file);
282
  }
 
291
  return;
292
  }
293
 
294
+ printTextarea('js: loading audio: ' + file.name + ', size: ' + file.size + ' bytes');
295
+ printTextarea('js: please wait ...');
 
 
 
 
296
 
297
  var reader = new FileReader();
298
  reader.onload = function(event) {
 
307
 
308
  offlineContext.startRendering().then(function(renderedBuffer) {
309
  audio = renderedBuffer.getChannelData(0);
310
+ printTextarea('js: audio loaded, size: ' + audio.length);
311
+
312
+ // truncate to first 30 seconds
313
+ if (audio.length > kMaxAudio_s*kSampleRate) {
314
+ audio = audio.slice(0, kMaxAudio_s*kSampleRate);
315
+ printTextarea('js: truncated audio to first ' + kMaxAudio_s + ' seconds');
316
+ }
317
+
318
+ setAudio(audio);
319
  });
320
+ }, function(e) {
321
+ printTextarea('js: error decoding audio: ' + e);
322
+ audio = null;
323
+ setAudio(audio);
324
  });
325
  }
326
  reader.readAsArrayBuffer(file);
327
  }
328
+
329
+ //
330
+ // Microphone
331
+ //
332
+
333
+ var mediaRecorder = null;
334
+ var doRecording = false;
335
+ var startTime = 0;
336
+
337
+ function stopRecording() {
338
+ doRecording = false;
339
+ }
340
+
341
+ // record up to kMaxAudio_s seconds of audio from the microphone
342
+ // check if doRecording is false every 1000 ms and stop recording if so
343
+ // update progress information
344
+ function startRecording() {
345
+ if (!context) {
346
+ context = new AudioContext({sampleRate: 16000});
347
+ }
348
+
349
+ document.getElementById('start').disabled = true;
350
+ document.getElementById('stop').disabled = false;
351
+
352
+ document.getElementById('progress-bar').style.width = '0%';
353
+ document.getElementById('progress-text').innerHTML = '0%';
354
+
355
+ doRecording = true;
356
+ startTime = Date.now();
357
+
358
+ var chunks = [];
359
+ var stream = null;
360
+
361
+ navigator.mediaDevices.getUserMedia({audio: true, video: false})
362
+ .then(function(s) {
363
+ stream = s;
364
+ mediaRecorder = new MediaRecorder(stream);
365
+ mediaRecorder.ondataavailable = function(e) {
366
+ chunks.push(e.data);
367
+ };
368
+ mediaRecorder.onstop = function(e) {
369
+ var blob = new Blob(chunks, { 'type' : 'audio/ogg; codecs=opus' });
370
+ chunks = [];
371
+
372
+ document.getElementById('start').disabled = false;
373
+ document.getElementById('stop').disabled = true;
374
+
375
+ var reader = new FileReader();
376
+ reader.onload = function(event) {
377
+ var buf = new Uint8Array(reader.result);
378
+
379
+ context.decodeAudioData(buf.buffer, function(audioBuffer) {
380
+ var offlineContext = new OfflineAudioContext(audioBuffer.numberOfChannels, audioBuffer.length, audioBuffer.sampleRate);
381
+ var source = offlineContext.createBufferSource();
382
+ source.buffer = audioBuffer;
383
+ source.connect(offlineContext.destination);
384
+ source.start(0);
385
+
386
+ offlineContext.startRendering().then(function(renderedBuffer) {
387
+ audio = renderedBuffer.getChannelData(0);
388
+ printTextarea('js: audio recorded, size: ' + audio.length);
389
+
390
+ // truncate to first 30 seconds
391
+ if (audio.length > kMaxAudio_s*kSampleRate) {
392
+ audio = audio.slice(0, kMaxAudio_s*kSampleRate);
393
+ printTextarea('js: truncated audio to first ' + kMaxAudio_s + ' seconds');
394
+ }
395
+ setAudio(audio);
396
+ });
397
+ }, function(e) {
398
+ printTextarea('js: error decoding audio: ' + e);
399
+ audio = null;
400
+ setAudio(audio);
401
+ });
402
+ }
403
+
404
+ reader.readAsArrayBuffer(blob);
405
+ };
406
+ mediaRecorder.start();
407
+ })
408
+ .catch(function(err) {
409
+ printTextarea('js: error getting audio stream: ' + err);
410
+ });
411
+
412
+ var interval = setInterval(function() {
413
+ if (!doRecording) {
414
+ clearInterval(interval);
415
+ mediaRecorder.stop();
416
+ stream.getTracks().forEach(function(track) {
417
+ track.stop();
418
+ });
419
+ }
420
+
421
+ document.getElementById('progress-bar').style.width = (100*(Date.now() - startTime)/1000/kMaxAudio_s) + '%';
422
+ document.getElementById('progress-text').innerHTML = (100*(Date.now() - startTime)/1000/kMaxAudio_s).toFixed(0) + '%';
423
+ }, 1000);
424
+
425
+ printTextarea('js: recording ...');
426
+
427
+ setTimeout(function() {
428
+ if (doRecording) {
429
+ printTextarea('js: recording stopped after ' + kMaxAudio_s + ' seconds');
430
+ stopRecording();
431
+ }
432
+ }, kMaxAudio_s*1000);
433
+ }
434
+
435
  //
436
  // Transcribe
437
  //
438
 
439
+ function onProcess(translate) {
440
+ if (!instance) {
441
+ instance = Module.init('ggml.bin');
442
+
443
+ if (instance) {
444
+ printTextarea("js: whisper initialized, instance: " + instance);
445
+ document.getElementById('model').innerHTML = 'Model loaded: ' + model_fname;
446
+ }
447
+ }
448
+
449
+ if (!instance) {
450
+ printTextarea("js: failed to initialize whisper");
451
+ return;
452
+ }
453
+
454
+ if (!audio) {
455
+ printTextarea("js: no audio data");
456
+ return;
457
+ }
458
+
459
+ if (instance) {
460
+ printTextarea('');
461
+ printTextarea('js: processing - this might take a while ...');
462
+ printTextarea('js: the page will be unresponsive until the processing is completed');
463
+ printTextarea('');
464
+ printTextarea('');
465
+
466
+ setTimeout(function() {
467
+ var ret = Module.full_default(instance, audio, document.getElementById('language').value, translate);
468
+ console.log('js: full_default returned: ' + ret);
469
+ if (ret) {
470
+ printTextarea("js: whisper returned: " + ret);
471
+ }
472
+ }, 100);
473
+ }
474
  }
475
  </script>
476
+ <script type="text/javascript" src="whisper.js"></script>
477
  </body>
478
  </html>