|
1 | 1 | # SEPIA Web-Audio Library |
2 | 2 |
|
3 | | -## -- Currently in BETA -- |
4 | | - |
5 | | -Modular, cross-browser library to record and process audio using audio-worklets, web-workers and script-processors (as fallback). |
6 | | -Can be used to chain different modules (worklets AND workers) to one audio pipeline. |
| 3 | +Modular, cross-browser library to record and process audio using **audio-worklets**, **web-workers** and script-processors (as fallback). |
| 4 | +Can be used to **chain different modules (worklets AND workers) to one audio pipeline**. |
| 5 | +Thanks to the worklet + worker combination most of the audio **processing can be done in the background** without messing with the main UI-thread! |
| 6 | + |
| 7 | +The main focus of this library is speech recording and processing (see: [SEPIA Client app](https://github.com/SEPIA-Framework/sepia-html-client-app)), |
| 8 | +but you can quickly add modules for many other use-cases as well (contributions welcome ^^). |
7 | 9 |
|
8 | 10 | Available modules: |
9 | 11 |
|
10 | | -- Resampler using Speex codec (WASM module) |
11 | | -- Voice-Activity-Detection (VAD) via WebRTC-VAD |
12 | | -- Custom SEPIA VAD module using Meyda to analyze bark-scale, MFCC and more |
13 | | -- Wave Encoder with lookback-buffer |
14 | | -- Porcupine Wake-Word detector |
| 12 | +- **Resampler** using Speex codec (WASM module) |
| 13 | +- **Voice-Activity-Detection** (VAD) via WebRTC-VAD |
| 14 | +- Custom SEPIA VAD module using Meyda to analyze bark-scale, **MFCC** and more |
| 15 | +- **Wave Encoder** with lookback-buffer |
| 16 | +- Porcupine **Wake-Word detector** (including: "Computer", "Jarvis", "Hey SEPIA" and more) |
| 17 | +- [SEPIA STT Server](https://github.com/SEPIA-Framework/sepia-stt-server) WebSocket module for **speech recognition** (see STT Server for demo) |
15 | 18 | - more to come ... |
| 19 | + |
| 20 | +Check out the [screenshots](screenshots) section for some test-page impressions :-). |
| 21 | + |
| 22 | +## Quick-Start - Voice Recorder |
| 23 | + |
| 24 | +<p align="center"> |
| 25 | + <img src="screenshots/voice-recorder-demo.png" alt="SEPIA Voice Recorder Demo"/> |
| 26 | +</p> |
| 27 | + |
| 28 | +Efficiently resampling audio to 16000 Hz and creating 16Bit mono samples for speech recognition was one of the primary objectives when building this library. |
| 29 | +While you can put together your own audio pipeline to do that (see below) there is a very convenient plugin available that does the job for you. |
| 30 | +In this quick-start guide you will learn the basics to use the **'SepiaVoiceRecorder'**. |
| 31 | + |
| 32 | +The first step is to import the required files and set the correct path to the modules folder. You will find more details about this step in the tutorial below. |
| 33 | +In this example the required files in 'modules' are `speex-resample-switch.js`, `wave-encoder-worker.js` and `shared/ring-buffer.min.js`: |
| 34 | +```html |
| 35 | +<script type="text/javascript" src="test/sepia-web-audio.min.js"></script> |
| 36 | +<script type="text/javascript" src="test/sepia-recorder.min.js"></script> |
| 37 | +<script> |
| 38 | + SepiaFW.webAudio.defaultProcessorOptions.moduleFolder = "test/modules"; |
| 39 | +</script> |
| 40 | +``` |
| 41 | + |
| 42 | +Note that we chose to copy the minified core library (from 'dist'), the recorder plugin and the modules folder (from 'src') to a folder named 'test'. |
| 43 | +Now we can create our recorder: |
| 44 | +```javascript |
| 45 | +//catch some core events: |
| 46 | +SepiaVoiceRecorder.onProcessorReady = console.log; //add your own handler here |
| 47 | +SepiaVoiceRecorder.onProcessorInitError = console.error; |
| 48 | + |
| 49 | +//events to process data: |
| 50 | +SepiaVoiceRecorder.onResamplerData = function(data){ |
| 51 | + //here you can process raw data, e.g.: |
| 52 | + //RMS volume: data.rms |
| 53 | + //Raw 16Bit mono buffer: data.samples[0] |
| 54 | +} |
| 55 | +SepiaVoiceRecorder.onWaveEncoderAudioData = function(waveData){ |
| 56 | + //when we get encoded WAV data we can add it as audio element to our page: |
| 57 | + SepiaVoiceRecorder.addAudioElementToPage(document.body, waveData, "audio/wav"); |
| 58 | +} |
| 59 | + |
| 60 | +//create the recorder: |
| 61 | +SepiaVoiceRecorder.create({ |
| 62 | + targetSampleRate: 16000, //16kHz is actually the voice recorder default |
| 63 | + gain: 3.0, //we can amplify the signal using the gain option |
| 64 | + recordingLimitMs: 30000, //Total recording limit ms |
| 65 | +}); |
| 66 | +``` |
| 67 | + |
| 68 | +Everything is prepared. To finally start recording you should wait for the ready event and then call: |
| 69 | +```javascript |
| 70 | +//to start: |
| 71 | +SepiaVoiceRecorder.start(); |
| 72 | + |
| 73 | +//to stop: |
| 74 | +SepiaVoiceRecorder.stop(); |
| 75 | +``` |
| 76 | + |
| 77 | +That's it :-). |
| 78 | + |
| 79 | +Of cause there are many more events, options and features available ^^. Please check out the [voice-recorder-demo.html](voice-recorder-demo.html) for a more complex recorder setup including VAD etc.. |
| 80 | + |
| 81 | + |
| 82 | +## Tutorial - Building Audio Pipelines |
| 83 | + |
| 84 | +Full code for this tutorial can be found at: [tutorial-code-page.html](tutorial-code-page.html). |
| 85 | +Please check out the extensive [modules-demo.html](modules-demo.html) and the test pages for more examples. |
| 86 | + |
| 87 | + |
| 88 | +### Part 1: Basic setup and single buffer module |
| 89 | + |
| 90 | +#### Import library and modules |
| 91 | + |
| 92 | +Copy `sepia-web-audio.js` to your project and import the library using the `<head>` section of your HTML page: |
| 93 | + |
| 94 | +```html |
| 95 | +<script type="text/javascript" src="src/sepia-web-audio.js"></script> |
| 96 | +``` |
| 97 | + |
| 98 | +Copy the audio modules you are planning to use, for part 1 of the tutorial we only need 'buffer-switch.js'. When you're done set the correct path to your modules folder: |
| 99 | + |
| 100 | +```html |
| 101 | +<script> |
| 102 | + SepiaFW.webAudio.defaultProcessorOptions.moduleFolder = "src/modules"; |
| 103 | +</script> |
| 104 | +``` |
| 105 | + |
| 106 | +Note: In this example we've used a folder called `src` for the library and modules folder but you can choose whatever you like. |
| 107 | +Note2: Currently there is no Javascript modules support yet. Feel free to create a request via the issues section if you need it ;-). |
| 108 | + |
| 109 | +#### Create the audio processor |
| 110 | + |
| 111 | +After importing the library you should see `SepiaFW.webAudio` in your scope. We have used this already in the head to set the modules folder. |
| 112 | +The `Processor` class is our main interface to handle the audio pipeline, but first we need to define the modules we want to use. |
| 113 | +Modules come in two main flavours, "switch" and "worker" with the main difference that switches are based on 'AudioWorklet' and can pass through data on a lower level (more details later). |
| 114 | + |
| 115 | +For this first example we only look at raw microphone data: |
| 116 | + |
| 117 | +```javascript |
| 118 | +var myModules = []; |
| 119 | + |
| 120 | +function bufferCallback(data){ |
| 121 | + //handle samples here using: data.samples |
| 122 | +} |
| 123 | +myModules.push({ |
| 124 | + name: 'buffer-switch', |
| 125 | + settings: { |
| 126 | + onmessage: bufferCallback, |
| 127 | + options: { |
| 128 | + processorOptions: { |
| 129 | + bufferSize: 512, //size of samples generated |
| 130 | + passThroughMode: 0, //0: none, 1: original (float32 array) |
| 131 | + } |
| 132 | + } |
| 133 | + } |
| 134 | +}); |
| 135 | +``` |
| 136 | + |
| 137 | +After the module is set up we can create the processor: |
| 138 | + |
| 139 | +```javascript |
| 140 | +var processor = new SepiaFW.webAudio.Processor({ |
| 141 | + onaudiostart: console.log, |
| 142 | + onaudioend: console.log, |
| 143 | + onrelease: console.log, |
| 144 | + onerror: console.error, |
| 145 | + modules: myModules |
| 146 | + |
| 147 | +}, function(info){ |
| 148 | + //Processor ready |
| 149 | + console.log(info); //Use 'info' to get details about source, sample-rate etc. |
| 150 | + |
| 151 | +}, function(err){ |
| 152 | + //Initialization error |
| 153 | + console.error(err); |
| 154 | +}); |
| 155 | +``` |
| 156 | + |
| 157 | +#### Start, stop and release the processor |
| 158 | + |
| 159 | +As soon as the ready event fired we can start processing with `processor.start()`, check `onaudiostart` (defined in processor options) and wait for data in `bufferCallback` (defined in module). |
| 160 | + |
| 161 | +After we're done we stop with `processor.stop()` and look out for `onaudioend`. |
| 162 | + |
| 163 | +If we don't want to restart later we can close the processor and clean up resources with `processor.release()`. |
| 164 | + |
| 165 | + |
| 166 | +### Part 2: Resample input and record raw 16Bit PCM mono audio (WAV) |
| 167 | + |
| 168 | +A very common use-case for this library is to resample microphone input and encode it as 16Bit PCM mono data (which is basically the default WAV file format). |
| 169 | +To make this happen we will replace the buffer module from earlier with a resampler and wave encoder module. |
| 170 | +If you haven't done already please copy `speex-resample-switch.js`, `wave-encoder-worker.js` and `shared/ring-buffer.min.js` to your modules folder. |
| 171 | + |
| 172 | +NOTE: Some browsers are actually able to natively resampling for us ^^. The resampler module will simply skip transformation in this case but it might be preferable to prevent native resampling to retain full control over the quality and speed. |
| 173 | +`SepiaFW.webAudio.isNativeStreamResamplingSupported` will be 'true' when the lib is imported because we can't test for the feature but set to 'false' after the first failed attempt of native resampling! |
| 174 | + |
| 175 | +We create the resampler first and we use the "switch" version (not the "worker", this is preferred if 'AudioWorklet' is supported): |
| 176 | + |
| 177 | +```javascript |
| 178 | +SepiaFW.webAudio.tryNativeStreamResampling = false; //global option (remain in control of resampling) |
| 179 | + |
| 180 | +var myModules = []; |
| 181 | +var targetSampleRate = 16000; //this is the sample-rate we want |
| 182 | +var bufferSize = 512; //size of samples generated |
| 183 | + |
| 184 | +function resamplerCallback(data){ |
| 185 | + //data will include e.g.: data.samples and data.rms (volume) |
| 186 | +} |
| 187 | +var resampler = { |
| 188 | + name: 'speex-resample-switch', |
| 189 | + settings: { |
| 190 | + onmessage: resamplerCallback, |
| 191 | + sendToModules: [], //[moduleIndex] - filled below with index of wave-encoder module |
| 192 | + options: { |
| 193 | + processorOptions: { |
| 194 | + targetSampleRate: targetSampleRate, |
| 195 | + bufferSize: bufferSize, |
| 196 | + resampleQuality: 5, //1 (low quality) - 10 (best quality) |
| 197 | + calculateRmsVolume: true, //the resampler can calculate RMS signal volume |
| 198 | + gain: 1.0, //we can amplify the signal here |
| 199 | + passThroughMode: 0 //0: none - only switch in our pipe atm |
| 200 | + } |
| 201 | + } |
| 202 | + } |
| 203 | +}; |
| 204 | +``` |
| 205 | + |
| 206 | +Next we create the wave-encoder module: |
| 207 | + |
| 208 | +```javascript |
| 209 | +function waveEncoderCallback(data){ |
| 210 | + //can be used to track capture state and get final WAV |
| 211 | + //check: data.gate, data.output.wav, data.output.buffer |
| 212 | +} |
| 213 | +var waveEncoder = { |
| 214 | + name: 'wave-encoder', |
| 215 | + type: 'worker', |
| 216 | + handle: {}, //will be updated on init. with ref. to node. |
| 217 | + settings: { |
| 218 | + onmessage: waveEncoderCallback, |
| 219 | + options: { |
| 220 | + setup: { |
| 221 | + inputSampleRate: targetSampleRate, //input of this will be ... |
| 222 | + inputSampleSize: bufferSize, //... output of resampler |
| 223 | + lookbackBufferMs: 0, //(experimental) ignore for now |
| 224 | + recordBufferLimitMs: 6000, //we can apply recording limit as milliseconds |
| 225 | + //recordBufferLimitKb: 600, //... or as kilobytes (default ~5MB) |
| 226 | + isFloat32: false //resampler gives int16 - use e.g. for buffer module |
| 227 | + } |
| 228 | + } |
| 229 | + } |
| 230 | +}; |
| 231 | +``` |
| 232 | + |
| 233 | +Now we combine both modules to our audio pipeline. To tell the wave-encoder what input to use we combine the modules like this: |
| 234 | + |
| 235 | +```javascript |
| 236 | +myModules.push(resampler); //index 1 |
| 237 | +myModules.push(waveEncoder); //index 2 |
| 238 | + |
| 239 | +//connect resampler output to wave-encoder input: |
| 240 | +resampler.settings.sendToModules.push(2); |
| 241 | +``` |
| 242 | + |
| 243 | +We create the processor the same way as before (`var processor = new SepiaFW.webAudio.Processor({...});`) and call `processor.start()` when ready. |
| 244 | +The wave-encoder will receive data now but only start capturing when we explicitly tell it to using the module message interface. |
| 245 | +The same interface is used to request the captured data after we stop processing: |
| 246 | + |
| 247 | +```javascript |
| 248 | +//start recording |
| 249 | +waveEncoder.handle.sendToModule({gate: "open"}); |
| 250 | + |
| 251 | +//wait some time then stop recording (this will trigger automatically after 'recordBufferLimitMs') |
| 252 | +waveEncoder.handle.sendToModule({gate: "close"}); |
| 253 | + |
| 254 | +//finally get the data (this is possible until processor is released) |
| 255 | +waveEncoder.handle.sendToModule({request: {get: "wave"}}); //encoded WAV |
| 256 | +waveEncoder.handle.sendToModule({request: {get: "buffer"}}); //raw int16 buffer |
| 257 | +``` |
| 258 | + |
| 259 | +Using the module interface an optimized 'waveEncoderCallback' (defined in module above) could look like this: |
| 260 | + |
| 261 | +```javascript |
| 262 | +//modified 'waveEncoderCallback': |
| 263 | +function waveEncoderCallback(data){ |
| 264 | + if (data.gate && data.gate.isOpen === false){ |
| 265 | + //stop processor |
| 266 | + processor.stop(); |
| 267 | + //get data |
| 268 | + waveEncoder.handle.sendToModule({request: {get: "wave"}}); |
| 269 | + waveEncoder.handle.sendToModule({request: {get: "buffer"}}); |
| 270 | + } |
| 271 | + if (data.output && data.output.wav){ |
| 272 | + //just for fun, add WAV to page: |
| 273 | + var targetEle = document.body; |
| 274 | + var blobType = "audio/wav"; |
| 275 | + SepiaFW.webAudio.addAudioElementToPage(targetEle, data.output.wav, blobType); |
| 276 | + } |
| 277 | +} |
| 278 | +``` |
| 279 | + |
| 280 | +... TO BE CONTINUED |
16 | 281 |
|
17 | 282 | # Resources (see LICENSE as well) |
18 | 283 |
|
|
0 commit comments