From 994db188c2574ae55eebc884d4c498b6d04b0352 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Sat, 14 Jan 2023 16:45:33 +0900 Subject: [PATCH] mp3 decoding but slow --- assets/disk0/home/mp3test.js | 150 -------- assets/disk0/home/playrawpcm.js | 2 +- assets/disk0/tvdos/bin/command.js | 54 ++- assets/disk0/tvdos/bin/playmov.js | 2 +- assets/disk0/tvdos/bin/playmp3.js | 227 ++++++++++++ assets/disk0/tvdos/bin/playpcm.js | 8 +- assets/disk0/tvdos/bin/playwav.js | 2 +- assets/disk0/tvdos/include/js-mp3/frame.js | 41 ++- .../disk0/tvdos/include/js-mp3/frameheader.js | 5 + assets/disk0/tvdos/include/mp3dec.js | 42 ++- assets/disk0/tvdos/tuidev/zfm.js | 8 +- terranmon.txt | 2 +- .../net/torvald/tsvm/AudioJSR223Delegate.kt | 340 +++++++++++++++++- 13 files changed, 672 insertions(+), 211 deletions(-) delete mode 100644 assets/disk0/home/mp3test.js create mode 100644 assets/disk0/tvdos/bin/playmp3.js diff --git a/assets/disk0/home/mp3test.js b/assets/disk0/home/mp3test.js deleted file mode 100644 index ae2a8a9..0000000 --- a/assets/disk0/home/mp3test.js +++ /dev/null @@ -1,150 +0,0 @@ -const Mp3 = require('mp3dec') -const pcm = require("pcm") - - -class SequentialFileBuffer { - - constructor(path, offset, length) { - if (Array.isArray(path)) throw Error("arg #1 is path(string), not array") - - this.path = path - this.file = files.open(path) - - this.offset = offset || 0 - this.originalOffset = offset - this.length = length || this.file.size - - this.seq = require("seqread") - this.seq.prepare(path) - } - - /*readFull(n) { - throw Error() - let ptr = this.seq.readBytes(n) - return ptr - }*/ - - readStr(n) { - let ptr = this.seq.readBytes(n) - let s = '' - for (let i = 0; i < n; i++) { - if (i >= this.length) break - s += String.fromCharCode(sys.peek(ptr + i)) - } - sys.free(ptr) - return s - } - - readByteNumbers(n) { - let ptr = this.seq.readBytes(n) - let s = [] - for (let i = 0; i < n; i++) { - if (i >= this.length) break - s.push(sys.peek(ptr + i)) - } - sys.free(ptr) - return s - } - - unread(diff) { - let newSkipLen = this.seq.getReadCount() - diff - this.seq.prepare(this.path) - this.seq.skip(newSkipLen) - } - - rewind() { - this.seq.prepare(this.path) - } - - seek(p) { - this.seq.prepare(this.path) - this.seq.skip(p) - } - - get byteLength() { - return this.length - } - - /*get remaining() { - return this.length - this.getReadCount() - }*/ -} - - - -Object.keys(Mp3).forEach(e=>{ - print(`${e}\t`) -}) -println() - -println("reading...") -//let arr = files.open("A:/gateless.mp3").bread() -//let ab = new ArrayBuffer(arr.length) -//let abba = new Uint8Array(ab) -//arr.forEach((v,i)=>{ abba[i] = v }) -//let mp3ArrayBuffer = new Uint8Array(ab, 0, arr.length)* - -println("decoding...") -let decoder = Mp3.newDecoder(new SequentialFileBuffer("A:/gateless0.mp3")) -if (decoder === null) throw Error("decoder is null") - -audio.resetParams(0) -audio.purgeQueue(0) -audio.setPcmMode(0) -audio.setMasterVolume(0, 255) -audio.play(0) - -let decodedLength = 0 -let readPtr = sys.malloc(8000) -let decodePtr = sys.malloc(12000) - - -function decodeAndResample(readArr, decodePtr, readLength) { - for (let i = 0; i < readLength; i+= 2) { - let sample = pcm.u16Tos16(readArr[i] | (readArr[i+1] << 8)) - let u8 = pcm.s16Tou8(sample) - - sys.poke(decodePtr + (i >> 1), u8) - } - return readLength / 2 -} - - -function printPlayBar() { -} - - -const QUEUE_MAX = 4 -let t1 = sys.nanoTime() -decoder.decode(obj=>{ - let t2 = sys.nanoTime() - - - let buf = obj.buf - let err = obj.err - - decodedLength += buf.byteLength - - let declen = decodeAndResample(buf, decodePtr, buf.byteLength) - - audio.putPcmDataByPtr(decodePtr, declen, 0) - audio.setSampleUploadLength(0, declen) - audio.startSampleUpload(0) - audio.play(0) - - -// sys.sleep(10) // decoding time is slower than realtime :( - - - let decodingTime = t2 - t1 - let bufRealTimeLen = (declen) / 64000.0 * 1000000000 - t1 = t2 - println(`Decoded ${decodedLength} bytes; lag: ${(decodingTime - bufRealTimeLen) / 1000000} ms`) - - - - -}) // now you got decoded PCM data - -sys.free(readPtr) -sys.free(decodePtr) \ No newline at end of file diff --git a/assets/disk0/home/playrawpcm.js b/assets/disk0/home/playrawpcm.js index 9ca94a7..13786d1 100644 --- a/assets/disk0/home/playrawpcm.js +++ b/assets/disk0/home/playrawpcm.js @@ -145,5 +145,5 @@ while (sampleSize > 0) { sys.sleep(10) } -audio.stop(0) +//audio.stop(0) sys.free(decodePtr) diff --git a/assets/disk0/tvdos/bin/command.js b/assets/disk0/tvdos/bin/command.js index dea0f9f..f0778a4 100644 --- a/assets/disk0/tvdos/bin/command.js +++ b/assets/disk0/tvdos/bin/command.js @@ -348,6 +348,13 @@ shell.resolvePathInput = function(input) { return { string: pathstr, pwd: newPwd, drive: driveLetter, full: `${driveLetter}:${pathstr}` } } +shell.isValidDriveLetter = function(l) { + if (typeof l === 'string' || l instanceof String) { + let lc = l.charCodeAt(0) + return (l == '$' || 65 <= lc && lc <= 90 || 97 <= lc && lc <= 122) + } + else return false +} shell.coreutils = { /* Args follow this format: * <1st arg> <2nd arg> ... @@ -682,30 +689,39 @@ shell.execute = function(line) { // search through PATH for execution var fileExists = false - var searchDir = (cmd.startsWith("/")) ? [""] : ["/"+shell_pwd.join("/")].concat(_TVDOS.getPath()) + var searchFile; + var searchPath = ""; - var pathExt = [] // it seems Nashorn does not like 'let' too much? this line gets ignored sometimes - // fill pathExt using %PATHEXT% but also capitalise them - if (cmd.split(".")[1] === undefined) - _TVDOS.variables.PATHEXT.split(';').forEach(function(it) { pathExt.push(it); pathExt.push(it.toUpperCase()); }) - else - pathExt.push("") // final empty extension + // if the file is absolute path: + if (shell.isValidDriveLetter(cmd[0]) && cmd[1] == ':') { + searchFile = files.open(cmd) + searchPath = trimStartRevSlash(searchFile.path) + fileExists = searchFile.exists + } + // else + else { + var searchDir = (cmd.startsWith("/")) ? [""] : ["/"+shell_pwd.join("/")].concat(_TVDOS.getPath()) - let searchPath = "" - let searchFile = 0 + var pathExt = [] // it seems Nashorn does not like 'let' too much? this line gets ignored sometimes + // fill pathExt using %PATHEXT% but also capitalise them + if (cmd.split(".")[1] === undefined) + _TVDOS.variables.PATHEXT.split(';').forEach(function(it) { pathExt.push(it); pathExt.push(it.toUpperCase()); }) + else + pathExt.push("") // final empty extension - searchLoop: - for (var i = 0; i < searchDir.length; i++) { - for (var j = 0; j < pathExt.length; j++) { - let search = searchDir[i]; if (!search.endsWith('\\')) search += '\\' - searchPath = trimStartRevSlash(search + cmd + pathExt[j]) + searchLoop: + for (var i = 0; i < searchDir.length; i++) { + for (var j = 0; j < pathExt.length; j++) { + let search = searchDir[i]; if (!search.endsWith('\\')) search += '\\' + searchPath = trimStartRevSlash(search + cmd + pathExt[j]) -// debugprintln("[shell.execute] file search path: "+searchPath) + // debugprintln("[shell.execute] file search path: "+searchPath) - searchFile = files.open(`${CURRENT_DRIVE}:\\${searchPath}`) - if (searchFile.exists) { - fileExists = true - break searchLoop + searchFile = files.open(`${CURRENT_DRIVE}:\\${searchPath}`) + if (searchFile.exists) { + fileExists = true + break searchLoop + } } } } diff --git a/assets/disk0/tvdos/bin/playmov.js b/assets/disk0/tvdos/bin/playmov.js index b026d49..1788128 100644 --- a/assets/disk0/tvdos/bin/playmov.js +++ b/assets/disk0/tvdos/bin/playmov.js @@ -247,7 +247,7 @@ while (!stopPlay && seqread.getReadCount() < FILE_LENGTH) { let endTime = sys.nanoTime() sys.free(ipfbuf) -audio.stop(0) +//audio.stop(0) let timeTook = (endTime - startTime) / 1000000000.0 diff --git a/assets/disk0/tvdos/bin/playmp3.js b/assets/disk0/tvdos/bin/playmp3.js new file mode 100644 index 0000000..ef2aa26 --- /dev/null +++ b/assets/disk0/tvdos/bin/playmp3.js @@ -0,0 +1,227 @@ +const Mp3 = require('mp3dec') +const pcm = require("pcm") +const interactive = exec_args[2] && exec_args[2].toLowerCase() == "/i" + +function printdbg(s) { if (0) serial.println(s) } + +class SequentialFileBuffer { + + constructor(path, offset, length) { + if (Array.isArray(path)) throw Error("arg #1 is path(string), not array") + + this.path = path + this.file = files.open(path) + + this.offset = offset || 0 + this.originalOffset = offset + this.length = length || this.file.size + + this.seq = require("seqread") + this.seq.prepare(path) + } + + /*readFull(n) { + throw Error() + let ptr = this.seq.readBytes(n) + return ptr + }*/ + + readStr(n) { + let ptr = this.seq.readBytes(n) + let s = '' + for (let i = 0; i < n; i++) { + if (i >= this.length) break + s += String.fromCharCode(sys.peek(ptr + i)) + } + sys.free(ptr) + return s + } + + readByteNumbers(n) { + let ptr = this.seq.readBytes(n) + try { + let s = [] + for (let i = 0; i < n; i++) { + if (i >= this.length) break + s.push(sys.peek(ptr + i)) + } + sys.free(ptr) + return s + } + catch (e) { + println(`n: ${n}; ptr: ${ptr}`) + println(e) + } + } + + unread(diff) { + let newSkipLen = this.seq.getReadCount() - diff + this.seq.prepare(this.path) + this.seq.skip(newSkipLen) + } + + rewind() { + this.seq.prepare(this.path) + } + + seek(p) { + this.seq.prepare(this.path) + this.seq.skip(p) + } + + get byteLength() { + return this.length + } + + /*get remaining() { + return this.length - this.getReadCount() + }*/ +} + + + +con.curs_set(0) +let [cy, cx] = con.getyx() +let [__, CONSOLE_WIDTH] = con.getmaxyx() +let paintWidth = CONSOLE_WIDTH - 16 +if (interactive) { + println("Decoding...") +} + + +printdbg("pre-decode...") +let filebuf = new SequentialFileBuffer(_G.shell.resolvePathInput(exec_args[1]).full) +const FILE_SIZE = filebuf.length +let decoder = Mp3.newDecoder(filebuf) +if (decoder === null) throw Error("decoder is null") + +const HEADER_SIZE = decoder.headerSize + 3 +const FRAME_SIZE = decoder.frameSize // only works reliably for CBR + +//serial.println(`header size: ${HEADER_SIZE}`) +//serial.println(`frame size: ${FRAME_SIZE}`) + +audio.resetParams(0) +audio.purgeQueue(0) +audio.setPcmMode(0) +audio.setPcmQueueCapacityIndex(0, 5) // queue size is now 24 +const QUEUE_MAX = audio.getPcmQueueCapacity(0) +audio.setMasterVolume(0, 255) +audio.play(0) + +let decodedLength = 0 +let readPtr = sys.malloc(8000) +let decodePtr = sys.malloc(12000) + +function bytesToSec(i) { + // using fixed value: FRAME_SIZE(216) bytes for 36 ms on sampling rate 32000 Hz + return i / (FRAME_SIZE * 1000 / bufRealTimeLen) +} +function secToReadable(n) { + let mins = ''+((n/60)|0) + let secs = ''+(n % 60) + return `${mins.padStart(2,'0')}:${secs.padStart(2,'0')}` +} +function decodeAndResample(inPtr, outPtr, inputLen) { + // TODO resample + for (let k = 0; k < inputLen / 2; k+=2) { + let sample = [ + pcm.u16Tos16(sys.peek(inPtr + k*2 + 0) | (sys.peek(inPtr + k*2 + 1) << 8)), + pcm.u16Tos16(sys.peek(inPtr + k*2 + 2) | (sys.peek(inPtr + k*2 + 3) << 8)) + ] + sys.poke(outPtr + k, pcm.s16Tou8(sample[0])) + sys.poke(outPtr + k + 1, pcm.s16Tou8(sample[1])) + // soothing visualiser(????) +// printvis(`${sampleToVisual(sample[0])} | ${sampleToVisual(sample[1])}`) + } + return inputLen / 2 +} + + +function printPlayBar() { +} + +let stopPlay = false +con.curs_set(0) +if (interactive) { + con.move(cy, cy) + println("Push and hold Backspace to exit") +} +[cy, cx] = con.getyx() +function printPlayBar(currently) { + if (interactive) { +// let currently = decodedLength + let total = FILE_SIZE - HEADER_SIZE + + let currentlySec = Math.round(bytesToSec(currently)) + let totalSec = Math.round(bytesToSec(total)) + + con.move(cy, 1) + print(' '.repeat(15)) + con.move(cy, 1) + + print(`${secToReadable(currentlySec)} / ${secToReadable(totalSec)}`) + + con.move(cy, 15) + print(' ') + let progressbar = '\x84205u'.repeat(paintWidth + 1) + print(progressbar) + + con.mvaddch(cy, 16 + Math.round(paintWidth * (currently / total)), 0xDB) + } +} +let t1 = sys.nanoTime() +let bufRealTimeLen = 36 +try { + decoder.decode((ptr, len, pos)=>{ + + if (interactive) { + sys.poke(-40, 1) + if (sys.peek(-41) == 67) { + stopPlay = true + throw "STOP" + } + } + + printPlayBar(pos) + + let t2 = sys.nanoTime() + + decodedLength += len + +// serial.println(`Audio queue size: ${audio.getPosition(0)}/${QUEUE_MAX}`) + + if (audio.getPosition(0) >= QUEUE_MAX) { + while (audio.getPosition(0) >= (QUEUE_MAX >>> 1)) { + printdbg(`Queue full, waiting until the queue has some space (${audio.getPosition(0)}/${QUEUE_MAX})`) +// serial.println(`Queue full, waiting until the queue has some space (${audio.getPosition(0)}/${QUEUE_MAX})`) + sys.sleep(bufRealTimeLen) + } + } + + + + let declen = decodeAndResample(ptr, decodePtr, len) + + audio.putPcmDataByPtr(decodePtr, declen, 0) + audio.setSampleUploadLength(0, declen) + audio.startSampleUpload(0) + + + let decodingTime = (t2 - t1) / 1000000.0 + bufRealTimeLen = (declen) / 64000.0 * 1000 + t1 = t2 + + printdbg(`Decoded ${decodedLength} bytes; target: ${bufRealTimeLen} ms, lag: ${decodingTime - bufRealTimeLen} ms`) + + + }) // now you got decoded PCM data +} +catch (e) { + if (e != "STOP") throw e +} +finally { + //audio.stop(0) + sys.free(readPtr) + sys.free(decodePtr) +} \ No newline at end of file diff --git a/assets/disk0/tvdos/bin/playpcm.js b/assets/disk0/tvdos/bin/playpcm.js index 3950438..3f42e5e 100644 --- a/assets/disk0/tvdos/bin/playpcm.js +++ b/assets/disk0/tvdos/bin/playpcm.js @@ -86,10 +86,10 @@ audio.setMasterVolume(0, 255) let readLength = 1 -function printPlayBar(startOffset) { +function printPlayBar() { if (interactive) { - let currently = seqread.getReadCount() - startOffset - let total = FILE_SIZE - startOffset - 8 + let currently = seqread.getReadCount() + let total = FILE_SIZE let currentlySec = Math.round(bytesToSec(currently)) let totalSec = Math.round(bytesToSec(total)) @@ -157,6 +157,6 @@ while (!stopPlay && seqread.getReadCount() < FILE_SIZE && readLength > 0) { } -audio.stop(0) +//audio.stop(0) if (readPtr !== undefined) sys.free(readPtr) if (decodePtr !== undefined) sys.free(decodePtr) diff --git a/assets/disk0/tvdos/bin/playwav.js b/assets/disk0/tvdos/bin/playwav.js index 81e3241..9ce0bd0 100644 --- a/assets/disk0/tvdos/bin/playwav.js +++ b/assets/disk0/tvdos/bin/playwav.js @@ -268,6 +268,6 @@ while (!stopPlay && seqread.getReadCount() < FILE_SIZE - 8) { sys.spin() } -audio.stop(0) +//audio.stop(0) if (readPtr !== undefined) sys.free(readPtr) if (decodePtr !== undefined) sys.free(decodePtr) diff --git a/assets/disk0/tvdos/include/js-mp3/frame.js b/assets/disk0/tvdos/include/js-mp3/frame.js index c605f42..a0f63b8 100644 --- a/assets/disk0/tvdos/include/js-mp3/frame.js +++ b/assets/disk0/tvdos/include/js-mp3/frame.js @@ -198,12 +198,14 @@ var Frame = { */ frame.decode = function () { var nch = frame.header.numberOfChannels(); - var out; + var out_ptr; + var out_ptr_len; if (nch === 1) { - out = new Uint8Array(consts.BytesPerFrame / 2); + out_ptr_len = consts.BytesPerFrame / 2 } else { - out = new Uint8Array(consts.BytesPerFrame); + out_ptr_len = consts.BytesPerFrame } + out_ptr = sys.malloc(out_ptr_len); for (var gr = 0; gr < 2; gr++) { for (var ch = 0; ch < nch; ch++) { frame.requantize(gr, ch); @@ -212,16 +214,21 @@ var Frame = { frame.stereo(gr); for (var ch = 0; ch < nch; ch++) { frame.antialias(gr, ch); - frame.hybridSynthesis(gr, ch); + + audio.mp3_hybridSynthesis(frame.sideInfo, frame.mainData.Is, frame.store[ch], gr, ch) + //frame.hybridSynthesis(gr, ch); + frame.frequencyInversion(gr, ch); if (nch === 1) { - frame.subbandSynthesis(gr, ch, out.subarray(consts.SamplesPerGr * 4 * gr / 2)); + audio.mp3_subbandSynthesis(nch, frame, gr, ch, out_ptr + (consts.SamplesPerGr * 4 * gr / 2)) + //frame.subbandSynthesis(gr, ch, out_ptr + (consts.SamplesPerGr * 4 * gr / 2)); } else { - frame.subbandSynthesis(gr, ch, out.subarray(consts.SamplesPerGr * 4 * gr)); + audio.mp3_subbandSynthesis(nch, frame, gr, ch, out_ptr + (consts.SamplesPerGr * 4 * gr)) + //frame.subbandSynthesis(gr, ch, out_ptr + (consts.SamplesPerGr * 4 * gr)); } } } - return out; + return [out_ptr, out_ptr_len]; }; frame.antialias = function (gr, ch) { @@ -433,7 +440,7 @@ var Frame = { var win_len = consts.SfBandIndicesSet[sfreq].S[sfb + 1] - consts.SfBandIndicesSet[sfreq].S[sfb]; - for (var i = 36; i < int(f.sideInfo.Count1[gr][ch]);) /* i++ done below! */ { + for (var i = 36; i < int(frame.sideInfo.Count1[gr][ch]);) /* i++ done below! */ { // Check if we're into the next scalefac band if (i === next_sfb) { sfb++; @@ -573,7 +580,7 @@ var Frame = { } }; - frame.subbandSynthesis = function (gr, ch, out) { + frame.subbandSynthesis = function (gr, ch, out_ptr) { var u_vec = new Float32Array(512); var s_vec = new Float32Array(32); @@ -616,7 +623,7 @@ var Frame = { } else if (samp < -32767) { samp = -32767; } - var s = samp; + var s = samp|0; var idx; if (nch === 1) { idx = 2 * (32*ss + i); @@ -624,15 +631,19 @@ var Frame = { idx = 4 * (32*ss + i); } if (ch === 0) { - out[idx] = s; - out[idx + 1] = (s >>> 8) >>> 0; +// out[idx] = s; +// out[idx + 1] = (s >>> 8) >>> 0; + sys.poke(out_ptr + idx, s) + sys.poke(out_ptr + idx + 1, (s >>> 8) >>> 0) } else { - out[idx + 2] = s; - out[idx + 3] = (s >>> 8) >>> 0; +// out[idx + 2] = s; +// out[idx + 3] = (s >>> 8) >>> 0; + sys.poke(out_ptr + idx + 2, s) + sys.poke(out_ptr + idx + 3, (s >>> 8) >>> 0) } } } - return out; + return out_ptr; }; frame.samplingFrequency = function () { diff --git a/assets/disk0/tvdos/include/js-mp3/frameheader.js b/assets/disk0/tvdos/include/js-mp3/frameheader.js index 9d7d7d8..9e20e0b 100644 --- a/assets/disk0/tvdos/include/js-mp3/frameheader.js +++ b/assets/disk0/tvdos/include/js-mp3/frameheader.js @@ -216,6 +216,11 @@ var Frameheader = { var pos = position; var buf = source.readFull(4) + if (buf.err) { + return { + err: buf.err + } + } if (buf.length < 4) { return { h: 0, diff --git a/assets/disk0/tvdos/include/mp3dec.js b/assets/disk0/tvdos/include/mp3dec.js index cbadafe..20a5096 100644 --- a/assets/disk0/tvdos/include/mp3dec.js +++ b/assets/disk0/tvdos/include/mp3dec.js @@ -61,13 +61,16 @@ var Mp3 = { }; source.readFull = function (length) { - if (length < 0) throw Error("Source.pos less than 0: "+source.pos) var l = Math.min(source.buf.byteLength - source.pos, length); - if (l < 0) { - serial.println("l < 0: "+l) - throw Error("l < 0: "+l) + if (length <= 0 || l <= 0 || length === undefined) { + return { err: ("Source.pos less than 0: "+source.pos) } + } + + if (l <= 0) { + serial.println("l <= 0: "+l) + throw Error("l <= 0: "+l) } // serial.println(`readFull(${length} -> ${l}); pos: ${source.pos}`) @@ -148,25 +151,35 @@ var Mp3 = { err: result.err } } + decoder.frame = result.f; - var pcm_buf = decoder.frame.decode(); + var [pcm_buf_ptr, pcm_buf_len] = decoder.frame.decode(); // decoder.buf = util.concatBuffers(decoder.buf, pcm_buf); - return { buf: pcm_buf }; + +// serial.println(`End of decoder.readFrame; ptr: ${pcm_buf_ptr} len: ${pcm_buf_len}`) + + return { + ptr: pcm_buf_ptr, + len: pcm_buf_len, + pos: result.position + }; }; decoder.decode = function (callback) { var result; - serial.println("Start decoding") +// serial.println("Start decoding") while(true) { result = decoder.readFrame(); - - if (typeof callback == "function") callback(result) - if (result.err) { break; } + + if (typeof callback == "function") callback(result.ptr, result.len, result.pos) + + sys.free(result.ptr) + } // return decoder.buf; }; @@ -230,6 +243,9 @@ var Mp3 = { return null; } + + + // serial.println("Reading first frame") var result = decoder.readFrame(); @@ -240,13 +256,15 @@ var Mp3 = { // serial.println("First frame finished reading") + decoder.headerSize = decoder.source.pos decoder.sampleRate = decoder.frame.samplingFrequency(); + decoder.frameSize = decoder.frame.header.frameSize() - serial.println("Sampling rate: "+decoder.sampleRate + " Hz") +// serial.println("Sampling rate: "+decoder.sampleRate + " Hz") result = decoder.ensureFrameStartsAndLength(); - serial.println("Decode end") +// serial.println("Decode end") if (result.err) { throw Error(`Error ensuring Frame starts and length: ${result.err}`) return null; diff --git a/assets/disk0/tvdos/tuidev/zfm.js b/assets/disk0/tvdos/tuidev/zfm.js index d33cb97..89af466 100644 --- a/assets/disk0/tvdos/tuidev/zfm.js +++ b/assets/disk0/tvdos/tuidev/zfm.js @@ -24,7 +24,10 @@ const COL_HL_EXT = { "bas": 215, "bat": 215, "wav": 31, - "adpcm": 32, + "adpcm": 31, + "pcm": 33, + "mp3": 34, + "mp2": 34, "mov": 213, "ipf1": 190, "ipf2": 191, @@ -35,6 +38,7 @@ const COL_HL_EXT = { const EXEC_FUNS = { "wav": (f) => _G.shell.execute(`playwav ${f} /i`), "adpcm": (f) => _G.shell.execute(`playwav ${f} /i`), + "mp3": (f) => _G.shell.execute(`playmp3 ${f} /i`), "mov": (f) => _G.shell.execute(`playmov ${f} /i`), "pcm": (f) => _G.shell.execute(`playpcm ${f} /i`), "ipf1": (f) => _G.shell.execute(`decodeipf ${f} /i`), @@ -46,7 +50,7 @@ let windowMode = 0 // 0 == left, 1 == right let windowFocus = 0 // 0: files window, 1: palette window, 2: popup window // window states -let path = [["A:"], ["A:"]] +let path = [["A:", "home"], ["A:"]] let scroll = [0, 0] let dirFileList = [[], []] let cursor = [0, 0] // absolute position! diff --git a/terranmon.txt b/terranmon.txt index 59fae2f..461ccbb 100644 --- a/terranmon.txt +++ b/terranmon.txt @@ -586,7 +586,7 @@ Sound Adapter MMIO 32 ??: ??? Sound Hardware Info - - Sampling rate: 30000 Hz + - Sampling rate: 32000 Hz - Bit depth: 8 bits/sample, unsigned - Always operate in stereo (mono samples must be expanded to stereo before uploading) diff --git a/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt b/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt index 84a7779..967badb 100644 --- a/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt +++ b/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt @@ -1,6 +1,11 @@ package net.torvald.tsvm +import com.oracle.truffle.regex.util.TruffleReadOnlyMap +import javazoom.jl.decoder.LayerIIIDecoder.pretab import net.torvald.tsvm.peripheral.AudioAdapter +import org.graalvm.polyglot.Value +import kotlin.math.cos +import kotlin.math.sign /** * Created by minjaesong on 2022-12-31. @@ -13,13 +18,13 @@ class AudioJSR223Delegate(private val vm: VM) { return a } private fun getPlayhead(playhead: Int) = getFirstSnd()?.playheads?.get(playhead) - + fun setPcmMode(playhead: Int) { getPlayhead(playhead)?.isPcmMode = true } fun isPcmMode(playhead: Int) = getPlayhead(playhead)?.isPcmMode == true - + fun setTrackerMode(playhead: Int) { getPlayhead(playhead)?.isPcmMode = false } fun isTrackerMode(playhead: Int) = getPlayhead(playhead)?.isPcmMode == false - + fun setMasterVolume(playhead: Int, volume: Int) { getPlayhead(playhead)?.apply { masterVolume = volume and 255 audioDevice.setVolume(masterVolume / 255f) @@ -61,8 +66,8 @@ class AudioJSR223Delegate(private val vm: VM) { fun getPcmData(index: Int) = getFirstSnd()?.pcmBin?.get(index.toLong()) fun setPcmQueueCapacityIndex(playhead: Int, index: Int) { getPlayhead(playhead)?.pcmQueueSizeIndex = index } - fun getPcmQueueCapacityIndex(playhead: Int, index: Int) { getPlayhead(playhead)?.pcmQueueSizeIndex } - fun getPcmQueueCapacity(playhead: Int, index: Int) { getPlayhead(playhead)?.getPcmQueueCapacity() } + fun getPcmQueueCapacityIndex(playhead: Int) { getPlayhead(playhead)?.pcmQueueSizeIndex } + fun getPcmQueueCapacity(playhead: Int) = getPlayhead(playhead)?.getPcmQueueCapacity() fun resetParams(playhead: Int) { getPlayhead(playhead)?.resetParams() @@ -71,4 +76,329 @@ class AudioJSR223Delegate(private val vm: VM) { fun purgeQueue(playhead: Int) { getPlayhead(playhead)?.purgeQueue() } + + + + + + + private val synthNWin = Array(64) { i -> FloatArray(32) { j -> cos(((16 + i) * (2 * j + 1)) * (Math.PI / 64.0)).toFloat() } } + private val synthDtbl = floatArrayOf(0.000000000f, -0.000015259f, -0.000015259f, -0.000015259f, + -0.000015259f, -0.000015259f, -0.000015259f, -0.000030518f, + -0.000030518f, -0.000030518f, -0.000030518f, -0.000045776f, + -0.000045776f, -0.000061035f, -0.000061035f, -0.000076294f, + -0.000076294f, -0.000091553f, -0.000106812f, -0.000106812f, + -0.000122070f, -0.000137329f, -0.000152588f, -0.000167847f, + -0.000198364f, -0.000213623f, -0.000244141f, -0.000259399f, + -0.000289917f, -0.000320435f, -0.000366211f, -0.000396729f, + -0.000442505f, -0.000473022f, -0.000534058f, -0.000579834f, + -0.000625610f, -0.000686646f, -0.000747681f, -0.000808716f, + -0.000885010f, -0.000961304f, -0.001037598f, -0.001113892f, + -0.001205444f, -0.001296997f, -0.001388550f, -0.001480103f, + -0.001586914f, -0.001693726f, -0.001785278f, -0.001907349f, + -0.002014160f, -0.002120972f, -0.002243042f, -0.002349854f, + -0.002456665f, -0.002578735f, -0.002685547f, -0.002792358f, + -0.002899170f, -0.002990723f, -0.003082275f, -0.003173828f, + 0.003250122f, 0.003326416f, 0.003387451f, 0.003433228f, + 0.003463745f, 0.003479004f, 0.003479004f, 0.003463745f, + 0.003417969f, 0.003372192f, 0.003280640f, 0.003173828f, + 0.003051758f, 0.002883911f, 0.002700806f, 0.002487183f, + 0.002227783f, 0.001937866f, 0.001617432f, 0.001266479f, + 0.000869751f, 0.000442505f, -0.000030518f, -0.000549316f, + -0.001098633f, -0.001693726f, -0.002334595f, -0.003005981f, + -0.003723145f, -0.004486084f, -0.005294800f, -0.006118774f, + -0.007003784f, -0.007919312f, -0.008865356f, -0.009841919f, + -0.010848999f, -0.011886597f, -0.012939453f, -0.014022827f, + -0.015121460f, -0.016235352f, -0.017349243f, -0.018463135f, + -0.019577026f, -0.020690918f, -0.021789551f, -0.022857666f, + -0.023910522f, -0.024932861f, -0.025909424f, -0.026840210f, + -0.027725220f, -0.028533936f, -0.029281616f, -0.029937744f, + -0.030532837f, -0.031005859f, -0.031387329f, -0.031661987f, + -0.031814575f, -0.031845093f, -0.031738281f, -0.031478882f, + 0.031082153f, 0.030517578f, 0.029785156f, 0.028884888f, + 0.027801514f, 0.026535034f, 0.025085449f, 0.023422241f, + 0.021575928f, 0.019531250f, 0.017257690f, 0.014801025f, + 0.012115479f, 0.009231567f, 0.006134033f, 0.002822876f, + -0.000686646f, -0.004394531f, -0.008316040f, -0.012420654f, + -0.016708374f, -0.021179199f, -0.025817871f, -0.030609131f, + -0.035552979f, -0.040634155f, -0.045837402f, -0.051132202f, + -0.056533813f, -0.061996460f, -0.067520142f, -0.073059082f, + -0.078628540f, -0.084182739f, -0.089706421f, -0.095169067f, + -0.100540161f, -0.105819702f, -0.110946655f, -0.115921021f, + -0.120697021f, -0.125259399f, -0.129562378f, -0.133590698f, + -0.137298584f, -0.140670776f, -0.143676758f, -0.146255493f, + -0.148422241f, -0.150115967f, -0.151306152f, -0.151962280f, + -0.152069092f, -0.151596069f, -0.150497437f, -0.148773193f, + -0.146362305f, -0.143264771f, -0.139450073f, -0.134887695f, + -0.129577637f, -0.123474121f, -0.116577148f, -0.108856201f, + 0.100311279f, 0.090927124f, 0.080688477f, 0.069595337f, + 0.057617188f, 0.044784546f, 0.031082153f, 0.016510010f, + 0.001068115f, -0.015228271f, -0.032379150f, -0.050354004f, + -0.069168091f, -0.088775635f, -0.109161377f, -0.130310059f, + -0.152206421f, -0.174789429f, -0.198059082f, -0.221984863f, + -0.246505737f, -0.271591187f, -0.297210693f, -0.323318481f, + -0.349868774f, -0.376800537f, -0.404083252f, -0.431655884f, + -0.459472656f, -0.487472534f, -0.515609741f, -0.543823242f, + -0.572036743f, -0.600219727f, -0.628295898f, -0.656219482f, + -0.683914185f, -0.711318970f, -0.738372803f, -0.765029907f, + -0.791213989f, -0.816864014f, -0.841949463f, -0.866363525f, + -0.890090942f, -0.913055420f, -0.935195923f, -0.956481934f, + -0.976852417f, -0.996246338f, -1.014617920f, -1.031936646f, + -1.048156738f, -1.063217163f, -1.077117920f, -1.089782715f, + -1.101211548f, -1.111373901f, -1.120223999f, -1.127746582f, + -1.133926392f, -1.138763428f, -1.142211914f, -1.144287109f, + 1.144989014f, 1.144287109f, 1.142211914f, 1.138763428f, + 1.133926392f, 1.127746582f, 1.120223999f, 1.111373901f, + 1.101211548f, 1.089782715f, 1.077117920f, 1.063217163f, + 1.048156738f, 1.031936646f, 1.014617920f, 0.996246338f, + 0.976852417f, 0.956481934f, 0.935195923f, 0.913055420f, + 0.890090942f, 0.866363525f, 0.841949463f, 0.816864014f, + 0.791213989f, 0.765029907f, 0.738372803f, 0.711318970f, + 0.683914185f, 0.656219482f, 0.628295898f, 0.600219727f, + 0.572036743f, 0.543823242f, 0.515609741f, 0.487472534f, + 0.459472656f, 0.431655884f, 0.404083252f, 0.376800537f, + 0.349868774f, 0.323318481f, 0.297210693f, 0.271591187f, + 0.246505737f, 0.221984863f, 0.198059082f, 0.174789429f, + 0.152206421f, 0.130310059f, 0.109161377f, 0.088775635f, + 0.069168091f, 0.050354004f, 0.032379150f, 0.015228271f, + -0.001068115f, -0.016510010f, -0.031082153f, -0.044784546f, + -0.057617188f, -0.069595337f, -0.080688477f, -0.090927124f, + 0.100311279f, 0.108856201f, 0.116577148f, 0.123474121f, + 0.129577637f, 0.134887695f, 0.139450073f, 0.143264771f, + 0.146362305f, 0.148773193f, 0.150497437f, 0.151596069f, + 0.152069092f, 0.151962280f, 0.151306152f, 0.150115967f, + 0.148422241f, 0.146255493f, 0.143676758f, 0.140670776f, + 0.137298584f, 0.133590698f, 0.129562378f, 0.125259399f, + 0.120697021f, 0.115921021f, 0.110946655f, 0.105819702f, + 0.100540161f, 0.095169067f, 0.089706421f, 0.084182739f, + 0.078628540f, 0.073059082f, 0.067520142f, 0.061996460f, + 0.056533813f, 0.051132202f, 0.045837402f, 0.040634155f, + 0.035552979f, 0.030609131f, 0.025817871f, 0.021179199f, + 0.016708374f, 0.012420654f, 0.008316040f, 0.004394531f, + 0.000686646f, -0.002822876f, -0.006134033f, -0.009231567f, + -0.012115479f, -0.014801025f, -0.017257690f, -0.019531250f, + -0.021575928f, -0.023422241f, -0.025085449f, -0.026535034f, + -0.027801514f, -0.028884888f, -0.029785156f, -0.030517578f, + 0.031082153f, 0.031478882f, 0.031738281f, 0.031845093f, + 0.031814575f, 0.031661987f, 0.031387329f, 0.031005859f, + 0.030532837f, 0.029937744f, 0.029281616f, 0.028533936f, + 0.027725220f, 0.026840210f, 0.025909424f, 0.024932861f, + 0.023910522f, 0.022857666f, 0.021789551f, 0.020690918f, + 0.019577026f, 0.018463135f, 0.017349243f, 0.016235352f, + 0.015121460f, 0.014022827f, 0.012939453f, 0.011886597f, + 0.010848999f, 0.009841919f, 0.008865356f, 0.007919312f, + 0.007003784f, 0.006118774f, 0.005294800f, 0.004486084f, + 0.003723145f, 0.003005981f, 0.002334595f, 0.001693726f, + 0.001098633f, 0.000549316f, 0.000030518f, -0.000442505f, + -0.000869751f, -0.001266479f, -0.001617432f, -0.001937866f, + -0.002227783f, -0.002487183f, -0.002700806f, -0.002883911f, + -0.003051758f, -0.003173828f, -0.003280640f, -0.003372192f, + -0.003417969f, -0.003463745f, -0.003479004f, -0.003479004f, + -0.003463745f, -0.003433228f, -0.003387451f, -0.003326416f, + 0.003250122f, 0.003173828f, 0.003082275f, 0.002990723f, + 0.002899170f, 0.002792358f, 0.002685547f, 0.002578735f, + 0.002456665f, 0.002349854f, 0.002243042f, 0.002120972f, + 0.002014160f, 0.001907349f, 0.001785278f, 0.001693726f, + 0.001586914f, 0.001480103f, 0.001388550f, 0.001296997f, + 0.001205444f, 0.001113892f, 0.001037598f, 0.000961304f, + 0.000885010f, 0.000808716f, 0.000747681f, 0.000686646f, + 0.000625610f, 0.000579834f, 0.000534058f, 0.000473022f, + 0.000442505f, 0.000396729f, 0.000366211f, 0.000320435f, + 0.000289917f, 0.000259399f, 0.000244141f, 0.000213623f, + 0.000198364f, 0.000167847f, 0.000152588f, 0.000137329f, + 0.000122070f, 0.000106812f, 0.000106812f, 0.000091553f, + 0.000076294f, 0.000076294f, 0.000061035f, 0.000061035f, + 0.000045776f, 0.000045776f, 0.000030518f, 0.000030518f, + 0.000030518f, 0.000030518f, 0.000015259f, 0.000015259f, + 0.000015259f, 0.000015259f, 0.000015259f, 0.000015259f,) + + private val imdctWinData = Array(4) { DoubleArray(36) } + private val cosN12 = Array(6) { DoubleArray(12) } + private val cosN36 = Array(18) { DoubleArray(36) } + + init { + for (i in 0 until 36) { + imdctWinData[0][i] = Math.sin(Math.PI / 36 * (i + 0.5)); + } + for (i in 0 until 18) { + imdctWinData[1][i] = Math.sin(Math.PI / 36 * (i + 0.5)); + } + for (i in 18 until 24) { + imdctWinData[1][i] = 1.0; + } + for (i in 24 until 30) { + imdctWinData[1][i] = Math.sin(Math.PI / 12 * (i + 0.5 - 18.0)); + } + for (i in 30 until 36) { + imdctWinData[1][i] = 0.0; + } + for (i in 0 until 12) { + imdctWinData[2][i] = Math.sin(Math.PI / 12 * (i + 0.5)); + } + for (i in 12 until 36) { + imdctWinData[2][i] = 0.0; + } + for (i in 0 until 6) { + imdctWinData[3][i] = 0.0; + } + for (i in 6 until 12) { + imdctWinData[3][i] = Math.sin(Math.PI / 12 * (i + 0.5 - 6.0)); + } + for (i in 12 until 18) { + imdctWinData[3][i] = 1.0; + } + for (i in 18 until 36) { + imdctWinData[3][i] = Math.sin(Math.PI / 36 * (i + 0.5)); + } + + val cosN12_N = 12 + for (i in 0 until 6) { + for (j in 0 until 12) { + cosN12[i][j] = Math.cos(Math.PI / (2 * cosN12_N) * (2*j + 1 + cosN12_N/2) * (2*i + 1)); + } + } + + val cosN36_N = 36 + for (i in 0 until 18) { + for (j in 0 until 36) { + cosN36[i][j] = Math.cos(Math.PI / (2 * cosN36_N) * (2*j + 1 + cosN36_N/2) * (2*i + 1)); + } + } + } + + private fun ImdctWin(inData: DoubleArray, blockType: Int): DoubleArray { + val out = DoubleArray(36) + + if (blockType == 2) { + val iwd = imdctWinData[blockType]; + val N = 12; + for (i in 0 until 3) { + for (p in 0 until N) { + var sum = 0.0; + for (m in 0 until N/2) { + sum += inData[i+3*m] * cosN12[m][p]; + } + out[6*i+p+6] += sum * iwd[p]; + } + } + return out; + } + + val N = 36; + val iwd = imdctWinData[blockType] + for (p in 0 until N) { + var sum = 0.0; + for (m in 0 until N/2) { + sum += inData[m] * cosN36[m][p]; + } + out[p] = sum * iwd[p]; + } + return out; + } + + + + + private fun FloatArray.typedArraySet(xs: List, index: Int) { + for (i in xs.indices) { + this[i + index] = xs[i] + } + } + + private fun Value.grch(gr: Long, ch: Long) = this.getArrayElement(gr).getArrayElement(ch) + + fun mp3_hybridSynthesis(sideInfo: Value, mainDataIs: Value, storeCh: Value, gr: Long, ch: Long) { + // Loop through all 32 subbands + for (sb in 0 until 32) { + // Determine blocktype for this subband + var bt = sideInfo.getMember("BlockType").grch(gr,ch).asInt(); + if ((sideInfo.getMember("WinSwitchFlag").grch(gr,ch).asInt() == 1) && + (sideInfo.getMember("MixedBlockFlag").grch(gr,ch).asInt() == 1) && (sb < 2)) { + bt = 0; + } + // Do the inverse modified DCT and windowing + val inData = DoubleArray(18) + for (i in 0 until 18) { + inData[i] = mainDataIs.grch(gr,ch).getArrayElement(sb * 18L + i).asDouble() + } + val rawout = ImdctWin(inData, bt); + // Overlapp add with stored vector into main_data vector + for (i in 0L until 18L) { + val storeChSb = storeCh.getArrayElement(sb.toLong()) + + mainDataIs.grch(gr,ch).setArrayElement(sb * 18 + i, rawout[i.toInt()] + storeChSb.getArrayElement(i).asDouble()) + storeChSb.setArrayElement(i, rawout[i.toInt() + 18]) + } + } + } + + fun mp3_subbandSynthesis(nch: Int, frame: Value, gr: Long, ch: Long, out_ptr: Int) { + val u_vec = FloatArray(512) + val s_vec = FloatArray(32) + + val frameV_vec_ch = frame.getMember("v_vec").getArrayElement(ch) + val d = frame.getMember("mainData").getMember("Is").grch(gr,ch) + + // Setup the n_win windowing vector and the v_vec intermediate vector + for (ss in 0 until 18) { // Loop through 18 samples in 32 subbands + // v_vec: Array(2) + // v_vec[ch]: Float32Array(1024) -- instance of TypedArray + frameV_vec_ch.invokeMember("set", + frameV_vec_ch.invokeMember("slice", 0, 1024 - 64), + 64 + ) + //frame.v_vec[ch].set(frame.v_vec[ch].slice(0, 1024 - 64), 64); // copy(f.v_vec[ch][64:1024], + // f.v_vec[ch][0:1024-64]) + + //var d = frame.mainData.Is[gr][ch]; + for (i in 0 until 32) { // Copy next 32 time samples to a temp vector + s_vec[i] = d.getArrayElement(i * 18L + ss).asDouble().toFloat() + //s_vec[i] = d[i * 18 + ss]; + } + for (i in 0 until 64) { // Matrix multiply input with n_win[][] matrix + var sum = 0f + for (j in 0 until 32) { + sum += synthNWin[i][j] * s_vec[j]; + } + frameV_vec_ch.setArrayElement(i.toLong(), sum) + //frame.v_vec[ch][i] = sum; + } + + val v = frameV_vec_ch + //var v = frame.v_vec[ch]; + for (i in 0 until 512 step 64) { // Build the U vector + u_vec.typedArraySet(((i shl 1) until (i shl 1) + 32).map { v.getArrayElement(it.toLong()).asDouble().toFloat() }, i) + //u_vec.set(v.slice((i shl 1), (i shl 1) + 32), i); // copy(u_vec[i:i+32], + // v[(i<<1):(i<<1)+32]) + + u_vec.typedArraySet(((i shl 1) + 96 until (i shl 1) + 128).map { v.getArrayElement(it.toLong()).asDouble().toFloat() }, i + 32) + //u_vec.set(v.slice((i shl 1) + 96, (i shl 1) + 128), i + 32); // copy(u_vec[i+32:i+64], + // v[(i<<1)+96:(i<<1)+128]) + } + for (i in 0 until 512) { // Window by u_vec[i] with synthDtbl[i] + u_vec[i] *= synthDtbl[i]; + } + for (i in 0 until 32) { // Calc 32 samples,store in outdata vector + var sum = 0f + for (j in 0 until 512 step 32) { + sum += u_vec[j + i]; + } + // sum now contains time sample 32*ss+i. Convert to 16-bit signed int + val samp = (sum * 32767).coerceIn(-32767f, 32767f) + val s = samp.toInt() + val idx = if (nch == 1) { + 2 * (32*ss + i) + } else { + 4 * (32*ss + i) + } + if (ch == 0L) { + vm.poke(out_ptr.toLong() + idx, s.toByte()) + vm.poke(out_ptr.toLong() + idx + 1, (s ushr 8).toByte()) + } else { + vm.poke(out_ptr.toLong() + idx + 2, s.toByte()) + vm.poke(out_ptr.toLong() + idx + 3, (s ushr 8).toByte()) + } + } + } + } } \ No newline at end of file