From f7e877eb0cdf32a678b53bbca682dc140821e1a0 Mon Sep 17 00:00:00 2001 From: Trek H Date: Thu, 18 Jul 2019 13:11:52 +0930 Subject: [PATCH 01/36] audio-player: initial commit --- cmd/audio-player/index.html | 19 +++++++++++++++++++ cmd/audio-player/main.js | 25 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 cmd/audio-player/index.html create mode 100644 cmd/audio-player/main.js diff --git a/cmd/audio-player/index.html b/cmd/audio-player/index.html new file mode 100644 index 00000000..2b813ed4 --- /dev/null +++ b/cmd/audio-player/index.html @@ -0,0 +1,19 @@ + + + + + Audio Player + + + + + + +
+
+ + +
+
+ + \ No newline at end of file diff --git a/cmd/audio-player/main.js b/cmd/audio-player/main.js new file mode 100644 index 00000000..92795a5f --- /dev/null +++ b/cmd/audio-player/main.js @@ -0,0 +1,25 @@ +window.onload = function () { + document.getElementById('input').addEventListener('change', getFile) +} + +function getFile(){ + const input = event.target + if ('files' in input && input.files.length > 0) { + placeFileContent(document.getElementById('content'), input.files[0]) + } +} + +function placeFileContent(target, file) { + readFileContent(file).then(content => { + target.value = content + }).catch(error => console.log(error)) +} + +function readFileContent(file) { + const reader = new FileReader() + return new Promise((resolve, reject) => { + reader.onload = event => resolve(event.target.result) + reader.onerror = error => reject(error) + reader.readAsText(file) + }) +} From 99acedb3431f2d1f0614c4ce4d3b4e4f68760943 Mon Sep 17 00:00:00 2001 From: Trek H Date: Thu, 18 Jul 2019 16:04:50 +0930 Subject: [PATCH 02/36] audio-player: playing pcm in browser --- cmd/audio-player/main.js | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/cmd/audio-player/main.js b/cmd/audio-player/main.js index 92795a5f..174b9059 100644 --- a/cmd/audio-player/main.js +++ b/cmd/audio-player/main.js @@ -1,25 +1,24 @@ window.onload = function () { - document.getElementById('input').addEventListener('change', getFile) + document.getElementById('input').addEventListener('change', playFile) } -function getFile(){ - const input = event.target - if ('files' in input && input.files.length > 0) { - placeFileContent(document.getElementById('content'), input.files[0]) - } +function playFile() { + const input = event.target.files[0] + + const reader = new FileReader() + reader.onload = event => playData(event.target.result) // desired file content + reader.onerror = error => reject(error) + reader.readAsArrayBuffer(input) // you could also read images and other binaries } -function placeFileContent(target, file) { - readFileContent(file).then(content => { - target.value = content - }).catch(error => console.log(error)) -} - -function readFileContent(file) { - const reader = new FileReader() - return new Promise((resolve, reject) => { - reader.onload = event => resolve(event.target.result) - reader.onerror = error => reject(error) - reader.readAsText(file) - }) -} +function playData(array){ + var data = new Uint8Array(array) + console.log("playing file") + var player = new PCMPlayer({ + encoding: '16bitInt', + channels: 1, + sampleRate: 48000, + flushingTime: 2000 + }); + player.feed(data) +} \ No newline at end of file From f0b198b61277956fe93d8ee8c7ec5e4e917d9eb0 Mon Sep 17 00:00:00 2001 From: Trek H Date: Thu, 18 Jul 2019 16:12:31 +0930 Subject: [PATCH 03/36] audio-player: removed unused elements --- cmd/audio-player/index.html | 1 - cmd/audio-player/main.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/audio-player/index.html b/cmd/audio-player/index.html index 2b813ed4..3e03e55e 100644 --- a/cmd/audio-player/index.html +++ b/cmd/audio-player/index.html @@ -12,7 +12,6 @@
-
diff --git a/cmd/audio-player/main.js b/cmd/audio-player/main.js index 174b9059..45299078 100644 --- a/cmd/audio-player/main.js +++ b/cmd/audio-player/main.js @@ -6,9 +6,9 @@ function playFile() { const input = event.target.files[0] const reader = new FileReader() - reader.onload = event => playData(event.target.result) // desired file content + reader.onload = event => playData(event.target.result) reader.onerror = error => reject(error) - reader.readAsArrayBuffer(input) // you could also read images and other binaries + reader.readAsArrayBuffer(input) } function playData(array){ From 1842319957532f6d6b6c3cc603c0a22205f1db9a Mon Sep 17 00:00:00 2001 From: Trek H Date: Tue, 23 Jul 2019 14:28:43 +0930 Subject: [PATCH 04/36] audio-player: added go wasm for access to go adpcm package --- cmd/audio-player/favicon.ico | Bin 0 -> 15406 bytes cmd/audio-player/index.html | 33 +- cmd/audio-player/main.go | 27 ++ cmd/audio-player/main.js | 37 ++- cmd/audio-player/pcm-player.min.js | 1 + cmd/audio-player/server.go | 32 ++ cmd/audio-player/wasm_exec.js | 465 +++++++++++++++++++++++++++++ go.mod | 1 + 8 files changed, 581 insertions(+), 15 deletions(-) create mode 100644 cmd/audio-player/favicon.ico create mode 100644 cmd/audio-player/main.go create mode 100644 cmd/audio-player/pcm-player.min.js create mode 100644 cmd/audio-player/server.go create mode 100644 cmd/audio-player/wasm_exec.js diff --git a/cmd/audio-player/favicon.ico b/cmd/audio-player/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..465b02c257d3a5388ce1a7744355834fbd37fa82 GIT binary patch literal 15406 zcmeHO32>9w5f(`rkPoo2&0#KI_`sKBUAAobKJh78zp*UIwk+SqMihrYN+u3zVIXPK z5E?oyB?U5cI-zMMkf8xmAS6Ii8X%;!C8VSrA($)J@olu-C+n7sEL)b%6wU1Xzx4m_ z?f3TW+ugVOM59fiJxTNMpyAbzR{s=@#--6{ettvWLp^D<9rzoAp?w}qqn+@f(L(VZ zY=SL@e#gtOA4yJVQi)*B-V(mo1xe!U%MT+eZss{j(!3p&`H_SLtuiM(U&L|iDTpZWS99@AZ$On5m%1_JWHS zV&Kkg4ieXbZnv{x*RO)GkExn|gltzRV!42b<9?a&-n=#t)aobz7@n?Q4MS{nc)npC zoIA|`Wv-XL{maiSerQ|L@BQy1;BTKq!r)*u3=Bj;b)J3gjj>Hc`_7I?lra+?JczQu zzj*mP%psSJZC3!{=HLO9f;{h=Oen`nk#6FGWBbR5os%i?;6?x2c zva|)iSLQD{Sd+ix=vb&c)BoLytdMOD(u}6njlwV@7b{q%D5*GZS$e=BA;+yt7(=&P z?lxqsM%yqTj`MC;(K#D^>GgFq@1cGSlxKzQ(_vVp z6fIgNnENI6HSWCicoBm%J!Eroa`IDE1&p>L7JX3v%uatX+IHetuiLE+#Zi(J|Ffg* zV=V1Z9|r465?f0Iz5`?FXDn^yX$#-f`7T{QLKYqEsVgjtcSCZwgWNRjh`-hKW$Rjz z8+OpZe=1OM9LE?oMz04dQ95@0(X?0h2}ZS@SqFyxaTh5_T6qsGs-ycCIk zf@#y|82e1)d*bW+`l8|2n?tP1fcTN(*qQogC2ymdGThEvBwK^$wC z2O^J+tE1rTX)ZjY^s~+%{oL^n6K%#DIzA7(o;w`?C2@41V%^|{mc?-9GZysrFoF2n zwRMZeo&WhWLGY7$e;f}Rx_)5kWlG`zXYlXrWI|^LThCV~D`)Z7@iV_Z=I@;OHNO@U% zkH~xXwg}t@80Z?mA|eze_m?`|~w^#B(t zvwieDjQy+m{gD4N_0wXWD@ar3f-K$NZjz?X0Wr^IM7#+WNy>sFx;%k7HfMZS zNeyUs1}{qo<%MYjiRYnD#0Dr6!C_s;R13;Kg) zf;pF!MXaK>Hrf+9{^UwgOP+3&q~=#-1s^C8%)jqQS>U)Hpsro5E@Ez3(NGYA?4}s= z{(rtFc@j*~(khWgnkT_mH6Jo(t5=u$)|Frx#N%Nb0YHMqM zw6causuprSB6%(??qO|}wtuHS$9(D_=0vVGE=ycvHijpfWm(Z!SNIF&XL~hcc2cg$ zPC2MC_8Kfr@cl-S&#jYZMrm}3_?oIb#%7!&;_D_#S4KLQ8hxVTXPqT}stwn4i!75b zNm+EB=$W1J7%5MOzbC@=pva4UU8Sn@XcWh<7joQ*Uz_Z*H^>2HElUqL)l`umuE=Md zB)R=ZMfO^pYOKZGuA>sNIPYjlPy5ZowbFGCJ|@GkewYTKI1j7Xp6d4 z6&ag`@}Z`Cts$Ny-|Ggll<7z_kUShjGoUO9>uIrA6CH!h&`-A`MSpUr(ruQYYxZc`ad#Tu^*tZR&b zbAL~S&Q2B#V7_KxfZH|kU z;A!&RBRdpv@Xg#hhYUj9e!LMhV^c)!)0!a zU3cH#7Xb&~TL@)|2Hha_it60i@Y?!dICeM@Zr@_V_xBj^+J*?MJ(!Gh82n2RKhHV% z`y=4cz6BcmBrhgQ@q`U)m~i442kza)HK)y$c6LS~{_shO|M2?@p(fuOo^J|-gZntp z{#~3!j(4)+9~g*)bDu}T{=Gc-&sS{d>1Jrodzjmd$%=ny4Ktt*YlGyRiaBoGI+?8a zb@*&uALDnt53?n68UIlH=7O=A8?;D#;SW1_aPgm9 z97hb+c87GzKKxFOpSAHP*}n+cSbI$LfHgIX;hjAk`1-4OO-$6$G4l9RB@0HK&4jZPySFUFvS0wabPShN%DkM6+!Uc+dPctS?~+o$JmVjQ3i?}Up%+W z#u z+w$_v--u&7zZAsm#uC2sLN9Puhz@blUI~yHWu?d?;!q1ss1t`P4)XgRh1jitjJj+&j>k+ z|L>Ww^V)XKy;{WcxT`GStZQqlb!n@ub*T}>HsiVX8;`&okpD~c;@SKIN+CZ2i`bg; zBG1)|<~GVww&Qc3X=}Z37DeG7J0E`@OTd8=_#Y2!$T$E1 literal 0 HcmV?d00001 diff --git a/cmd/audio-player/index.html b/cmd/audio-player/index.html index 3e03e55e..12487c53 100644 --- a/cmd/audio-player/index.html +++ b/cmd/audio-player/index.html @@ -1,18 +1,23 @@ - - - Audio Player - - - - - - -
-
- -
+ + + + Audio Player + + + + + + + + +
+
+
- +
+ + \ No newline at end of file diff --git a/cmd/audio-player/main.go b/cmd/audio-player/main.go new file mode 100644 index 00000000..92d02c9a --- /dev/null +++ b/cmd/audio-player/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "syscall/js" + + "bitbucket.org/ausocean/av/codec/adpcm" +) + +func main() { + c := make(chan struct{}, 0) + + println("WASM Go Initialized") + // register functions + registerCallbacks() + + <-c +} + +func registerCallbacks() { + js.Global().Set("decode", js.FuncOf(decode)) +} + +func decode(this js.Value, args []js.Value) interface{} { + fmt.Println("calculating") + return adpcm.EncBytes(args[0].Int()) +} diff --git a/cmd/audio-player/main.js b/cmd/audio-player/main.js index 45299078..c18173fd 100644 --- a/cmd/audio-player/main.js +++ b/cmd/audio-player/main.js @@ -1,8 +1,10 @@ window.onload = function () { + initWasm() document.getElementById('input').addEventListener('change', playFile) } function playFile() { + console.log(decode(48000)) const input = event.target.files[0] const reader = new FileReader() @@ -11,7 +13,7 @@ function playFile() { reader.readAsArrayBuffer(input) } -function playData(array){ +function playData(array) { var data = new Uint8Array(array) console.log("playing file") var player = new PCMPlayer({ @@ -21,4 +23,37 @@ function playData(array){ flushingTime: 2000 }); player.feed(data) +} + +function initWasm() { + if (!WebAssembly.instantiateStreaming) { + // polyfill + WebAssembly.instantiateStreaming = async (resp, importObject) => { + const source = await (await resp).arrayBuffer() + return await WebAssembly.instantiate(source, importObject) + } + } + + const go = new Go() + let mod, inst + // memoryBytes is an Uint8Array pointing to the webassembly linear memory. + let memoryBytes; + console.log("Initializing wasm...") + WebAssembly.instantiateStreaming( + fetch('lib.wasm'), go.importObject).then( + result => { + mod = result.module + inst = result.instance + memoryBytes = new Uint8Array(inst.exports.mem.buffer) + console.log("Initialization complete.") + run() + } + ) + + async function run() { + await go.run(inst) + inst = await WebAssembly.instantiate(mod, go.importObject) // reset instance + } + + } \ No newline at end of file diff --git a/cmd/audio-player/pcm-player.min.js b/cmd/audio-player/pcm-player.min.js new file mode 100644 index 00000000..42c33624 --- /dev/null +++ b/cmd/audio-player/pcm-player.min.js @@ -0,0 +1 @@ +function PCMPlayer(t){this.init(t)}PCMPlayer.prototype.init=function(t){this.option=Object.assign({},{encoding:"16bitInt",channels:1,sampleRate:8e3,flushingTime:1e3},t),this.samples=new Float32Array,this.flush=this.flush.bind(this),this.interval=setInterval(this.flush,this.option.flushingTime),this.maxValue=this.getMaxValue(),this.typedArray=this.getTypedArray(),this.createContext()},PCMPlayer.prototype.getMaxValue=function(){var t={"8bitInt":128,"16bitInt":32768,"32bitInt":2147483648,"32bitFloat":1};return t[this.option.encoding]?t[this.option.encoding]:t["16bitInt"]},PCMPlayer.prototype.getTypedArray=function(){var t={"8bitInt":Int8Array,"16bitInt":Int16Array,"32bitInt":Int32Array,"32bitFloat":Float32Array};return t[this.option.encoding]?t[this.option.encoding]:t["16bitInt"]},PCMPlayer.prototype.createContext=function(){this.audioCtx=new(window.AudioContext||window.webkitAudioContext),this.gainNode=this.audioCtx.createGain(),this.gainNode.gain.value=1,this.gainNode.connect(this.audioCtx.destination),this.startTime=this.audioCtx.currentTime},PCMPlayer.prototype.isTypedArray=function(t){return t.byteLength&&t.buffer&&t.buffer.constructor==ArrayBuffer},PCMPlayer.prototype.feed=function(t){if(this.isTypedArray(t)){t=this.getFormatedValue(t);var e=new Float32Array(this.samples.length+t.length);e.set(this.samples,0),e.set(t,this.samples.length),this.samples=e}},PCMPlayer.prototype.getFormatedValue=function(t){t=new this.typedArray(t.buffer);var e,i=new Float32Array(t.length);for(e=0;e { + if (typeof global !== "undefined") { + // global already exists + } else if (typeof window !== "undefined") { + window.global = window; + } else if (typeof self !== "undefined") { + self.global = self; + } else { + throw new Error("cannot export Go (neither global, window nor self is defined)"); + } + + // Map web browser API and Node.js API to a single common API (preferring web standards over Node.js API). + const isNodeJS = global.process && global.process.title === "node"; + if (isNodeJS) { + global.require = require; + global.fs = require("fs"); + + const nodeCrypto = require("crypto"); + global.crypto = { + getRandomValues(b) { + nodeCrypto.randomFillSync(b); + }, + }; + + global.performance = { + now() { + const [sec, nsec] = process.hrtime(); + return sec * 1000 + nsec / 1000000; + }, + }; + + const util = require("util"); + global.TextEncoder = util.TextEncoder; + global.TextDecoder = util.TextDecoder; + } else { + let outputBuf = ""; + global.fs = { + constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused + writeSync(fd, buf) { + outputBuf += decoder.decode(buf); + const nl = outputBuf.lastIndexOf("\n"); + if (nl != -1) { + console.log(outputBuf.substr(0, nl)); + outputBuf = outputBuf.substr(nl + 1); + } + return buf.length; + }, + write(fd, buf, offset, length, position, callback) { + if (offset !== 0 || length !== buf.length || position !== null) { + throw new Error("not implemented"); + } + const n = this.writeSync(fd, buf); + callback(null, n); + }, + open(path, flags, mode, callback) { + const err = new Error("not implemented"); + err.code = "ENOSYS"; + callback(err); + }, + read(fd, buffer, offset, length, position, callback) { + const err = new Error("not implemented"); + err.code = "ENOSYS"; + callback(err); + }, + fsync(fd, callback) { + callback(null); + }, + }; + } + + const encoder = new TextEncoder("utf-8"); + const decoder = new TextDecoder("utf-8"); + + global.Go = class { + constructor() { + this.argv = ["js"]; + this.env = {}; + this.exit = (code) => { + if (code !== 0) { + console.warn("exit code:", code); + } + }; + this._exitPromise = new Promise((resolve) => { + this._resolveExitPromise = resolve; + }); + this._pendingEvent = null; + this._scheduledTimeouts = new Map(); + this._nextCallbackTimeoutID = 1; + + const mem = () => { + // The buffer may change when requesting more memory. + return new DataView(this._inst.exports.mem.buffer); + } + + const setInt64 = (addr, v) => { + mem().setUint32(addr + 0, v, true); + mem().setUint32(addr + 4, Math.floor(v / 4294967296), true); + } + + const getInt64 = (addr) => { + const low = mem().getUint32(addr + 0, true); + const high = mem().getInt32(addr + 4, true); + return low + high * 4294967296; + } + + const loadValue = (addr) => { + const f = mem().getFloat64(addr, true); + if (f === 0) { + return undefined; + } + if (!isNaN(f)) { + return f; + } + + const id = mem().getUint32(addr, true); + return this._values[id]; + } + + const storeValue = (addr, v) => { + const nanHead = 0x7FF80000; + + if (typeof v === "number") { + if (isNaN(v)) { + mem().setUint32(addr + 4, nanHead, true); + mem().setUint32(addr, 0, true); + return; + } + if (v === 0) { + mem().setUint32(addr + 4, nanHead, true); + mem().setUint32(addr, 1, true); + return; + } + mem().setFloat64(addr, v, true); + return; + } + + switch (v) { + case undefined: + mem().setFloat64(addr, 0, true); + return; + case null: + mem().setUint32(addr + 4, nanHead, true); + mem().setUint32(addr, 2, true); + return; + case true: + mem().setUint32(addr + 4, nanHead, true); + mem().setUint32(addr, 3, true); + return; + case false: + mem().setUint32(addr + 4, nanHead, true); + mem().setUint32(addr, 4, true); + return; + } + + let ref = this._refs.get(v); + if (ref === undefined) { + ref = this._values.length; + this._values.push(v); + this._refs.set(v, ref); + } + let typeFlag = 0; + switch (typeof v) { + case "string": + typeFlag = 1; + break; + case "symbol": + typeFlag = 2; + break; + case "function": + typeFlag = 3; + break; + } + mem().setUint32(addr + 4, nanHead | typeFlag, true); + mem().setUint32(addr, ref, true); + } + + const loadSlice = (addr) => { + const array = getInt64(addr + 0); + const len = getInt64(addr + 8); + return new Uint8Array(this._inst.exports.mem.buffer, array, len); + } + + const loadSliceOfValues = (addr) => { + const array = getInt64(addr + 0); + const len = getInt64(addr + 8); + const a = new Array(len); + for (let i = 0; i < len; i++) { + a[i] = loadValue(array + i * 8); + } + return a; + } + + const loadString = (addr) => { + const saddr = getInt64(addr + 0); + const len = getInt64(addr + 8); + return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); + } + + const timeOrigin = Date.now() - performance.now(); + this.importObject = { + go: { + // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) + // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported + // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). + // This changes the SP, thus we have to update the SP used by the imported function. + + // func wasmExit(code int32) + "runtime.wasmExit": (sp) => { + const code = mem().getInt32(sp + 8, true); + this.exited = true; + delete this._inst; + delete this._values; + delete this._refs; + this.exit(code); + }, + + // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) + "runtime.wasmWrite": (sp) => { + const fd = getInt64(sp + 8); + const p = getInt64(sp + 16); + const n = mem().getInt32(sp + 24, true); + fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); + }, + + // func nanotime() int64 + "runtime.nanotime": (sp) => { + setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); + }, + + // func walltime() (sec int64, nsec int32) + "runtime.walltime": (sp) => { + const msec = (new Date).getTime(); + setInt64(sp + 8, msec / 1000); + mem().setInt32(sp + 16, (msec % 1000) * 1000000, true); + }, + + // func scheduleTimeoutEvent(delay int64) int32 + "runtime.scheduleTimeoutEvent": (sp) => { + const id = this._nextCallbackTimeoutID; + this._nextCallbackTimeoutID++; + this._scheduledTimeouts.set(id, setTimeout( + () => { this._resume(); }, + getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early + )); + mem().setInt32(sp + 16, id, true); + }, + + // func clearTimeoutEvent(id int32) + "runtime.clearTimeoutEvent": (sp) => { + const id = mem().getInt32(sp + 8, true); + clearTimeout(this._scheduledTimeouts.get(id)); + this._scheduledTimeouts.delete(id); + }, + + // func getRandomData(r []byte) + "runtime.getRandomData": (sp) => { + crypto.getRandomValues(loadSlice(sp + 8)); + }, + + // func stringVal(value string) ref + "syscall/js.stringVal": (sp) => { + storeValue(sp + 24, loadString(sp + 8)); + }, + + // func valueGet(v ref, p string) ref + "syscall/js.valueGet": (sp) => { + const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); + sp = this._inst.exports.getsp(); // see comment above + storeValue(sp + 32, result); + }, + + // func valueSet(v ref, p string, x ref) + "syscall/js.valueSet": (sp) => { + Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); + }, + + // func valueIndex(v ref, i int) ref + "syscall/js.valueIndex": (sp) => { + storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); + }, + + // valueSetIndex(v ref, i int, x ref) + "syscall/js.valueSetIndex": (sp) => { + Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); + }, + + // func valueCall(v ref, m string, args []ref) (ref, bool) + "syscall/js.valueCall": (sp) => { + try { + const v = loadValue(sp + 8); + const m = Reflect.get(v, loadString(sp + 16)); + const args = loadSliceOfValues(sp + 32); + const result = Reflect.apply(m, v, args); + sp = this._inst.exports.getsp(); // see comment above + storeValue(sp + 56, result); + mem().setUint8(sp + 64, 1); + } catch (err) { + storeValue(sp + 56, err); + mem().setUint8(sp + 64, 0); + } + }, + + // func valueInvoke(v ref, args []ref) (ref, bool) + "syscall/js.valueInvoke": (sp) => { + try { + const v = loadValue(sp + 8); + const args = loadSliceOfValues(sp + 16); + const result = Reflect.apply(v, undefined, args); + sp = this._inst.exports.getsp(); // see comment above + storeValue(sp + 40, result); + mem().setUint8(sp + 48, 1); + } catch (err) { + storeValue(sp + 40, err); + mem().setUint8(sp + 48, 0); + } + }, + + // func valueNew(v ref, args []ref) (ref, bool) + "syscall/js.valueNew": (sp) => { + try { + const v = loadValue(sp + 8); + const args = loadSliceOfValues(sp + 16); + const result = Reflect.construct(v, args); + sp = this._inst.exports.getsp(); // see comment above + storeValue(sp + 40, result); + mem().setUint8(sp + 48, 1); + } catch (err) { + storeValue(sp + 40, err); + mem().setUint8(sp + 48, 0); + } + }, + + // func valueLength(v ref) int + "syscall/js.valueLength": (sp) => { + setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); + }, + + // valuePrepareString(v ref) (ref, int) + "syscall/js.valuePrepareString": (sp) => { + const str = encoder.encode(String(loadValue(sp + 8))); + storeValue(sp + 16, str); + setInt64(sp + 24, str.length); + }, + + // valueLoadString(v ref, b []byte) + "syscall/js.valueLoadString": (sp) => { + const str = loadValue(sp + 8); + loadSlice(sp + 16).set(str); + }, + + // func valueInstanceOf(v ref, t ref) bool + "syscall/js.valueInstanceOf": (sp) => { + mem().setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16)); + }, + + "debug": (value) => { + console.log(value); + }, + } + }; + } + + async run(instance) { + this._inst = instance; + this._values = [ // TODO: garbage collection + NaN, + 0, + null, + true, + false, + global, + this._inst.exports.mem, + this, + ]; + this._refs = new Map(); + this.exited = false; + + const mem = new DataView(this._inst.exports.mem.buffer) + + // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. + let offset = 4096; + + const strPtr = (str) => { + let ptr = offset; + new Uint8Array(mem.buffer, offset, str.length + 1).set(encoder.encode(str + "\0")); + offset += str.length + (8 - (str.length % 8)); + return ptr; + }; + + const argc = this.argv.length; + + const argvPtrs = []; + this.argv.forEach((arg) => { + argvPtrs.push(strPtr(arg)); + }); + + const keys = Object.keys(this.env).sort(); + argvPtrs.push(keys.length); + keys.forEach((key) => { + argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); + }); + + const argv = offset; + argvPtrs.forEach((ptr) => { + mem.setUint32(offset, ptr, true); + mem.setUint32(offset + 4, 0, true); + offset += 8; + }); + + this._inst.exports.run(argc, argv); + if (this.exited) { + this._resolveExitPromise(); + } + await this._exitPromise; + } + + _resume() { + if (this.exited) { + throw new Error("Go program has already exited"); + } + this._inst.exports.resume(); + if (this.exited) { + this._resolveExitPromise(); + } + } + + _makeFuncWrapper(id) { + const go = this; + return function () { + const event = { id: id, this: this, args: arguments }; + go._pendingEvent = event; + go._resume(); + return event.result; + }; + } + } + + if (isNodeJS) { + if (process.argv.length < 3) { + process.stderr.write("usage: go_js_wasm_exec [wasm binary] [arguments]\n"); + process.exit(1); + } + + const go = new Go(); + go.argv = process.argv.slice(2); + go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env); + go.exit = process.exit; + WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { + process.on("exit", (code) => { // Node.js exits if no event handler is pending + if (code === 0 && !go.exited) { + // deadlock, make Go print error and stack traces + go._pendingEvent = { id: 0 }; + go._resume(); + } + }); + return go.run(result.instance); + }).catch((err) => { + throw err; + }); + } +})(); diff --git a/go.mod b/go.mod index c3d766c5..00293cdb 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480 github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884 github.com/mewkiz/flac v1.0.5 + github.com/pkg/errors v0.8.1 github.com/sergi/go-diff v1.0.0 // indirect github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect From e23c1aac7eef8abbf8b277be5760cbfa420094df Mon Sep 17 00:00:00 2001 From: Trek H Date: Tue, 23 Jul 2019 17:57:03 +0930 Subject: [PATCH 05/36] audio-player: wasm decoding merged with pcm player --- cmd/audio-player/main.go | 72 +++++++++++++++++++---- cmd/audio-player/main.js | 124 +++++++++++++++++++++++---------------- 2 files changed, 132 insertions(+), 64 deletions(-) diff --git a/cmd/audio-player/main.go b/cmd/audio-player/main.go index 92d02c9a..9f613320 100644 --- a/cmd/audio-player/main.go +++ b/cmd/audio-player/main.go @@ -1,27 +1,73 @@ package main import ( - "fmt" + "bytes" + "reflect" "syscall/js" + "unsafe" "bitbucket.org/ausocean/av/codec/adpcm" ) func main() { - c := make(chan struct{}, 0) - - println("WASM Go Initialized") - // register functions - registerCallbacks() - - <-c + dec := NewDecoder() + dec.Start() } -func registerCallbacks() { - js.Global().Set("decode", js.FuncOf(decode)) +// Decoder is a client side adpcm decoder +type Decoder struct { + inBuf []uint8 + onImgLoadCb, initMemCb js.Func + console js.Value + done chan struct{} } -func decode(this js.Value, args []js.Value) interface{} { - fmt.Println("calculating") - return adpcm.EncBytes(args[0].Int()) +// NewDecoder returns a new instance of decoder +func NewDecoder() *Decoder { + return &Decoder{ + console: js.Global().Get("console"), + done: make(chan struct{}), + } +} + +func (s *Decoder) setupOnImgLoadCb() { + s.onImgLoadCb = js.FuncOf(func(this js.Value, args []js.Value) interface{} { + // reader := bytes.NewReader(s.inBuf) + + //DECODING HAPPENS HERE + + decoded := bytes.NewBuffer(make([]byte, 0, len(s.inBuf)*4)) + dec := adpcm.NewDecoder(decoded) + dec.Write(s.inBuf) + + s.inBuf = decoded.Bytes() + + return nil + }) +} + +func (s *Decoder) setupInitMemCb() { + // The length of the data array buffer is passed. + // Then the buf slice is initialized to that length. + // A pointer to that slice is passed back to the browser. + s.initMemCb = js.FuncOf(func(this js.Value, i []js.Value) interface{} { + length := i[0].Int() + s.console.Call("log", "length:", length) + s.inBuf = make([]uint8, length) + hdr := (*reflect.SliceHeader)(unsafe.Pointer(&s.inBuf)) + ptr := uintptr(unsafe.Pointer(hdr.Data)) + s.console.Call("log", "ptr:", ptr) + js.Global().Call("gotMem", ptr) + return nil + }) +} + +// Start sets up all the callbacks and waits for the close signal +// to be sent from the browser. +func (s *Decoder) Start() { + s.setupInitMemCb() + js.Global().Set("initMem", s.initMemCb) + s.setupOnImgLoadCb() + js.Global().Set("loadData", s.onImgLoadCb) + <-s.done } diff --git a/cmd/audio-player/main.js b/cmd/audio-player/main.js index c18173fd..fef42805 100644 --- a/cmd/audio-player/main.js +++ b/cmd/audio-player/main.js @@ -1,59 +1,81 @@ +const go = new Go() +// memoryBytes is an Uint8Array pointing to the webassembly linear memory. +let memoryBytes; +let bytes; +let mod, inst +console.log("Initializing wasm...") +WebAssembly.instantiateStreaming( + fetch('lib.wasm'), go.importObject).then( + result => { + mod = result.module + inst = result.instance + memoryBytes = new Uint8Array(inst.exports.mem.buffer) + console.log("Initialization complete.") + run() + } +) + +async function run() { + await go.run(inst) + inst = await WebAssembly.instantiate(mod, go.importObject) // reset instance +} + +// gotMem sets the webassembly linear memory with the data buffer result +// at the slice header pointer passed from Go. +function gotMem(pointer) { + memoryBytes.set(bytes, pointer); + // Now the data can be loaded from the slice. + loadData(); +} + +// // displayImage takes the pointer to the target image in the wasm linear memory +// // and its length. Gets the resulting byte slice and creates an image blob. +// function displayImage(pointer, length) { +// let resultBytes = memoryBytes.slice(pointer, pointer + length); +// let blob = new Blob([resultBytes], { +// 'type': imageType +// }); +// document.getElementById('targetImg').src = URL.createObjectURL(blob); +// } + +// document.getElementById('input').addEventListener('change', function () { +// let reader = new FileReader(); + +// reader.onload = (ev) => { +// bytes = new Uint8Array(ev.target.result); +// initMem(bytes.length); +// let blob = new Blob([bytes], { +// 'type': imageType +// }); +// document.getElementById("sourceImg").src = URL.createObjectURL(blob); +// }; +// imageType = this.files[0].type; +// reader.readAsArrayBuffer(this.files[0]); +// }); window.onload = function () { - initWasm() - document.getElementById('input').addEventListener('change', playFile) -} -function playFile() { - console.log(decode(48000)) - const input = event.target.files[0] + document.getElementById('input').addEventListener('change', function () { + const input = event.target.files[0] + const reader = new FileReader() - const reader = new FileReader() - reader.onload = event => playData(event.target.result) - reader.onerror = error => reject(error) - reader.readAsArrayBuffer(input) -} + reader.onload = event => { + bytes = new Uint8Array(event.target.result) -function playData(array) { - var data = new Uint8Array(array) - console.log("playing file") - var player = new PCMPlayer({ - encoding: '16bitInt', - channels: 1, - sampleRate: 48000, - flushingTime: 2000 - }); - player.feed(data) -} + initMem(bytes.length) -function initWasm() { - if (!WebAssembly.instantiateStreaming) { - // polyfill - WebAssembly.instantiateStreaming = async (resp, importObject) => { - const source = await (await resp).arrayBuffer() - return await WebAssembly.instantiate(source, importObject) + // bytes should be decoded at this point, if not try memoryBytes.set(bytes, pointer) + + console.log("playing file") + var player = new PCMPlayer({ + encoding: '16bitInt', + channels: 1, + sampleRate: 48000, + flushingTime: 2000 + }); + player.feed(bytes) } - } - - const go = new Go() - let mod, inst - // memoryBytes is an Uint8Array pointing to the webassembly linear memory. - let memoryBytes; - console.log("Initializing wasm...") - WebAssembly.instantiateStreaming( - fetch('lib.wasm'), go.importObject).then( - result => { - mod = result.module - inst = result.instance - memoryBytes = new Uint8Array(inst.exports.mem.buffer) - console.log("Initialization complete.") - run() - } - ) - - async function run() { - await go.run(inst) - inst = await WebAssembly.instantiate(mod, go.importObject) // reset instance - } - + reader.onerror = error => reject(error) + reader.readAsArrayBuffer(input) + }) } \ No newline at end of file From 908b8e6e564195529d8083481352ac0eaf1ffa9e Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 24 Jul 2019 13:33:17 +0930 Subject: [PATCH 06/36] audio-player: wasm decoding test --- cmd/audio-player/main.go | 16 ++++++++++++---- cmd/audio-player/main.js | 9 +++++++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/cmd/audio-player/main.go b/cmd/audio-player/main.go index 9f613320..970cfccb 100644 --- a/cmd/audio-player/main.go +++ b/cmd/audio-player/main.go @@ -16,10 +16,10 @@ func main() { // Decoder is a client side adpcm decoder type Decoder struct { - inBuf []uint8 - onImgLoadCb, initMemCb js.Func - console js.Value - done chan struct{} + inBuf []uint8 + onImgLoadCb, initMemCb, getDecLenCb js.Func + console js.Value + done chan struct{} } // NewDecoder returns a new instance of decoder @@ -62,6 +62,12 @@ func (s *Decoder) setupInitMemCb() { }) } +func (s *Decoder) setupGetDecLenCb() { + s.getDecLenCb = js.FuncOf(func(this js.Value, i []js.Value) interface{} { + return len(s.inBuf) + }) +} + // Start sets up all the callbacks and waits for the close signal // to be sent from the browser. func (s *Decoder) Start() { @@ -69,5 +75,7 @@ func (s *Decoder) Start() { js.Global().Set("initMem", s.initMemCb) s.setupOnImgLoadCb() js.Global().Set("loadData", s.onImgLoadCb) + s.setupGetDecLenCb() + js.Global().Set("getDecLen", s.getDecLenCb) <-s.done } diff --git a/cmd/audio-player/main.js b/cmd/audio-player/main.js index fef42805..962ef19a 100644 --- a/cmd/audio-player/main.js +++ b/cmd/audio-player/main.js @@ -20,9 +20,12 @@ async function run() { inst = await WebAssembly.instantiate(mod, go.importObject) // reset instance } +let memPointer + // gotMem sets the webassembly linear memory with the data buffer result // at the slice header pointer passed from Go. function gotMem(pointer) { + memPointer = pointer memoryBytes.set(bytes, pointer); // Now the data can be loaded from the slice. loadData(); @@ -63,7 +66,9 @@ window.onload = function () { initMem(bytes.length) - // bytes should be decoded at this point, if not try memoryBytes.set(bytes, pointer) + let resultBuffer = new ArrayBuffer(getDecLen()); + let resultBytes = new Uint8Array(resultBuffer) + resultBytes.set(memoryBytes.slice(memPointer, memPointer + getDecLen())) console.log("playing file") var player = new PCMPlayer({ @@ -72,7 +77,7 @@ window.onload = function () { sampleRate: 48000, flushingTime: 2000 }); - player.feed(bytes) + player.feed(resultBytes) } reader.onerror = error => reject(error) reader.readAsArrayBuffer(input) From 8a3eeec59d54019d053f9524cfa1fc514ca500b2 Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 24 Jul 2019 17:12:24 +0930 Subject: [PATCH 07/36] audio-player: added js adpcm decode func --- cmd/audio-player/index.html | 1 - cmd/audio-player/main.js | 65 +-------------------------------- cmd/audio-player/server.go | 32 +++++------------ codec/adpcm/adpcm.go | 2 +- codec/adpcm/adpcm.js | 72 +++++++++++++++++++++++++++++++++++++ 5 files changed, 83 insertions(+), 89 deletions(-) create mode 100644 codec/adpcm/adpcm.js diff --git a/cmd/audio-player/index.html b/cmd/audio-player/index.html index 12487c53..d8e7aa23 100644 --- a/cmd/audio-player/index.html +++ b/cmd/audio-player/index.html @@ -5,7 +5,6 @@ Audio Player - diff --git a/cmd/audio-player/main.js b/cmd/audio-player/main.js index 962ef19a..c81deb05 100644 --- a/cmd/audio-player/main.js +++ b/cmd/audio-player/main.js @@ -1,60 +1,3 @@ -const go = new Go() -// memoryBytes is an Uint8Array pointing to the webassembly linear memory. -let memoryBytes; -let bytes; -let mod, inst -console.log("Initializing wasm...") -WebAssembly.instantiateStreaming( - fetch('lib.wasm'), go.importObject).then( - result => { - mod = result.module - inst = result.instance - memoryBytes = new Uint8Array(inst.exports.mem.buffer) - console.log("Initialization complete.") - run() - } -) - -async function run() { - await go.run(inst) - inst = await WebAssembly.instantiate(mod, go.importObject) // reset instance -} - -let memPointer - -// gotMem sets the webassembly linear memory with the data buffer result -// at the slice header pointer passed from Go. -function gotMem(pointer) { - memPointer = pointer - memoryBytes.set(bytes, pointer); - // Now the data can be loaded from the slice. - loadData(); -} - -// // displayImage takes the pointer to the target image in the wasm linear memory -// // and its length. Gets the resulting byte slice and creates an image blob. -// function displayImage(pointer, length) { -// let resultBytes = memoryBytes.slice(pointer, pointer + length); -// let blob = new Blob([resultBytes], { -// 'type': imageType -// }); -// document.getElementById('targetImg').src = URL.createObjectURL(blob); -// } - -// document.getElementById('input').addEventListener('change', function () { -// let reader = new FileReader(); - -// reader.onload = (ev) => { -// bytes = new Uint8Array(ev.target.result); -// initMem(bytes.length); -// let blob = new Blob([bytes], { -// 'type': imageType -// }); -// document.getElementById("sourceImg").src = URL.createObjectURL(blob); -// }; -// imageType = this.files[0].type; -// reader.readAsArrayBuffer(this.files[0]); -// }); window.onload = function () { document.getElementById('input').addEventListener('change', function () { @@ -64,12 +7,6 @@ window.onload = function () { reader.onload = event => { bytes = new Uint8Array(event.target.result) - initMem(bytes.length) - - let resultBuffer = new ArrayBuffer(getDecLen()); - let resultBytes = new Uint8Array(resultBuffer) - resultBytes.set(memoryBytes.slice(memPointer, memPointer + getDecLen())) - console.log("playing file") var player = new PCMPlayer({ encoding: '16bitInt', @@ -77,7 +14,7 @@ window.onload = function () { sampleRate: 48000, flushingTime: 2000 }); - player.feed(resultBytes) + player.feed(bytes) } reader.onerror = error => reject(error) reader.readAsArrayBuffer(input) diff --git a/cmd/audio-player/server.go b/cmd/audio-player/server.go index f8100992..407be14b 100644 --- a/cmd/audio-player/server.go +++ b/cmd/audio-player/server.go @@ -1,32 +1,18 @@ package main import ( - "flag" - "log" - "net/http" - "strings" + "flag" + "log" + "net/http" ) var ( - listen = flag.String("listen", ":8080", "listen address") - dir = flag.String("dir", ".", "directory to serve") + listen = flag.String("listen", ":8080", "listen address") + dir = flag.String("dir", ".", "directory to serve") ) func main() { - flag.Parse() - log.Printf("listening on %q...", *listen) - h := wasmContentTypeSetter(http.FileServer(http.Dir(*dir))) - err := http.ListenAndServe(*listen, h) - if err != http.ErrServerClosed { - log.Fatal(err) - } -} - -func wasmContentTypeSetter(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if strings.HasSuffix(r.URL.Path, ".wasm") { - w.Header().Set("content-type", "application/wasm") - } - h.ServeHTTP(w, r) - }) -} + flag.Parse() + log.Printf("listening on %q...", *listen) + log.Fatal(http.ListenAndServe(*listen, http.FileServer(http.Dir(*dir)))) +} \ No newline at end of file diff --git a/codec/adpcm/adpcm.go b/codec/adpcm/adpcm.go index ce8ae9f7..c59bf873 100644 --- a/codec/adpcm/adpcm.go +++ b/codec/adpcm/adpcm.go @@ -295,7 +295,7 @@ func (d *Decoder) Write(b []byte) (int, error) { // For each byte, seperate it into two nibbles (each nibble is a compressed sample), // then decode each nibble and output the resulting 16-bit samples. - // If padding flag is true (Adpcm[3]), only decode up until the last byte, then decode that separately. + // If padding flag is true (b[3]), only decode up until the last byte, then decode that separately. for i := headBytes; i < len(b)-int(b[3]); i++ { twoNibs := b[i] nib2 := byte(twoNibs >> 4) diff --git a/codec/adpcm/adpcm.js b/codec/adpcm/adpcm.js new file mode 100644 index 00000000..2637fff5 --- /dev/null +++ b/codec/adpcm/adpcm.js @@ -0,0 +1,72 @@ +const indexTable = [ + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8 +] + +const stepTable = [ + 7, 8, 9, 10, 11, 12, 13, 14, + 16, 17, 19, 21, 23, 25, 28, 31, + 34, 37, 41, 45, 50, 55, 60, 66, + 73, 80, 88, 97, 107, 118, 130, 143, + 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, + 724, 796, 876, 963, 1060, 1166, 1282, 1411, + 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, + 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, + 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, + 32767 +] + +const byteDepth = 2, // We are working with 16-bit samples. TODO(Trek): make configurable. + initSamps = 2, // Number of samples used to initialise the encoder. + initBytes = initSamps * byteDepth, + headBytes = 4, // Number of bytes in the header of ADPCM. + samplesPerEnc = 2, // Number of sample encoded at a time eg. 2 16-bit samples get encoded into 1 byte. + bytesPerEnc = samplesPerEnc * byteDepth, + compFact = 4; // In general ADPCM compresses by a factor of 4. + +let est, // Estimation of sample based on quantised ADPCM nibble. + idx, // Index to step used for estimation. + step; + +function decode(b) { + // b should be a Uint8Array + if (!(b instanceof Uint8Array)) { + console.log("Error: data is not a Uint8Array"); + return; + } + + // Initialize Decoder with first 4 bytes of b. + est = (new Uint16Array(b.slice(0, 2).buffer))[1]; + idx = b[byteDepth]; + step = stepTable[idx]; + + var result = new Uint8Array(b.slice(0, 2)); + + for (var i = headBytes; i < b.length - b[3]; i++) { + var twoNibs = b[i]; + var nib2 = twoNibs >> 4; + var nib1 = (nib2 << 4) ^ twoNibs + var firstBytes = decodeSample(nib1) + concat(result, firstBytes) + + var secondBytes = decodeSample(nib2) + concat(result, secondBytes) + } + if (b[3] == 1) { + var padNib = b[b.length - 1] + var samp = decodeSample(padNib) + concat(result, samp) + } + + return result; +} + +// concat concatenates TypedArrays a and b of same type. +function concat(a, b) { + var c = new(a.constructor)(a.length + b.length); + c.set(a, 0); + c.set(b, a.length); + return c; +} \ No newline at end of file From 2c436b7edff7c82096626ffcc5f38628d97850f7 Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 24 Jul 2019 17:55:02 +0930 Subject: [PATCH 08/36] audio-player: decode function completed --- {codec/adpcm => cmd/audio-player}/adpcm.js | 10 +++++++--- cmd/audio-player/index.html | 1 + cmd/audio-player/main.js | 21 ++++++++++++++------- 3 files changed, 22 insertions(+), 10 deletions(-) rename {codec/adpcm => cmd/audio-player}/adpcm.js (91%) diff --git a/codec/adpcm/adpcm.js b/cmd/audio-player/adpcm.js similarity index 91% rename from codec/adpcm/adpcm.js rename to cmd/audio-player/adpcm.js index 2637fff5..c1362938 100644 --- a/codec/adpcm/adpcm.js +++ b/cmd/audio-player/adpcm.js @@ -30,6 +30,10 @@ let est, // Estimation of sample based on quantised ADPCM nibble. idx, // Index to step used for estimation. step; +function decodeSample(nibble) { + return new Uint8Array([1, 2]) +} + function decode(b) { // b should be a Uint8Array if (!(b instanceof Uint8Array)) { @@ -49,15 +53,15 @@ function decode(b) { var nib2 = twoNibs >> 4; var nib1 = (nib2 << 4) ^ twoNibs var firstBytes = decodeSample(nib1) - concat(result, firstBytes) + result = concat(result, firstBytes) var secondBytes = decodeSample(nib2) - concat(result, secondBytes) + result = concat(result, secondBytes) } if (b[3] == 1) { var padNib = b[b.length - 1] var samp = decodeSample(padNib) - concat(result, samp) + result = concat(result, samp) } return result; diff --git a/cmd/audio-player/index.html b/cmd/audio-player/index.html index d8e7aa23..d4015ab4 100644 --- a/cmd/audio-player/index.html +++ b/cmd/audio-player/index.html @@ -5,6 +5,7 @@ Audio Player + diff --git a/cmd/audio-player/main.js b/cmd/audio-player/main.js index c81deb05..84e171d9 100644 --- a/cmd/audio-player/main.js +++ b/cmd/audio-player/main.js @@ -7,14 +7,21 @@ window.onload = function () { reader.onload = event => { bytes = new Uint8Array(event.target.result) + console.log(bytes.slice(0, 16)) + + var decoded = decode(bytes) + console.log("playing file") - var player = new PCMPlayer({ - encoding: '16bitInt', - channels: 1, - sampleRate: 48000, - flushingTime: 2000 - }); - player.feed(bytes) + + console.log(decoded.slice(0, 16)) + + // var player = new PCMPlayer({ + // encoding: '16bitInt', + // channels: 1, + // sampleRate: 48000, + // flushingTime: 2000 + // }); + // player.feed(bytes) } reader.onerror = error => reject(error) reader.readAsArrayBuffer(input) From cdc5b52f3644b199e42bbdcf3753d5457466114d Mon Sep 17 00:00:00 2001 From: Trek H Date: Thu, 25 Jul 2019 13:32:53 +0930 Subject: [PATCH 09/36] audio-player: adpcm file playing in browser Adpcm files can be uploaded and played however decoding takes a significant time. The process needs to be optimised. --- cmd/audio-player/adpcm.js | 39 +++++++++++++++++++++++++++++++++------ cmd/audio-player/main.js | 19 +++++++------------ 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/cmd/audio-player/adpcm.js b/cmd/audio-player/adpcm.js index c1362938..4f32f047 100644 --- a/cmd/audio-player/adpcm.js +++ b/cmd/audio-player/adpcm.js @@ -26,12 +26,40 @@ const byteDepth = 2, // We are working with 16-bit samples. TODO(Trek): make con bytesPerEnc = samplesPerEnc * byteDepth, compFact = 4; // In general ADPCM compresses by a factor of 4. -let est, // Estimation of sample based on quantised ADPCM nibble. - idx, // Index to step used for estimation. - step; +let est = 0, // Estimation of sample based on quantised ADPCM nibble. + idx = 0, // Index to step used for estimation. + step = 0; function decodeSample(nibble) { - return new Uint8Array([1, 2]) + + let diff = 0; + if ((nibble & 4) != 0) { + diff += step; + } + if ((nibble & 2) != 0) { + diff += step >> 1; + } + if ((nibble & 1) != 0) { + diff += step >> 2; + } + diff += step >> 3; + + if ((nibble & 8) != 0) { + diff = -diff; + } + est += diff; + idx += indexTable[nibble]; + + if (idx < 0) { + idx = 0; + } else if (idx > stepTable.length - 1) { + idx = stepTable.length - 1; + } + + step = stepTable[idx]; + + result = new Uint8Array(new Uint16Array([est]).buffer); + return result; } function decode(b) { @@ -42,7 +70,7 @@ function decode(b) { } // Initialize Decoder with first 4 bytes of b. - est = (new Uint16Array(b.slice(0, 2).buffer))[1]; + est = (new Uint16Array(b.slice(0, 2).buffer))[0]; idx = b[byteDepth]; step = stepTable[idx]; @@ -63,7 +91,6 @@ function decode(b) { var samp = decodeSample(padNib) result = concat(result, samp) } - return result; } diff --git a/cmd/audio-player/main.js b/cmd/audio-player/main.js index 84e171d9..caeaa404 100644 --- a/cmd/audio-player/main.js +++ b/cmd/audio-player/main.js @@ -7,21 +7,16 @@ window.onload = function () { reader.onload = event => { bytes = new Uint8Array(event.target.result) - console.log(bytes.slice(0, 16)) - var decoded = decode(bytes) console.log("playing file") - - console.log(decoded.slice(0, 16)) - - // var player = new PCMPlayer({ - // encoding: '16bitInt', - // channels: 1, - // sampleRate: 48000, - // flushingTime: 2000 - // }); - // player.feed(bytes) + var player = new PCMPlayer({ + encoding: '16bitInt', + channels: 1, + sampleRate: 48000, + flushingTime: 2000 + }); + player.feed(decoded) } reader.onerror = error => reject(error) reader.readAsArrayBuffer(input) From 000b71068f2d38d9735ca1c1b81c150bed68dd05 Mon Sep 17 00:00:00 2001 From: Trek H Date: Thu, 25 Jul 2019 14:58:39 +0930 Subject: [PATCH 10/36] audio-player: decoding optimised, not using typed arrays --- cmd/audio-player/adpcm.js | 38 ++++++++++++++++---------------------- cmd/audio-player/main.js | 4 ++-- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/cmd/audio-player/adpcm.js b/cmd/audio-player/adpcm.js index 4f32f047..a2cb4036 100644 --- a/cmd/audio-player/adpcm.js +++ b/cmd/audio-player/adpcm.js @@ -31,7 +31,6 @@ let est = 0, // Estimation of sample based on quantised ADPCM nibble. step = 0; function decodeSample(nibble) { - let diff = 0; if ((nibble & 4) != 0) { diff += step; @@ -58,46 +57,41 @@ function decodeSample(nibble) { step = stepTable[idx]; - result = new Uint8Array(new Uint16Array([est]).buffer); + result = est; return result; } function decode(b) { - // b should be a Uint8Array - if (!(b instanceof Uint8Array)) { - console.log("Error: data is not a Uint8Array"); - return; - } - // Initialize Decoder with first 4 bytes of b. - est = (new Uint16Array(b.slice(0, 2).buffer))[0]; + est = bytesToInt16(b[0], b[1]); idx = b[byteDepth]; step = stepTable[idx]; - var result = new Uint8Array(b.slice(0, 2)); + var result = b.slice(0, 2); for (var i = headBytes; i < b.length - b[3]; i++) { var twoNibs = b[i]; var nib2 = twoNibs >> 4; var nib1 = (nib2 << 4) ^ twoNibs - var firstBytes = decodeSample(nib1) - result = concat(result, firstBytes) - var secondBytes = decodeSample(nib2) - result = concat(result, secondBytes) + var sample1 = int16ToBytes(decodeSample(nib1)) + result.push(...sample1) + + var sample2 = int16ToBytes(decodeSample(nib2)) + result.push(...sample2) } if (b[3] == 1) { var padNib = b[b.length - 1] - var samp = decodeSample(padNib) - result = concat(result, samp) + var sample = int16ToBytes(decodeSample(padNib)) + result.push(...sample) } return result; } -// concat concatenates TypedArrays a and b of same type. -function concat(a, b) { - var c = new(a.constructor)(a.length + b.length); - c.set(a, 0); - c.set(b, a.length); - return c; +function int16ToBytes(num) { + return [(num & 0x00ff), (num & 0xff00) >> 8]; +} + +function bytesToInt16(b) { + return (b[0] | (b[1] << 8)) } \ No newline at end of file diff --git a/cmd/audio-player/main.js b/cmd/audio-player/main.js index caeaa404..b68ab43f 100644 --- a/cmd/audio-player/main.js +++ b/cmd/audio-player/main.js @@ -7,7 +7,7 @@ window.onload = function () { reader.onload = event => { bytes = new Uint8Array(event.target.result) - var decoded = decode(bytes) + var decoded = decode(Array.from(bytes)) console.log("playing file") var player = new PCMPlayer({ @@ -16,7 +16,7 @@ window.onload = function () { sampleRate: 48000, flushingTime: 2000 }); - player.feed(decoded) + player.feed(Uint8Array.from(decoded)) } reader.onerror = error => reject(error) reader.readAsArrayBuffer(input) From 9e9ac688c3f3d5f416cc81bf8bda4b8e2589192f Mon Sep 17 00:00:00 2001 From: Trek H Date: Sat, 3 Aug 2019 18:02:39 +0930 Subject: [PATCH 11/36] audio-player: added wav encoder for html 5 player --- cmd/audio-player/adpcm.js | 31 ++ cmd/audio-player/index.html | 28 +- cmd/audio-player/main.go | 81 ----- cmd/audio-player/main.js | 46 ++- cmd/audio-player/pcm-player.min.js | 1 - cmd/audio-player/pcm-to-wav.js | 64 ++++ cmd/audio-player/wasm_exec.js | 465 ----------------------------- 7 files changed, 155 insertions(+), 561 deletions(-) delete mode 100644 cmd/audio-player/main.go delete mode 100644 cmd/audio-player/pcm-player.min.js create mode 100644 cmd/audio-player/pcm-to-wav.js delete mode 100644 cmd/audio-player/wasm_exec.js diff --git a/cmd/audio-player/adpcm.js b/cmd/audio-player/adpcm.js index a2cb4036..6767452e 100644 --- a/cmd/audio-player/adpcm.js +++ b/cmd/audio-player/adpcm.js @@ -1,3 +1,32 @@ +/* +NAME + adpcm.js + +AUTHOR + Trek Hopton + +LICENSE + This file is Copyright (C) 2018 the Australian Ocean Lab (AusOcean) + + It is free software: you can redistribute it and/or modify them + under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + It is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License in gpl.txt. + If not, see [GNU licenses](http://www.gnu.org/licenses). +*/ + +/* + Original IMA/DVI ADPCM specification: (http://www.cs.columbia.edu/~hgs/audio/dvi/IMA_ADPCM.pdf). + Reference algorithms for ADPCM compression and decompression are in part 6. +*/ + const indexTable = [ -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8 @@ -30,6 +59,7 @@ let est = 0, // Estimation of sample based on quantised ADPCM nibble. idx = 0, // Index to step used for estimation. step = 0; +// decodeSample takes 4 bits which represents a single ADPCM nibble, and returns a 16 bit decoded PCM sample. function decodeSample(nibble) { let diff = 0; if ((nibble & 4) != 0) { @@ -61,6 +91,7 @@ function decodeSample(nibble) { return result; } +// decode takes an array of bytes of arbitrary length representing adpcm and decodes it into pcm. function decode(b) { // Initialize Decoder with first 4 bytes of b. est = bytesToInt16(b[0], b[1]); diff --git a/cmd/audio-player/index.html b/cmd/audio-player/index.html index d4015ab4..1624870f 100644 --- a/cmd/audio-player/index.html +++ b/cmd/audio-player/index.html @@ -4,20 +4,34 @@ Audio Player - + - + - -
-
- + +
+
+
+ +
+
+ +
+
+
+ ©2019 Australian Ocean Laboratory Limited (AusOcean) (License) +
+
\ No newline at end of file diff --git a/cmd/audio-player/main.go b/cmd/audio-player/main.go deleted file mode 100644 index 970cfccb..00000000 --- a/cmd/audio-player/main.go +++ /dev/null @@ -1,81 +0,0 @@ -package main - -import ( - "bytes" - "reflect" - "syscall/js" - "unsafe" - - "bitbucket.org/ausocean/av/codec/adpcm" -) - -func main() { - dec := NewDecoder() - dec.Start() -} - -// Decoder is a client side adpcm decoder -type Decoder struct { - inBuf []uint8 - onImgLoadCb, initMemCb, getDecLenCb js.Func - console js.Value - done chan struct{} -} - -// NewDecoder returns a new instance of decoder -func NewDecoder() *Decoder { - return &Decoder{ - console: js.Global().Get("console"), - done: make(chan struct{}), - } -} - -func (s *Decoder) setupOnImgLoadCb() { - s.onImgLoadCb = js.FuncOf(func(this js.Value, args []js.Value) interface{} { - // reader := bytes.NewReader(s.inBuf) - - //DECODING HAPPENS HERE - - decoded := bytes.NewBuffer(make([]byte, 0, len(s.inBuf)*4)) - dec := adpcm.NewDecoder(decoded) - dec.Write(s.inBuf) - - s.inBuf = decoded.Bytes() - - return nil - }) -} - -func (s *Decoder) setupInitMemCb() { - // The length of the data array buffer is passed. - // Then the buf slice is initialized to that length. - // A pointer to that slice is passed back to the browser. - s.initMemCb = js.FuncOf(func(this js.Value, i []js.Value) interface{} { - length := i[0].Int() - s.console.Call("log", "length:", length) - s.inBuf = make([]uint8, length) - hdr := (*reflect.SliceHeader)(unsafe.Pointer(&s.inBuf)) - ptr := uintptr(unsafe.Pointer(hdr.Data)) - s.console.Call("log", "ptr:", ptr) - js.Global().Call("gotMem", ptr) - return nil - }) -} - -func (s *Decoder) setupGetDecLenCb() { - s.getDecLenCb = js.FuncOf(func(this js.Value, i []js.Value) interface{} { - return len(s.inBuf) - }) -} - -// Start sets up all the callbacks and waits for the close signal -// to be sent from the browser. -func (s *Decoder) Start() { - s.setupInitMemCb() - js.Global().Set("initMem", s.initMemCb) - s.setupOnImgLoadCb() - js.Global().Set("loadData", s.onImgLoadCb) - s.setupGetDecLenCb() - js.Global().Set("getDecLen", s.getDecLenCb) - <-s.done -} diff --git a/cmd/audio-player/main.js b/cmd/audio-player/main.js index b68ab43f..00d0eab6 100644 --- a/cmd/audio-player/main.js +++ b/cmd/audio-player/main.js @@ -1,3 +1,27 @@ +/* +NAME + main.js + +AUTHOR + Trek Hopton + +LICENSE + This file is Copyright (C) 2018 the Australian Ocean Lab (AusOcean) + + It is free software: you can redistribute it and/or modify them + under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + It is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License in gpl.txt. + If not, see [GNU licenses](http://www.gnu.org/licenses). +*/ + window.onload = function () { document.getElementById('input').addEventListener('change', function () { @@ -7,16 +31,24 @@ window.onload = function () { reader.onload = event => { bytes = new Uint8Array(event.target.result) + // decode adpcm to pcm var decoded = decode(Array.from(bytes)) - console.log("playing file") - var player = new PCMPlayer({ - encoding: '16bitInt', - channels: 1, - sampleRate: 48000, - flushingTime: 2000 + // convert raw pcm to wav + var wav = pcmToWav(decoded, 48000, 1, 16); + + // play wav data in player + const blob = new Blob([Uint8Array.from(wav)], { + type: 'audio/wav' }); - player.feed(Uint8Array.from(decoded)) + const url = URL.createObjectURL(blob); + + const audio = document.getElementById('audio'); + const source = document.getElementById('source'); + + source.src = url; + audio.load(); + audio.play(); } reader.onerror = error => reject(error) reader.readAsArrayBuffer(input) diff --git a/cmd/audio-player/pcm-player.min.js b/cmd/audio-player/pcm-player.min.js deleted file mode 100644 index 42c33624..00000000 --- a/cmd/audio-player/pcm-player.min.js +++ /dev/null @@ -1 +0,0 @@ -function PCMPlayer(t){this.init(t)}PCMPlayer.prototype.init=function(t){this.option=Object.assign({},{encoding:"16bitInt",channels:1,sampleRate:8e3,flushingTime:1e3},t),this.samples=new Float32Array,this.flush=this.flush.bind(this),this.interval=setInterval(this.flush,this.option.flushingTime),this.maxValue=this.getMaxValue(),this.typedArray=this.getTypedArray(),this.createContext()},PCMPlayer.prototype.getMaxValue=function(){var t={"8bitInt":128,"16bitInt":32768,"32bitInt":2147483648,"32bitFloat":1};return t[this.option.encoding]?t[this.option.encoding]:t["16bitInt"]},PCMPlayer.prototype.getTypedArray=function(){var t={"8bitInt":Int8Array,"16bitInt":Int16Array,"32bitInt":Int32Array,"32bitFloat":Float32Array};return t[this.option.encoding]?t[this.option.encoding]:t["16bitInt"]},PCMPlayer.prototype.createContext=function(){this.audioCtx=new(window.AudioContext||window.webkitAudioContext),this.gainNode=this.audioCtx.createGain(),this.gainNode.gain.value=1,this.gainNode.connect(this.audioCtx.destination),this.startTime=this.audioCtx.currentTime},PCMPlayer.prototype.isTypedArray=function(t){return t.byteLength&&t.buffer&&t.buffer.constructor==ArrayBuffer},PCMPlayer.prototype.feed=function(t){if(this.isTypedArray(t)){t=this.getFormatedValue(t);var e=new Float32Array(this.samples.length+t.length);e.set(this.samples,0),e.set(t,this.samples.length),this.samples=e}},PCMPlayer.prototype.getFormatedValue=function(t){t=new this.typedArray(t.buffer);var e,i=new Float32Array(t.length);for(e=0;e + +LICENSE + This file is Copyright (C) 2018 the Australian Ocean Lab (AusOcean) + + It is free software: you can redistribute it and/or modify them + under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + It is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License in gpl.txt. + If not, see [GNU licenses](http://www.gnu.org/licenses). +*/ + +// pcmToWav takes raw pcm data along with the sample rate, number of channels and bit-depth, +// and adds a WAV header to it so that it can be read and played by common players. +// input and output data bytes are represented as arrays of 8 bit integers. +// WAV spec.: http://soundfile.sapp.org/doc/WaveFormat/ +function pcmToWav(data, rate, channels, bitdepth) { + subChunk2ID = [100, 97, 116, 97]; // "data" + subChunk2Size = int32ToBytes(data.length); + + subChunk1ID = [102, 109, 116, 32]; // "fmt " + subChunk1Size = int32ToBytes(16); + audioFmt = int16ToBytes(1); // 1 = PCM + numChannels = int16ToBytes(channels); + sampleRate = int32ToBytes(rate); + byteRate = int32ToBytes(rate * channels * bitdepth / 8); + blockAlign = int16ToBytes(channels * bitdepth / 8); + bitsPerSample = int16ToBytes(bitdepth) + + chunkID = [82, 73, 70, 70]; // "RIFF" + chunkSize = int32ToBytes(36 + data.length); + format = [87, 65, 86, 69]; // "WAVE" + + result = chunkID; + result.push(...chunkSize, ...format, ...subChunk1ID, ...subChunk1Size, ...audioFmt, ...numChannels, ...sampleRate, ...byteRate, ...blockAlign, ...bitsPerSample, ...subChunk2ID, ...subChunk2Size); + return result.concat(data); +} + +// int32ToBytes takes a number assumed to be an int 32 and converts it to an array containing bytes (Little Endian). +function int32ToBytes(num) { + return [ + (num & 0x000000ff), + (num & 0x0000ff00) >> 8, + (num & 0x00ff0000) >> 16, + (num & 0xff000000) >> 24 + ]; +} + +// int16ToBytes takes a number assumed to be an int 16 and converts it to an array containing bytes (Little Endian). +function int16ToBytes(num) { + return [(num & 0x00ff), (num & 0xff00) >> 8]; +} \ No newline at end of file diff --git a/cmd/audio-player/wasm_exec.js b/cmd/audio-player/wasm_exec.js deleted file mode 100644 index 165d5677..00000000 --- a/cmd/audio-player/wasm_exec.js +++ /dev/null @@ -1,465 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -(() => { - if (typeof global !== "undefined") { - // global already exists - } else if (typeof window !== "undefined") { - window.global = window; - } else if (typeof self !== "undefined") { - self.global = self; - } else { - throw new Error("cannot export Go (neither global, window nor self is defined)"); - } - - // Map web browser API and Node.js API to a single common API (preferring web standards over Node.js API). - const isNodeJS = global.process && global.process.title === "node"; - if (isNodeJS) { - global.require = require; - global.fs = require("fs"); - - const nodeCrypto = require("crypto"); - global.crypto = { - getRandomValues(b) { - nodeCrypto.randomFillSync(b); - }, - }; - - global.performance = { - now() { - const [sec, nsec] = process.hrtime(); - return sec * 1000 + nsec / 1000000; - }, - }; - - const util = require("util"); - global.TextEncoder = util.TextEncoder; - global.TextDecoder = util.TextDecoder; - } else { - let outputBuf = ""; - global.fs = { - constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused - writeSync(fd, buf) { - outputBuf += decoder.decode(buf); - const nl = outputBuf.lastIndexOf("\n"); - if (nl != -1) { - console.log(outputBuf.substr(0, nl)); - outputBuf = outputBuf.substr(nl + 1); - } - return buf.length; - }, - write(fd, buf, offset, length, position, callback) { - if (offset !== 0 || length !== buf.length || position !== null) { - throw new Error("not implemented"); - } - const n = this.writeSync(fd, buf); - callback(null, n); - }, - open(path, flags, mode, callback) { - const err = new Error("not implemented"); - err.code = "ENOSYS"; - callback(err); - }, - read(fd, buffer, offset, length, position, callback) { - const err = new Error("not implemented"); - err.code = "ENOSYS"; - callback(err); - }, - fsync(fd, callback) { - callback(null); - }, - }; - } - - const encoder = new TextEncoder("utf-8"); - const decoder = new TextDecoder("utf-8"); - - global.Go = class { - constructor() { - this.argv = ["js"]; - this.env = {}; - this.exit = (code) => { - if (code !== 0) { - console.warn("exit code:", code); - } - }; - this._exitPromise = new Promise((resolve) => { - this._resolveExitPromise = resolve; - }); - this._pendingEvent = null; - this._scheduledTimeouts = new Map(); - this._nextCallbackTimeoutID = 1; - - const mem = () => { - // The buffer may change when requesting more memory. - return new DataView(this._inst.exports.mem.buffer); - } - - const setInt64 = (addr, v) => { - mem().setUint32(addr + 0, v, true); - mem().setUint32(addr + 4, Math.floor(v / 4294967296), true); - } - - const getInt64 = (addr) => { - const low = mem().getUint32(addr + 0, true); - const high = mem().getInt32(addr + 4, true); - return low + high * 4294967296; - } - - const loadValue = (addr) => { - const f = mem().getFloat64(addr, true); - if (f === 0) { - return undefined; - } - if (!isNaN(f)) { - return f; - } - - const id = mem().getUint32(addr, true); - return this._values[id]; - } - - const storeValue = (addr, v) => { - const nanHead = 0x7FF80000; - - if (typeof v === "number") { - if (isNaN(v)) { - mem().setUint32(addr + 4, nanHead, true); - mem().setUint32(addr, 0, true); - return; - } - if (v === 0) { - mem().setUint32(addr + 4, nanHead, true); - mem().setUint32(addr, 1, true); - return; - } - mem().setFloat64(addr, v, true); - return; - } - - switch (v) { - case undefined: - mem().setFloat64(addr, 0, true); - return; - case null: - mem().setUint32(addr + 4, nanHead, true); - mem().setUint32(addr, 2, true); - return; - case true: - mem().setUint32(addr + 4, nanHead, true); - mem().setUint32(addr, 3, true); - return; - case false: - mem().setUint32(addr + 4, nanHead, true); - mem().setUint32(addr, 4, true); - return; - } - - let ref = this._refs.get(v); - if (ref === undefined) { - ref = this._values.length; - this._values.push(v); - this._refs.set(v, ref); - } - let typeFlag = 0; - switch (typeof v) { - case "string": - typeFlag = 1; - break; - case "symbol": - typeFlag = 2; - break; - case "function": - typeFlag = 3; - break; - } - mem().setUint32(addr + 4, nanHead | typeFlag, true); - mem().setUint32(addr, ref, true); - } - - const loadSlice = (addr) => { - const array = getInt64(addr + 0); - const len = getInt64(addr + 8); - return new Uint8Array(this._inst.exports.mem.buffer, array, len); - } - - const loadSliceOfValues = (addr) => { - const array = getInt64(addr + 0); - const len = getInt64(addr + 8); - const a = new Array(len); - for (let i = 0; i < len; i++) { - a[i] = loadValue(array + i * 8); - } - return a; - } - - const loadString = (addr) => { - const saddr = getInt64(addr + 0); - const len = getInt64(addr + 8); - return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); - } - - const timeOrigin = Date.now() - performance.now(); - this.importObject = { - go: { - // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) - // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported - // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). - // This changes the SP, thus we have to update the SP used by the imported function. - - // func wasmExit(code int32) - "runtime.wasmExit": (sp) => { - const code = mem().getInt32(sp + 8, true); - this.exited = true; - delete this._inst; - delete this._values; - delete this._refs; - this.exit(code); - }, - - // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) - "runtime.wasmWrite": (sp) => { - const fd = getInt64(sp + 8); - const p = getInt64(sp + 16); - const n = mem().getInt32(sp + 24, true); - fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); - }, - - // func nanotime() int64 - "runtime.nanotime": (sp) => { - setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); - }, - - // func walltime() (sec int64, nsec int32) - "runtime.walltime": (sp) => { - const msec = (new Date).getTime(); - setInt64(sp + 8, msec / 1000); - mem().setInt32(sp + 16, (msec % 1000) * 1000000, true); - }, - - // func scheduleTimeoutEvent(delay int64) int32 - "runtime.scheduleTimeoutEvent": (sp) => { - const id = this._nextCallbackTimeoutID; - this._nextCallbackTimeoutID++; - this._scheduledTimeouts.set(id, setTimeout( - () => { this._resume(); }, - getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early - )); - mem().setInt32(sp + 16, id, true); - }, - - // func clearTimeoutEvent(id int32) - "runtime.clearTimeoutEvent": (sp) => { - const id = mem().getInt32(sp + 8, true); - clearTimeout(this._scheduledTimeouts.get(id)); - this._scheduledTimeouts.delete(id); - }, - - // func getRandomData(r []byte) - "runtime.getRandomData": (sp) => { - crypto.getRandomValues(loadSlice(sp + 8)); - }, - - // func stringVal(value string) ref - "syscall/js.stringVal": (sp) => { - storeValue(sp + 24, loadString(sp + 8)); - }, - - // func valueGet(v ref, p string) ref - "syscall/js.valueGet": (sp) => { - const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); - sp = this._inst.exports.getsp(); // see comment above - storeValue(sp + 32, result); - }, - - // func valueSet(v ref, p string, x ref) - "syscall/js.valueSet": (sp) => { - Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); - }, - - // func valueIndex(v ref, i int) ref - "syscall/js.valueIndex": (sp) => { - storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); - }, - - // valueSetIndex(v ref, i int, x ref) - "syscall/js.valueSetIndex": (sp) => { - Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); - }, - - // func valueCall(v ref, m string, args []ref) (ref, bool) - "syscall/js.valueCall": (sp) => { - try { - const v = loadValue(sp + 8); - const m = Reflect.get(v, loadString(sp + 16)); - const args = loadSliceOfValues(sp + 32); - const result = Reflect.apply(m, v, args); - sp = this._inst.exports.getsp(); // see comment above - storeValue(sp + 56, result); - mem().setUint8(sp + 64, 1); - } catch (err) { - storeValue(sp + 56, err); - mem().setUint8(sp + 64, 0); - } - }, - - // func valueInvoke(v ref, args []ref) (ref, bool) - "syscall/js.valueInvoke": (sp) => { - try { - const v = loadValue(sp + 8); - const args = loadSliceOfValues(sp + 16); - const result = Reflect.apply(v, undefined, args); - sp = this._inst.exports.getsp(); // see comment above - storeValue(sp + 40, result); - mem().setUint8(sp + 48, 1); - } catch (err) { - storeValue(sp + 40, err); - mem().setUint8(sp + 48, 0); - } - }, - - // func valueNew(v ref, args []ref) (ref, bool) - "syscall/js.valueNew": (sp) => { - try { - const v = loadValue(sp + 8); - const args = loadSliceOfValues(sp + 16); - const result = Reflect.construct(v, args); - sp = this._inst.exports.getsp(); // see comment above - storeValue(sp + 40, result); - mem().setUint8(sp + 48, 1); - } catch (err) { - storeValue(sp + 40, err); - mem().setUint8(sp + 48, 0); - } - }, - - // func valueLength(v ref) int - "syscall/js.valueLength": (sp) => { - setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); - }, - - // valuePrepareString(v ref) (ref, int) - "syscall/js.valuePrepareString": (sp) => { - const str = encoder.encode(String(loadValue(sp + 8))); - storeValue(sp + 16, str); - setInt64(sp + 24, str.length); - }, - - // valueLoadString(v ref, b []byte) - "syscall/js.valueLoadString": (sp) => { - const str = loadValue(sp + 8); - loadSlice(sp + 16).set(str); - }, - - // func valueInstanceOf(v ref, t ref) bool - "syscall/js.valueInstanceOf": (sp) => { - mem().setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16)); - }, - - "debug": (value) => { - console.log(value); - }, - } - }; - } - - async run(instance) { - this._inst = instance; - this._values = [ // TODO: garbage collection - NaN, - 0, - null, - true, - false, - global, - this._inst.exports.mem, - this, - ]; - this._refs = new Map(); - this.exited = false; - - const mem = new DataView(this._inst.exports.mem.buffer) - - // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. - let offset = 4096; - - const strPtr = (str) => { - let ptr = offset; - new Uint8Array(mem.buffer, offset, str.length + 1).set(encoder.encode(str + "\0")); - offset += str.length + (8 - (str.length % 8)); - return ptr; - }; - - const argc = this.argv.length; - - const argvPtrs = []; - this.argv.forEach((arg) => { - argvPtrs.push(strPtr(arg)); - }); - - const keys = Object.keys(this.env).sort(); - argvPtrs.push(keys.length); - keys.forEach((key) => { - argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); - }); - - const argv = offset; - argvPtrs.forEach((ptr) => { - mem.setUint32(offset, ptr, true); - mem.setUint32(offset + 4, 0, true); - offset += 8; - }); - - this._inst.exports.run(argc, argv); - if (this.exited) { - this._resolveExitPromise(); - } - await this._exitPromise; - } - - _resume() { - if (this.exited) { - throw new Error("Go program has already exited"); - } - this._inst.exports.resume(); - if (this.exited) { - this._resolveExitPromise(); - } - } - - _makeFuncWrapper(id) { - const go = this; - return function () { - const event = { id: id, this: this, args: arguments }; - go._pendingEvent = event; - go._resume(); - return event.result; - }; - } - } - - if (isNodeJS) { - if (process.argv.length < 3) { - process.stderr.write("usage: go_js_wasm_exec [wasm binary] [arguments]\n"); - process.exit(1); - } - - const go = new Go(); - go.argv = process.argv.slice(2); - go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env); - go.exit = process.exit; - WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { - process.on("exit", (code) => { // Node.js exits if no event handler is pending - if (code === 0 && !go.exited) { - // deadlock, make Go print error and stack traces - go._pendingEvent = { id: 0 }; - go._resume(); - } - }); - return go.run(result.instance); - }).catch((err) => { - throw err; - }); - } -})(); From b0f87dd01edc2726e5386d8190643776c2e8b081 Mon Sep 17 00:00:00 2001 From: Trek H Date: Sat, 3 Aug 2019 18:03:32 +0930 Subject: [PATCH 12/36] audio-player: added style --- cmd/audio-player/style.css | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 cmd/audio-player/style.css diff --git a/cmd/audio-player/style.css b/cmd/audio-player/style.css new file mode 100644 index 00000000..f0ab2e6e --- /dev/null +++ b/cmd/audio-player/style.css @@ -0,0 +1,16 @@ +html, +body { + min-height: 100%; +} + +#page-content { + flex: 1 0 auto; +} + +#sticky-footer { + flex-shrink: none; +} + +.jumbotron { + margin: 0; +} \ No newline at end of file From 935e3fca6277ebc502cf83cd55a1a2d588c98943 Mon Sep 17 00:00:00 2001 From: Trek H Date: Mon, 5 Aug 2019 12:39:55 +0930 Subject: [PATCH 13/36] audio-player: footer and liscence --- cmd/audio-player/index.html | 25 ++++++++++++------------- cmd/audio-player/style.css | 16 ---------------- 2 files changed, 12 insertions(+), 29 deletions(-) delete mode 100644 cmd/audio-player/style.css diff --git a/cmd/audio-player/index.html b/cmd/audio-player/index.html index 1624870f..27907841 100644 --- a/cmd/audio-player/index.html +++ b/cmd/audio-player/index.html @@ -12,21 +12,20 @@ - -
-
-
- -
-
- -
+ +
+
+ +
+
+
-