mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-21 11:44:04 +09:00
wav direct upload with bugfixes
This commit is contained in:
@@ -28,6 +28,10 @@ class SequentialFileBuffer {
|
||||
get fileHeader() { return this.seq.fileHeader }
|
||||
}
|
||||
|
||||
// Load the visualiser's font ROM now, while no audio file is streaming. The drive is
|
||||
// single-file-open, so loading it lazily during playback would corrupt the audio stream.
|
||||
if (gui) gui.preloadAssets()
|
||||
|
||||
const filebuf = new SequentialFileBuffer(_G.shell.resolvePathInput(exec_args[1]).full)
|
||||
const FILE_SIZE = filebuf.length
|
||||
const FRAME_SIZE = audio.mp2GetInitialFrameSize(filebuf.fileHeader)
|
||||
@@ -82,7 +86,12 @@ let stopPlay = false
|
||||
let errorlevel = 0
|
||||
try {
|
||||
while (bytes_left > 0 && !stopPlay) {
|
||||
if (interactive && gui.audioIsExitRequested()) { stopPlay = true; break }
|
||||
if (interactive && gui.audioIsExitRequested()) {
|
||||
// Stop immediately and drop everything still queued, so audio doesn't keep playing
|
||||
// the buffered chunks after the user quits.
|
||||
audio.stop(PLAYHEAD); audio.purgeQueue(PLAYHEAD)
|
||||
stopPlay = true; break
|
||||
}
|
||||
|
||||
filebuf.readBytes(FRAME_SIZE, SND_BASE_ADDR - 2368)
|
||||
audio.mp2Decode()
|
||||
@@ -101,7 +110,7 @@ try {
|
||||
sys.sleep(bufRealTimeLen)
|
||||
}
|
||||
}
|
||||
audio.mp2UploadDecoded(0)
|
||||
audio.mp2UploadDecoded(PLAYHEAD)
|
||||
|
||||
if (interactive) {
|
||||
gui.audioSetProgress(decodedLength / FILE_SIZE,
|
||||
@@ -115,8 +124,19 @@ try {
|
||||
}
|
||||
} catch (e) {
|
||||
printerrln(e)
|
||||
// Recover + show the host (Java) stack trace, which `e` alone does not carry.
|
||||
try { printerrln(sys.printStackTrace(e)) } catch (_) {}
|
||||
errorlevel = 1
|
||||
} finally {
|
||||
// Never leave the playhead in 'play' mode for the next program. On a clean finish, let the
|
||||
// queued tail play out first; on Backspace/error, stop immediately.
|
||||
if (!stopPlay && errorlevel === 0) {
|
||||
let guard = 0
|
||||
while (audio.getPosition(PLAYHEAD) > 0 && guard++ < 1500) sys.sleep(20) // drain, capped ~30s
|
||||
}
|
||||
audio.stop(PLAYHEAD)
|
||||
audio.purgeQueue(PLAYHEAD)
|
||||
|
||||
if (interactive) {
|
||||
if (mp2VisScratch) sys.free(mp2VisScratch)
|
||||
gui.audioClose()
|
||||
|
||||
@@ -20,6 +20,9 @@ const byterate = 2 * samplingRate
|
||||
|
||||
function bytesToSec(i) { return i / byterate }
|
||||
|
||||
// Load the visualiser's font ROM now, while no audio file is streaming (single-file-open drive).
|
||||
if (gui) gui.preloadAssets()
|
||||
|
||||
seqread.prepare(filePath)
|
||||
|
||||
const readPtr = sys.malloc(BLOCK_SIZE)
|
||||
@@ -44,29 +47,29 @@ let errorlevel = 0
|
||||
let readLength = 1
|
||||
try {
|
||||
while (!stopPlay && seqread.getReadCount() < FILE_SIZE && readLength > 0) {
|
||||
if (interactive && gui.audioIsExitRequested()) { stopPlay = true; break }
|
||||
|
||||
const queueSize = audio.getPosition(PLAYHEAD)
|
||||
if (queueSize <= 1) {
|
||||
for (let repeat = QUEUE_MAX - queueSize; repeat > 0; repeat--) {
|
||||
const remainingBytes = FILE_SIZE - seqread.getReadCount()
|
||||
readLength = (remainingBytes < INFILE_BLOCK_SIZE) ? remainingBytes : INFILE_BLOCK_SIZE
|
||||
if (readLength <= 0) break
|
||||
|
||||
seqread.readBytes(readLength, readPtr)
|
||||
|
||||
// Raw PCMu8 stereo — sampleCount = bytes / 2.
|
||||
if (interactive) gui.audioFeedPcm(readPtr, readLength >> 1)
|
||||
|
||||
audio.putPcmDataByPtr(PLAYHEAD, readPtr, readLength, 0)
|
||||
audio.setSampleUploadLength(PLAYHEAD, readLength)
|
||||
audio.startSampleUpload(PLAYHEAD)
|
||||
|
||||
if (repeat > 1) sys.sleep(10)
|
||||
}
|
||||
audio.play(PLAYHEAD)
|
||||
if (interactive && gui.audioIsExitRequested()) {
|
||||
// Stop immediately and drop everything still queued, so audio doesn't keep playing
|
||||
// the buffered chunks after the user quits.
|
||||
audio.stop(PLAYHEAD); audio.purgeQueue(PLAYHEAD)
|
||||
stopPlay = true; break
|
||||
}
|
||||
|
||||
// Top the queue up to QUEUE_MAX chunks with a DIRECT enqueue (no putPcmData/startUpload
|
||||
// handshake, no sys.sleep). The handshake dropped chunks under load → skips/fast-forward.
|
||||
while (audio.getPosition(PLAYHEAD) < QUEUE_MAX && seqread.getReadCount() < FILE_SIZE) {
|
||||
const remainingBytes = FILE_SIZE - seqread.getReadCount()
|
||||
readLength = (remainingBytes < INFILE_BLOCK_SIZE) ? remainingBytes : INFILE_BLOCK_SIZE
|
||||
if (readLength <= 0) break
|
||||
|
||||
seqread.readBytes(readLength, readPtr)
|
||||
|
||||
// Raw PCMu8 stereo — sampleCount = bytes / 2.
|
||||
if (interactive) gui.audioFeedPcm(readPtr, readLength >> 1)
|
||||
|
||||
audio.queuePcmDataByPtr(PLAYHEAD, readPtr, readLength)
|
||||
}
|
||||
audio.play(PLAYHEAD)
|
||||
|
||||
if (interactive) {
|
||||
const cur = seqread.getReadCount()
|
||||
gui.audioSetProgress(cur / FILE_SIZE, bytesToSec(cur), bytesToSec(FILE_SIZE))
|
||||
@@ -78,6 +81,15 @@ try {
|
||||
printerrln(e)
|
||||
errorlevel = 1
|
||||
} finally {
|
||||
// Never leave the playhead in 'play' mode for the next program. On a clean finish, let the
|
||||
// queued tail play out first; on Backspace/error, stop immediately.
|
||||
if (!stopPlay && errorlevel === 0) {
|
||||
let guard = 0
|
||||
while (audio.getPosition(PLAYHEAD) > 0 && guard++ < 1500) sys.sleep(20) // drain, capped ~30s
|
||||
}
|
||||
audio.stop(PLAYHEAD)
|
||||
audio.purgeQueue(PLAYHEAD)
|
||||
|
||||
if (readPtr !== undefined) sys.free(readPtr)
|
||||
if (interactive) gui.audioClose()
|
||||
}
|
||||
|
||||
@@ -60,6 +60,9 @@ class SequentialFileBuffer {
|
||||
getReadCount() { return this.seq.getReadCount() }
|
||||
}
|
||||
|
||||
// Load the visualiser's font ROM now, while no audio file is streaming (single-file-open drive).
|
||||
if (gui) gui.preloadAssets()
|
||||
|
||||
const filebuf = new SequentialFileBuffer(_G.shell.resolvePathInput(exec_args[1]).full)
|
||||
const FILE_SIZE = filebuf.length
|
||||
|
||||
@@ -132,7 +135,12 @@ let stopPlay = false
|
||||
let errorlevel = 0
|
||||
try {
|
||||
while (bytes_left > 0 && !stopPlay) {
|
||||
if (interactive && gui.audioIsExitRequested()) { stopPlay = true; break }
|
||||
if (interactive && gui.audioIsExitRequested()) {
|
||||
// Stop immediately and drop everything still queued, so audio doesn't keep playing
|
||||
// the buffered chunks after the user quits.
|
||||
audio.stop(PLAYHEAD); audio.purgeQueue(PLAYHEAD)
|
||||
stopPlay = true; break
|
||||
}
|
||||
|
||||
const sampleCount = filebuf.readShort()
|
||||
const maxIndex = filebuf.readByte()
|
||||
@@ -184,7 +192,10 @@ try {
|
||||
bytesToSec(decodedLength), bytesToSec(FILE_SIZE))
|
||||
let sliceOff = 0
|
||||
while (sliceOff < sampleCount && !stopPlay) {
|
||||
if (gui.audioIsExitRequested()) { stopPlay = true; break }
|
||||
if (gui.audioIsExitRequested()) {
|
||||
audio.stop(PLAYHEAD); audio.purgeQueue(PLAYHEAD)
|
||||
stopPlay = true; break
|
||||
}
|
||||
const sliceN = Math.min(TAD_VIS_SLICE, sampleCount - sliceOff)
|
||||
// tadDecodedBin is negative-addressed: sample i sits at
|
||||
// TAD_DECODED_ADDR - i*2. audioFeedPcm flips the read
|
||||
@@ -212,6 +223,15 @@ try {
|
||||
printerrln(e)
|
||||
errorlevel = 1
|
||||
} finally {
|
||||
// Never leave the playhead in 'play' mode for the next program. On a clean finish, let the
|
||||
// queued tail play out first; on Backspace/error, stop immediately.
|
||||
if (!stopPlay && errorlevel === 0) {
|
||||
let guard = 0
|
||||
while (audio.getPosition(PLAYHEAD) > 0 && guard++ < 1500) sys.sleep(20) // drain, capped ~30s
|
||||
}
|
||||
audio.stop(PLAYHEAD)
|
||||
audio.purgeQueue(PLAYHEAD)
|
||||
|
||||
if (interactive) gui.audioClose()
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,10 @@ function GCD(a, b) {
|
||||
}
|
||||
function LCM(a, b) { return (!a || !b) ? 0 : Math.abs((a * b) / GCD(a, b)) }
|
||||
|
||||
// Load the visualiser's font ROM now, while no audio file is streaming. The drive is
|
||||
// single-file-open, so loading it lazily during playback would corrupt the audio stream.
|
||||
if (gui) gui.preloadAssets()
|
||||
|
||||
seqread.prepare(filePath)
|
||||
if (seqread.readFourCC() !== "RIFF") throw Error("File not RIFF")
|
||||
const FILE_SIZE = seqread.readInt()
|
||||
@@ -142,30 +146,34 @@ try {
|
||||
|
||||
let readLength = 1
|
||||
while (!stopPlay && seqread.getReadCount() < startOffset + chunkSize && readLength > 0) {
|
||||
if (interactive && gui.audioIsExitRequested()) { stopPlay = true; break }
|
||||
|
||||
if (audio.getPosition(PLAYHEAD) <= 1) {
|
||||
for (let repeat = 0; repeat < QUEUE_MAX; repeat++) {
|
||||
const remainingBytes = FILE_SIZE - 8 - seqread.getReadCount()
|
||||
readLength = (remainingBytes < INFILE_BLOCK_SIZE) ? remainingBytes : INFILE_BLOCK_SIZE
|
||||
if (readLength <= 0) break
|
||||
|
||||
seqread.readBytes(readLength, readPtr)
|
||||
const decodedSampleLength = decodeInfilePcm(readPtr, decodePtr, readLength)
|
||||
|
||||
// Hand the decoded PCMu8 stereo block to the visualiser
|
||||
// before queueing — the buffer is reused next iteration.
|
||||
if (interactive) gui.audioFeedPcm(decodePtr, decodedSampleLength >> 1)
|
||||
|
||||
audio.putPcmDataByPtr(PLAYHEAD, decodePtr, decodedSampleLength, 0)
|
||||
audio.setSampleUploadLength(PLAYHEAD, decodedSampleLength)
|
||||
audio.startSampleUpload(PLAYHEAD)
|
||||
|
||||
sys.spin()
|
||||
}
|
||||
audio.play(PLAYHEAD)
|
||||
if (interactive && gui.audioIsExitRequested()) {
|
||||
// Stop immediately and drop everything still queued, so audio doesn't keep
|
||||
// playing the ~half-second of buffered chunks after the user quits.
|
||||
audio.stop(PLAYHEAD); audio.purgeQueue(PLAYHEAD)
|
||||
stopPlay = true; break
|
||||
}
|
||||
|
||||
// Top the queue up to QUEUE_MAX chunks with a DIRECT enqueue (no putPcmData/
|
||||
// setSampleUploadLength/startSampleUpload handshake, no sys.spin). The handshake
|
||||
// routes through a single-slot pcmBin and dropped chunks when fed in a burst, which
|
||||
// skipped/fast-forwarded the song. queuePcmDataByPtr enqueues synchronously.
|
||||
while (audio.getPosition(PLAYHEAD) < QUEUE_MAX &&
|
||||
seqread.getReadCount() < startOffset + chunkSize) {
|
||||
const remainingBytes = FILE_SIZE - 8 - seqread.getReadCount()
|
||||
readLength = (remainingBytes < INFILE_BLOCK_SIZE) ? remainingBytes : INFILE_BLOCK_SIZE
|
||||
if (readLength <= 0) break
|
||||
|
||||
seqread.readBytes(readLength, readPtr)
|
||||
const decodedSampleLength = decodeInfilePcm(readPtr, decodePtr, readLength)
|
||||
|
||||
// Hand the decoded PCMu8 stereo block to the visualiser before queueing —
|
||||
// the buffer is reused next iteration.
|
||||
if (interactive) gui.audioFeedPcm(decodePtr, decodedSampleLength >> 1)
|
||||
|
||||
audio.queuePcmDataByPtr(PLAYHEAD, decodePtr, decodedSampleLength)
|
||||
}
|
||||
audio.play(PLAYHEAD)
|
||||
|
||||
if (interactive) {
|
||||
const cur = seqread.getReadCount() - startOffset
|
||||
const tot = FILE_SIZE - startOffset - 8
|
||||
@@ -174,6 +182,15 @@ try {
|
||||
}
|
||||
sys.sleep(10)
|
||||
}
|
||||
|
||||
// Release the playhead so it isn't left in 'play' mode for the next program. On a clean
|
||||
// finish, let the queued tail play out first; on Backspace/error, stop immediately.
|
||||
if (!stopPlay && errorlevel === 0) {
|
||||
let guard = 0
|
||||
while (audio.getPosition(PLAYHEAD) > 0 && guard++ < 1500) sys.sleep(20) // drain, ~30s cap
|
||||
}
|
||||
audio.stop(PLAYHEAD)
|
||||
audio.purgeQueue(PLAYHEAD)
|
||||
}
|
||||
else {
|
||||
seqread.skip(chunkSize)
|
||||
@@ -183,6 +200,8 @@ try {
|
||||
}
|
||||
} catch (e) {
|
||||
printerrln(e)
|
||||
// Recover + show the host (Java) stack trace, which `e` alone does not carry.
|
||||
try { printerrln(sys.printStackTrace(e)) } catch (_) {}
|
||||
errorlevel = 1
|
||||
} finally {
|
||||
if (readPtr !== undefined) sys.free(readPtr)
|
||||
|
||||
Reference in New Issue
Block a user