mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-07 19:51:51 +09:00
pcm streaming does work but...
This commit is contained in:
@@ -96,7 +96,7 @@ function readBytes(length) {
|
||||
return ptr
|
||||
}
|
||||
|
||||
let sampleSize = FILE_SIZE
|
||||
/*let sampleSize = FILE_SIZE
|
||||
const FETCH_INTERVAL = 631578947
|
||||
let updateAkku = FETCH_INTERVAL
|
||||
let oldNanoTime = sys.nanoTime()
|
||||
@@ -127,4 +127,49 @@ while (sampleSize > 0) {
|
||||
}
|
||||
|
||||
sys.spin()
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
let sampleSize = FILE_SIZE
|
||||
const BLOCK_SIZE = 4096
|
||||
const QUEUEING_SIZE = 4
|
||||
|
||||
audio.resetParams(0)
|
||||
audio.purgeQueue(0)
|
||||
audio.setPcmMode(0)
|
||||
audio.setMasterVolume(0, 255)
|
||||
|
||||
// FIXME: when a playback was interrupted using SHIFT-CTRL-T-R, then re-tried, the ghost from the previous run
|
||||
// briefly manifests, even if you're queueing only once
|
||||
|
||||
while (sampleSize > 0) {
|
||||
let queueSize = audio.getPosition(0)
|
||||
|
||||
serial.println(`[js] Trying to upload samples, queueSize = ${queueSize}`)
|
||||
print(".")
|
||||
|
||||
if (queueSize == 0) {
|
||||
println()
|
||||
println((FILE_SIZE - sampleSize) / FILE_SIZE * 100 + " %")
|
||||
|
||||
// upload four samples for lag-safely
|
||||
for (let repeat = QUEUEING_SIZE; repeat > 0; repeat--) {
|
||||
let readLength = (sampleSize < BLOCK_SIZE) ? sampleSize : BLOCK_SIZE
|
||||
let samples = readBytes(readLength)
|
||||
|
||||
audio.putPcmDataByPtr(samples, readLength, 0)
|
||||
audio.uploadSamples(0, readLength)
|
||||
|
||||
sampleSize -= readLength
|
||||
sys.free(samples)
|
||||
|
||||
if (repeat > 1) sys.sleep(10)
|
||||
}
|
||||
|
||||
audio.play(0)
|
||||
}
|
||||
|
||||
sys.sleep(10)
|
||||
}
|
||||
|
||||
audio.stop(0) // this shouldn't be necessary, it should stop automatically
|
||||
|
||||
@@ -552,9 +552,8 @@ Sound Adapter MMIO
|
||||
32 ??: ???
|
||||
|
||||
Play Head Position
|
||||
- Cuesheet Counter for Tracker mode
|
||||
- Numbers of processed internal buffers (0-3)
|
||||
when the number is 3, get ready to upload more samples
|
||||
- Tracker mode: Cuesheet Counter
|
||||
- PCM mode: Number of buffers uploaded and received by the adapter
|
||||
|
||||
Length Param
|
||||
PCM Mode: length of the samples to upload to the speaker
|
||||
@@ -562,9 +561,17 @@ Length Param
|
||||
|
||||
Play Head Flags
|
||||
Byte 1
|
||||
- 0b m00p 0000
|
||||
- 0b mrqp 0000
|
||||
m: mode (0 for Tracker, 1 for PCM)
|
||||
r: reset parameters; always 0 when read
|
||||
resetting will:
|
||||
set position to 0,
|
||||
set length param to 0,
|
||||
unset play bit
|
||||
q: purge queues (likely do nothing if not PCM); always 0 when read
|
||||
p: play (0 if not -- mute all output)
|
||||
|
||||
NOTE: changing from PCM mode to Tracker mode or vice versa will also reset the parameters as described above
|
||||
Byte 2
|
||||
- PCM Mode: Sampling rate multiplier in 3.5 Unsigned Minifloat (0.03125x to 126x)
|
||||
|
||||
|
||||
@@ -26,11 +26,10 @@ class AudioJSR223Delegate(private val vm: VM) {
|
||||
fun stop(playhead: Int) { getPlayhead(playhead)?.isPlaying = false }
|
||||
fun isPlaying(playhead: Int) = getPlayhead(playhead)?.isPlaying
|
||||
|
||||
fun setPosition(playhead: Int, pos: Int) { getPlayhead(playhead)?.position = pos and 65535 }
|
||||
// fun setPosition(playhead: Int, pos: Int) { getPlayhead(playhead)?.position = pos and 65535 }
|
||||
fun getPosition(playhead: Int) = getPlayhead(playhead)?.position
|
||||
|
||||
fun setUploadLength(playhead: Int, pos: Int) { getPlayhead(playhead)?.pcmUploadLength = pos and 65535 }
|
||||
fun setUploadLength(playhead: Int) = getPlayhead(playhead)?.pcmUploadLength
|
||||
fun uploadSamples(playhead: Int, length: Int) { getPlayhead(playhead)?.pcmUploadLength = length and 65535 }
|
||||
|
||||
fun setSamplingRate(playhead: Int, rate: Int) { getPlayhead(playhead)?.setSamplingRate(rate) }
|
||||
fun getSamplingRate(playhead: Int) = getPlayhead(playhead)?.getSamplingRate()
|
||||
@@ -55,4 +54,11 @@ class AudioJSR223Delegate(private val vm: VM) {
|
||||
}
|
||||
fun getPcmData(index: Int) = getFirstSnd()?.pcmBin?.get(index.toLong())
|
||||
|
||||
fun resetParams(playhead: Int) {
|
||||
getPlayhead(playhead)?.resetParams()
|
||||
}
|
||||
|
||||
fun purgeQueue(playhead: Int) {
|
||||
getPlayhead(playhead)?.purgeQueue()
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,12 @@ package net.torvald.tsvm.peripheral
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.audio.AudioDevice
|
||||
import com.badlogic.gdx.backends.lwjgl3.audio.OpenALAudioDevice
|
||||
import com.badlogic.gdx.backends.lwjgl3.audio.OpenALLwjgl3Audio
|
||||
import com.badlogic.gdx.utils.Queue
|
||||
import net.torvald.UnsafeHelper
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toUint
|
||||
import net.torvald.tsvm.ThreeFiveMiniUfloat
|
||||
import net.torvald.tsvm.VM
|
||||
import org.lwjgl.openal.AL11
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
private fun Boolean.toInt() = if (this) 1 else 0
|
||||
|
||||
@@ -18,6 +16,11 @@ private fun Boolean.toInt() = if (this) 1 else 0
|
||||
*/
|
||||
class AudioAdapter(val vm: VM) : PeriBase {
|
||||
|
||||
private val DBGPRN = true
|
||||
private fun printdbg(msg: Any) {
|
||||
if (DBGPRN) println("[AudioAdapter] $msg")
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SAMPLING_RATE = 30000
|
||||
}
|
||||
@@ -31,6 +34,8 @@ class AudioAdapter(val vm: VM) : PeriBase {
|
||||
|
||||
private lateinit var audioDevices: Array<AudioDevice>
|
||||
private val renderThreads = Array(4) { Thread(getRenderFun(it)) }
|
||||
private val writeQueueingThreads = Array(4) { Thread(getQueueingFun(it)) }
|
||||
// private val writeQueues = Array(4) { Queue<FloatArray>() }
|
||||
|
||||
/*private val alSources = Array(4) {
|
||||
val audioField = OpenALAudioDevice::class.java.getDeclaredField("audio")
|
||||
@@ -79,6 +84,24 @@ class AudioAdapter(val vm: VM) : PeriBase {
|
||||
Thread.sleep(1)
|
||||
} }
|
||||
|
||||
private fun getQueueingFun(pheadNum: Int): () -> Unit = { while (true) {
|
||||
|
||||
playheads[pheadNum].let {
|
||||
if (it.pcmUploadLength > 0) {
|
||||
printdbg("Downloading samples ${it.pcmUploadLength}")
|
||||
|
||||
val samples = FloatArray(it.pcmUploadLength) { pcmBin[it.toLong()].toUint().div(255f) * 2f - 1f }
|
||||
it.pcmQueue.addLast(samples)
|
||||
|
||||
it.pcmUploadLength = 0
|
||||
it.position += 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Thread.sleep(4)
|
||||
} }
|
||||
|
||||
init {
|
||||
|
||||
val deviceBufferSize = Gdx.audio.javaClass.getDeclaredField("deviceBufferSize").let {
|
||||
@@ -90,14 +113,56 @@ class AudioAdapter(val vm: VM) : PeriBase {
|
||||
it.get(Gdx.audio) as Int
|
||||
}
|
||||
|
||||
audioDevices = Array(4) { OpenALBufferedAudioDevice(Gdx.audio as OpenALLwjgl3Audio, SAMPLING_RATE, false, deviceBufferSize, deviceBufferCount) }
|
||||
printdbg("buffer size: $deviceBufferSize x $deviceBufferCount")
|
||||
|
||||
audioDevices = Array(4) { OpenALBufferedAudioDevice(
|
||||
Gdx.audio as OpenALLwjgl3Audio,
|
||||
SAMPLING_RATE,
|
||||
false,
|
||||
deviceBufferSize,
|
||||
deviceBufferCount) {
|
||||
|
||||
} }
|
||||
|
||||
|
||||
// println("AudioAdapter latency: ${audioDevice.latency}")
|
||||
// printdbg("AudioAdapter latency: ${audioDevice.latency}")
|
||||
renderThreads.forEach { it.start() }
|
||||
writeQueueingThreads.forEach { it.start() }
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Put this function into a separate thread and keep track of the delta time by yourself
|
||||
*/
|
||||
private fun render(playhead: Playhead, pheadNum: Int) {
|
||||
if (playhead.isPcmMode) {
|
||||
|
||||
val writeQueue = playhead.pcmQueue
|
||||
|
||||
if (playhead.isPlaying && writeQueue.notEmpty()) {
|
||||
|
||||
printdbg("Taking samples from queue (queue size: ${writeQueue.size})")
|
||||
|
||||
val samples = writeQueue.removeFirst()
|
||||
playhead.position = writeQueue.size
|
||||
|
||||
printdbg("P${pheadNum+1} Vol ${playhead.masterVolume}; LpP ${playhead.pcmUploadLength}; start playback...")
|
||||
// printdbg(""+(0..42).joinToString { String.format("%.2f", samples[it]) })
|
||||
if (playhead.masterVolume == 0) printdbg("P${pheadNum+1} volume is zero!")
|
||||
|
||||
audioDevices[pheadNum].setVolume(playhead.masterVolume / 255f)
|
||||
audioDevices[pheadNum].writeSamples(samples, 0, samples.size)
|
||||
|
||||
printdbg("P${pheadNum+1} go back to spinning")
|
||||
|
||||
}
|
||||
else if (playhead.isPlaying) {
|
||||
// printdbg("Queue exhausted, stopping...")
|
||||
// it.isPlaying = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun peek(addr: Long): Byte {
|
||||
return when (val adi = addr.toInt()) {
|
||||
in 0..114687 -> sampleBin[addr]
|
||||
@@ -149,6 +214,7 @@ class AudioAdapter(val vm: VM) : PeriBase {
|
||||
|
||||
override fun dispose() {
|
||||
renderThreads.forEach { it.interrupt() }
|
||||
writeQueueingThreads.forEach { it.interrupt() }
|
||||
audioDevices.forEach { it.dispose() }
|
||||
// freeAlSources()
|
||||
sampleBin.destroy()
|
||||
@@ -159,34 +225,6 @@ class AudioAdapter(val vm: VM) : PeriBase {
|
||||
return vm
|
||||
}
|
||||
|
||||
/**
|
||||
* Put this function into a separate thread and keep track of the delta time by yourself
|
||||
*/
|
||||
private fun render(it: Playhead, pheadNum: Int) {
|
||||
if (it.isPcmMode) {
|
||||
if (it.isPlaying) {
|
||||
|
||||
audioDevices[pheadNum].setVolume(it.masterVolume / 255f)
|
||||
|
||||
if (it.pcmUploadLength > 0) {
|
||||
val samples = FloatArray(it.pcmUploadLength) { pcmBin[it.toLong()].toUint().div(255f) * 2f - 1f }
|
||||
|
||||
println("[AudioAdapter] P${pheadNum+1} Vol ${it.masterVolume}; LpP ${it.pcmUploadLength}; start playback...")
|
||||
// println("[AudioAdapter] "+(0..42).joinToString { String.format("%.2f", samples[it]) })
|
||||
|
||||
if (it.masterVolume == 0) System.err.println("[AudioAdapter] P${pheadNum+1} volume is zero!")
|
||||
|
||||
audioDevices[pheadNum].writeSamples(samples, 0, it.pcmUploadLength)
|
||||
|
||||
println("[AudioAdapter] P${pheadNum+1} go back to spinning")
|
||||
|
||||
}
|
||||
|
||||
it.isPlaying = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val typestring = VM.PERITYPE_SOUND
|
||||
|
||||
|
||||
@@ -241,7 +279,9 @@ class AudioAdapter(val vm: VM) : PeriBase {
|
||||
var isPlaying: Boolean = false,
|
||||
var samplingRateMult: ThreeFiveMiniUfloat = ThreeFiveMiniUfloat(32),
|
||||
var bpm: Int = 120, // "stored" as 96
|
||||
var tickRate: Int = 6
|
||||
var tickRate: Int = 6,
|
||||
|
||||
var pcmQueue: Queue<FloatArray> = Queue<FloatArray>()
|
||||
) {
|
||||
fun read(index: Int): Byte = when (index) {
|
||||
0 -> position.toByte()
|
||||
@@ -258,15 +298,19 @@ class AudioAdapter(val vm: VM) : PeriBase {
|
||||
}
|
||||
|
||||
fun write(index: Int, byte: Int) = when (index) {
|
||||
0 -> { position = position.and(0xff00) or position }
|
||||
1 -> { position = position.and(0x00ff) or position.shl(8) }
|
||||
0 -> if (!isPcmMode) { position = position.and(0xff00) or position } else {}
|
||||
1 -> if (!isPcmMode) { position = position.and(0x00ff) or position.shl(8) } else {}
|
||||
2 -> { pcmUploadLength = pcmUploadLength.and(0xff00) or pcmUploadLength }
|
||||
3 -> { pcmUploadLength = pcmUploadLength.and(0x00ff) or pcmUploadLength.shl(8) }
|
||||
4 -> { masterVolume = byte }
|
||||
5 -> { masterPan = byte }
|
||||
6 -> { byte.let {
|
||||
val oldPcmMode = isPcmMode
|
||||
isPcmMode = (it and 0b10000000) != 0
|
||||
isPlaying = (it and 0b00010000) != 0
|
||||
|
||||
if (it and 0b01000000 != 0 || oldPcmMode != isPcmMode) resetParams()
|
||||
if (it and 0b00100000 != 0) purgeQueue()
|
||||
} }
|
||||
7 -> { samplingRateMult = ThreeFiveMiniUfloat(byte) }
|
||||
8 -> { bpm = byte + 24 }
|
||||
@@ -280,6 +324,20 @@ class AudioAdapter(val vm: VM) : PeriBase {
|
||||
bpm = rateDiff.and(255) + 24
|
||||
tickRate = rateDiff.ushr(8).and(255)
|
||||
}
|
||||
|
||||
fun resetParams() {
|
||||
position = 0
|
||||
pcmUploadLength = 0
|
||||
isPlaying = false
|
||||
}
|
||||
|
||||
fun purgeQueue() {
|
||||
pcmQueue.clear()
|
||||
if (isPcmMode) {
|
||||
position = 0
|
||||
pcmUploadLength = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal data class TaudPlayData(
|
||||
|
||||
@@ -37,7 +37,8 @@ class OpenALBufferedAudioDevice(
|
||||
val rate: Int,
|
||||
isMono: Boolean,
|
||||
private val bufferSize: Int,
|
||||
private val bufferCount: Int
|
||||
private val bufferCount: Int,
|
||||
private val fillBufferCallback: () -> Unit
|
||||
) : AudioDevice {
|
||||
private val channels: Int
|
||||
private var buffers: IntBuffer? = null
|
||||
@@ -50,6 +51,12 @@ class OpenALBufferedAudioDevice(
|
||||
private var bytes: ByteArray? = null
|
||||
private val tempBuffer: ByteBuffer
|
||||
|
||||
/**
|
||||
* Invoked whenever a buffer is emptied after writing samples
|
||||
*
|
||||
* Preferably you write 2-3 buffers worth of samples at the beginning of the playback
|
||||
*/
|
||||
|
||||
init {
|
||||
channels = if (isMono) 1 else 2
|
||||
format = if (channels > 1) AL10.AL_FORMAT_STEREO16 else AL10.AL_FORMAT_MONO16
|
||||
@@ -160,6 +167,7 @@ class OpenALBufferedAudioDevice(
|
||||
// Wait for buffer to be free.
|
||||
try {
|
||||
Thread.sleep((1000 * secondsPerBuffer).toLong())
|
||||
fillBufferCallback()
|
||||
}
|
||||
catch (ignored: InterruptedException) {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user