natasa365/whisper.cpp
0
1<!doctype html>2<html lang="en-us">3 <head>4 <title>command : Voice assistant example using Whisper + WebAssembly</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 <script src="../coi-serviceworker.js"></script>28 <link rel="icon" href="data:,">29 </head>30 <body>31 <div id="main-container">32 <b>command : Voice assistant example using Whisper + WebAssembly</b>33 34 <br><br>35 36 You can find more about this project on <a href="https://github.com/ggerganov/whisper.cpp/tree/master/examples/command.wasm">GitHub</a>.37 38 <br><br>39 40 <b>More examples:</b>41 <a href="../">main</a> |42 <a href="../bench.wasm/">bench</a> |43 <a href="../stream.wasm">stream</a> |44 <a href="../command.wasm/">command</a> |45 46 <br><br>47 48 <hr>49 50 Select the model you would like to use, click the "Start" button and follow the instructions.51 52 <br><br>53 54 <div id="model-whisper">55 Whisper model: <span id="model-whisper-status"></span>56 <button id="fetch-whisper-tiny-en" onclick="loadWhisper('tiny.en')">tiny.en (75 MB)</button>57 <button id="fetch-whisper-base-en" onclick="loadWhisper('base.en')">base.en (142 MB)</button>58 <br><br>59 Quantized models:<br><br>60 <button id="fetch-whisper-tiny-en-q5_1" onclick="loadWhisper('tiny-en-q5_1')">tiny.en (Q5_1, 31 MB)</button>61 <button id="fetch-whisper-base-en-q5_1" onclick="loadWhisper('base-en-q5_1')">base.en (Q5_1, 57 MB)</button>62 <span id="fetch-whisper-progress"></span>63 64 <!--65 <input type="file" id="file" name="file" onchange="loadFile(event, 'whisper.bin')" />66 -->67 </div>68 69 <br>70 71 <div id="input">72 <button id="start" onclick="onStart()" disabled>Start</button>73 <button id="stop" onclick="onStop()" disabled>Stop</button>74 <button id="clear" onclick="clearCache()">Clear Cache</button>75 </div>76 77 <br>78 79 <div id="state">80 Status: <b><span id="state-status">not started</span></b>81 82 <pre id="state-transcribed">[The recognized voice commands will be displayed here]</pre>83 </div>84 85 <hr>86 87 Debug output:88 <textarea id="output" rows="20"></textarea>89 90 <br>91 92 <b>Troubleshooting</b>93 94 <br><br>95 96 The page does some heavy computations, so make sure:97 98 <ul>99 <li>To use a modern web browser (e.g. Chrome, Firefox)</li>100 <li>To use a fast desktop or laptop computer (i.e. not a mobile phone)</li>101 <li>Your browser supports WASM <a href="https://webassembly.org/roadmap/">Fixed-width SIMD</a></li>102 </ul>103 104 <div class="cell-version">105 <span>106 |107 Build time: <span class="nav-link">@GIT_DATE@</span> |108 Commit hash: <a class="nav-link" href="https://github.com/ggerganov/whisper.cpp/commit/@GIT_SHA1@">@GIT_SHA1@</a> |109 Commit subject: <span class="nav-link">@GIT_COMMIT_SUBJECT@</span> |110 <a class="nav-link" href="https://github.com/ggerganov/whisper.cpp/tree/master/examples/command.wasm">Source Code</a> |111 </span>112 </div>113 </div>114 115 <script type="text/javascript" src="helpers.js"></script>116 <script type='text/javascript'>117 // web audio context118 var context = null;119 120 // audio data121 var audio = null;122 var audio0 = null;123 124 // the command instance125 var instance = null;126 127 // model name128 var model_whisper = null;129 130 var Module = {131 print: printTextarea,132 printErr: printTextarea,133 setStatus: function(text) {134 printTextarea('js: ' + text);135 },136 monitorRunDependencies: function(left) {137 },138 preRun: function() {139 printTextarea('js: Preparing ...');140 },141 postRun: function() {142 printTextarea('js: Initialized successfully!');143 }144 };145 146 //147 // fetch models148 //149 150 let dbVersion = 1151 let dbName = 'whisper.ggerganov.com';152 let indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB153 154 function storeFS(fname, buf) {155 // write to WASM file using FS_createDataFile156 // if the file exists, delete it157 try {158 Module.FS_unlink(fname);159 } catch (e) {160 // ignore161 }162 163 Module.FS_createDataFile("/", fname, buf, true, true);164 165 printTextarea('storeFS: stored model: ' + fname + ' size: ' + buf.length);166 167 document.getElementById('model-whisper-status').innerHTML = 'loaded "' + model_whisper + '"!';168 169 if (model_whisper != null) {170 document.getElementById('start').disabled = false;171 document.getElementById('stop' ).disabled = true;172 }173 }174 175 function loadWhisper(model) {176 let urls = {177 'tiny.en': 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.en.bin',178 'base.en': 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin',179 180 'tiny-en-q5_1': 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.en-q5_1.bin',181 'base-en-q5_1': 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en-q5_1.bin',182 };183 184 let sizes = {185 'tiny.en': 75,186 'base.en': 142,187 188 'tiny-en-q5_1': 31,189 'base-en-q5_1': 57,190 };191 192 let url = urls[model];193 let dst = 'whisper.bin';194 let size_mb = sizes[model];195 196 model_whisper = model;197 198 document.getElementById('fetch-whisper-tiny-en').style.display = 'none';199 document.getElementById('fetch-whisper-base-en').style.display = 'none';200 201 document.getElementById('fetch-whisper-tiny-en-q5_1').style.display = 'none';202 document.getElementById('fetch-whisper-base-en-q5_1').style.display = 'none';203 204 document.getElementById('model-whisper-status').innerHTML = 'loading "' + model + '" ... ';205 206 cbProgress = function(p) {207 let el = document.getElementById('fetch-whisper-progress');208 el.innerHTML = Math.round(100*p) + '%';209 };210 211 cbCancel = function() {212 var el;213 el = document.getElementById('fetch-whisper-tiny-en'); if (el) el.style.display = 'inline-block';214 el = document.getElementById('fetch-whisper-base-en'); if (el) el.style.display = 'inline-block';215 216 el = document.getElementById('fetch-whisper-tiny-en-q5_1'); if (el) el.style.display = 'inline-block';217 el = document.getElementById('fetch-whisper-base-en-q5_1'); if (el) el.style.display = 'inline-block';218 219 el = document.getElementById('model-whisper-status'); if (el) el.innerHTML = '';220 };221 222 loadRemote(url, dst, size_mb, cbProgress, storeFS, cbCancel, printTextarea);223 }224 225 //226 // microphone227 //228 229 const kSampleRate = 16000;230 const kRestartRecording_s = 120;231 const kIntervalAudio_ms = 250; // pass the recorded audio to the C++ instance at this rate232 233 var mediaRecorder = null;234 var doRecording = false;235 var startTime = 0;236 237 window.AudioContext = window.AudioContext || window.webkitAudioContext;238 window.OfflineAudioContext = window.OfflineAudioContext || window.webkitOfflineAudioContext;239 240 function stopRecording() {241 Module.set_status("paused");242 doRecording = false;243 audio0 = null;244 audio = null;245 context = null;246 }247 248 function startRecording() {249 if (!context) {250 context = new AudioContext({251 sampleRate: kSampleRate,252 channelCount: 1,253 echoCancellation: false,254 autoGainControl: true,255 noiseSuppression: true,256 });257 }258 259 Module.set_status("");260 261 document.getElementById('start').disabled = true;262 document.getElementById('stop').disabled = false;263 264 doRecording = true;265 startTime = Date.now();266 267 var chunks = [];268 var stream = null;269 270 navigator.mediaDevices.getUserMedia({audio: true, video: false})271 .then(function(s) {272 stream = s;273 mediaRecorder = new MediaRecorder(stream);274 mediaRecorder.ondataavailable = function(e) {275 chunks.push(e.data);276 277 var blob = new Blob(chunks, { 'type' : 'audio/ogg; codecs=opus' });278 var reader = new FileReader();279 280 reader.onload = function(event) {281 var buf = new Uint8Array(reader.result);282 283 if (!context) {284 return;285 }286 context.decodeAudioData(buf.buffer, function(audioBuffer) {287 var offlineContext = new OfflineAudioContext(audioBuffer.numberOfChannels, audioBuffer.length, audioBuffer.sampleRate);288 var source = offlineContext.createBufferSource();289 source.buffer = audioBuffer;290 source.connect(offlineContext.destination);291 source.start(0);292 293 offlineContext.startRendering().then(function(renderedBuffer) {294 audio = renderedBuffer.getChannelData(0);295 296 //printTextarea('js: audio recorded, size: ' + audio.length + ', old size: ' + (audio0 == null ? 0 : audio0.length));297 298 var audioAll = new Float32Array(audio0 == null ? audio.length : audio0.length + audio.length);299 if (audio0 != null) {300 audioAll.set(audio0, 0);301 }302 audioAll.set(audio, audio0 == null ? 0 : audio0.length);303 304 if (instance) {305 Module.set_audio(instance, audioAll);306 }307 });308 }, function(e) {309 audio = null;310 });311 }312 313 reader.readAsArrayBuffer(blob);314 };315 316 mediaRecorder.onstop = function(e) {317 if (doRecording) {318 setTimeout(function() {319 startRecording();320 });321 }322 };323 324 mediaRecorder.start(kIntervalAudio_ms);325 })326 .catch(function(err) {327 printTextarea('js: error getting audio stream: ' + err);328 });329 330 var interval = setInterval(function() {331 if (!doRecording) {332 clearInterval(interval);333 mediaRecorder.stop();334 stream.getTracks().forEach(function(track) {335 track.stop();336 });337 338 document.getElementById('start').disabled = false;339 document.getElementById('stop').disabled = true;340 341 mediaRecorder = null;342 }343 344 // if audio length is more than kRestartRecording_s seconds, restart recording345 if (audio != null && audio.length > kSampleRate*kRestartRecording_s) {346 if (doRecording) {347 //printTextarea('js: restarting recording');348 349 clearInterval(interval);350 audio0 = audio;351 audio = null;352 mediaRecorder.stop();353 stream.getTracks().forEach(function(track) {354 track.stop();355 });356 }357 }358 }, 100);359 }360 361 //362 // main363 //364 365 var nLines = 0;366 var intervalUpdate = null;367 var transcribedAll = '';368 369 function onStart() {370 if (!instance) {371 instance = Module.init('whisper.bin');372 373 if (instance) {374 printTextarea("js: whisper initialized, instance: " + instance);375 }376 }377 378 if (!instance) {379 printTextarea("js: failed to initialize whisper");380 return;381 }382 383 startRecording();384 385 intervalUpdate = setInterval(function() {386 var transcribed = Module.get_transcribed();387 388 if (transcribed != null && transcribed.length > 1) {389 transcribedAll += transcribed + '<br>';390 nLines++;391 392 // if more than 10 lines, remove the first line393 if (nLines > 10) {394 var i = transcribedAll.indexOf('<br>');395 if (i > 0) {396 transcribedAll = transcribedAll.substring(i + 4);397 nLines--;398 }399 }400 }401 402 document.getElementById('state-status').innerHTML = Module.get_status();403 document.getElementById('state-transcribed').innerHTML = transcribedAll;404 }, 100);405 }406 407 function onStop() {408 stopRecording();409 }410 411 </script>412 <script type="text/javascript" src="command.js"></script>413 </body>414</html>415 