mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-10 21:21:51 +09:00
one idea down, one last idea to go
This commit is contained in:
130
assets/disk0/home/soundtest.js
Normal file
130
assets/disk0/home/soundtest.js
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
/*let music = files.open("A:/orstphone-hdma.bytes")
|
||||||
|
let samples = sys.malloc(65536)
|
||||||
|
music.pread(samples, 65534)
|
||||||
|
|
||||||
|
audio.setPcmMode(0)
|
||||||
|
audio.setMasterVolume(0, 255)
|
||||||
|
audio.putPcmDataByPtr(samples, 65534, 0)
|
||||||
|
audio.setLoopPoint(0, 65534)
|
||||||
|
audio.play(0)*/
|
||||||
|
|
||||||
|
let filename = exec_args[1]
|
||||||
|
const port = _TVDOS.DRV.FS.SERIAL._toPorts("A")[0]
|
||||||
|
|
||||||
|
let filex = files.open(_G.shell.resolvePathInput(filename).full)
|
||||||
|
const FILE_SIZE = filex.size
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
println("Reading...")
|
||||||
|
serial.println("!!! READING")
|
||||||
|
|
||||||
|
com.sendMessage(port, "DEVRST\x17")
|
||||||
|
com.sendMessage(port, `OPENR"${filename}",1`)
|
||||||
|
let statusCode = com.getStatusCode(port)
|
||||||
|
|
||||||
|
if (statusCode != 0) {
|
||||||
|
printerrln(`No such file (${statusCode})`)
|
||||||
|
return statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
com.sendMessage(port, "READ")
|
||||||
|
statusCode = com.getStatusCode(port)
|
||||||
|
if (statusCode != 0) {
|
||||||
|
printerrln("READ failed with "+statusCode)
|
||||||
|
return statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let readCount = 0
|
||||||
|
function readBytes(length) {
|
||||||
|
let ptr = sys.malloc(length)
|
||||||
|
let requiredBlocks = Math.floor((readCount + length) / 4096) - Math.floor(readCount / 4096)
|
||||||
|
|
||||||
|
let completedReads = 0
|
||||||
|
|
||||||
|
// serial.println(`readBytes(${length}); readCount = ${readCount}`)
|
||||||
|
|
||||||
|
for (let bc = 0; bc < requiredBlocks + 1; bc++) {
|
||||||
|
if (completedReads >= length) break
|
||||||
|
|
||||||
|
if (readCount % 4096 == 0) {
|
||||||
|
// serial.println("READ from serial")
|
||||||
|
// pull the actual message
|
||||||
|
sys.poke(-4093 - port, 6);sys.sleep(0) // spinning is required as Graal run is desynced with the Java side
|
||||||
|
|
||||||
|
let blockTransferStatus = ((sys.peek(-4085 - port*2) & 255) | ((sys.peek(-4086 - port*2) & 255) << 8))
|
||||||
|
let thisBlockLen = blockTransferStatus & 4095
|
||||||
|
if (thisBlockLen == 0) thisBlockLen = 4096 // [1, 4096]
|
||||||
|
let hasMore = (blockTransferStatus & 0x8000 != 0)
|
||||||
|
|
||||||
|
|
||||||
|
// serial.println(`block: (${thisBlockLen})[${[...Array(thisBlockLen).keys()].map(k => (sys.peek(-4097 - k) & 255).toString(16).padStart(2,'0')).join()}]`)
|
||||||
|
|
||||||
|
let remaining = Math.min(thisBlockLen, length - completedReads)
|
||||||
|
|
||||||
|
// serial.println(`Pulled a block (${thisBlockLen}); readCount = ${readCount}, completedReads = ${completedReads}, remaining = ${remaining}`)
|
||||||
|
|
||||||
|
// copy from read buffer to designated position
|
||||||
|
sys.memcpy(-4097, ptr + completedReads, remaining)
|
||||||
|
|
||||||
|
// increment readCount properly
|
||||||
|
readCount += remaining
|
||||||
|
completedReads += remaining
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let padding = readCount % 4096
|
||||||
|
let remaining = length - completedReads
|
||||||
|
let thisBlockLen = Math.min(4096 - padding, length - completedReads)
|
||||||
|
|
||||||
|
// serial.println(`padding = ${padding}; remaining = ${remaining}`)
|
||||||
|
// serial.println(`block: (${thisBlockLen})[${[...Array(thisBlockLen).keys()].map(k => (sys.peek(-4097 - padding - k) & 255).toString(16).padStart(2,'0')).join()}]`)
|
||||||
|
// serial.println(`Reusing a block (${thisBlockLen}); readCount = ${readCount}, completedReads = ${completedReads}`)
|
||||||
|
|
||||||
|
// copy from read buffer to designated position
|
||||||
|
sys.memcpy(-4097 - padding, ptr + completedReads, thisBlockLen)
|
||||||
|
|
||||||
|
// increment readCount properly
|
||||||
|
readCount += thisBlockLen
|
||||||
|
completedReads += thisBlockLen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//serial.println(`END readBytes(${length}); readCount = ${readCount}\n`)
|
||||||
|
|
||||||
|
return ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
let sampleSize = FILE_SIZE
|
||||||
|
const FETCH_INTERVAL = 631578947
|
||||||
|
let updateAkku = FETCH_INTERVAL
|
||||||
|
let oldNanoTime = sys.nanoTime()
|
||||||
|
|
||||||
|
const BLOCK_SIZE = 37894
|
||||||
|
|
||||||
|
audio.setPcmMode(0)
|
||||||
|
audio.setMasterVolume(0, 255)
|
||||||
|
|
||||||
|
while (sampleSize > 0) {
|
||||||
|
let newNanoTime = sys.nanoTime()
|
||||||
|
updateAkku += newNanoTime - oldNanoTime
|
||||||
|
oldNanoTime = newNanoTime
|
||||||
|
|
||||||
|
if (updateAkku >= FETCH_INTERVAL) {
|
||||||
|
println((FILE_SIZE - sampleSize) / FILE_SIZE * 100 + "%")
|
||||||
|
updateAkku -= FETCH_INTERVAL
|
||||||
|
|
||||||
|
let readLength = (sampleSize < BLOCK_SIZE) ? sampleSize : BLOCK_SIZE
|
||||||
|
let samples = readBytes(readLength)
|
||||||
|
|
||||||
|
audio.setUploadLength(0, readLength)
|
||||||
|
audio.putPcmDataByPtr(samples, readLength, 0)
|
||||||
|
audio.play(0)
|
||||||
|
|
||||||
|
sampleSize -= readLength
|
||||||
|
sys.free(samples)
|
||||||
|
}
|
||||||
|
|
||||||
|
sys.spin()
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ let shell_pwd = [""]
|
|||||||
let goInteractive = false
|
let goInteractive = false
|
||||||
let goFancy = false
|
let goFancy = false
|
||||||
|
|
||||||
let DEBUG_PRINT = true
|
let DEBUG_PRINT = false
|
||||||
|
|
||||||
let errorlevel = 0
|
let errorlevel = 0
|
||||||
|
|
||||||
@@ -723,7 +723,7 @@ shell.execute = function(line) {
|
|||||||
if (!gotError && (errorlevel == undefined || (typeof errorlevel.trim == "function" && errorlevel.trim().length == 0) || isNaN(errorlevel)))
|
if (!gotError && (errorlevel == undefined || (typeof errorlevel.trim == "function" && errorlevel.trim().length == 0) || isNaN(errorlevel)))
|
||||||
errorlevel = 0
|
errorlevel = 0
|
||||||
|
|
||||||
serial.printerr(`errorlevel: ${errorlevel}`)
|
debugprintln(`errorlevel: ${errorlevel}`)
|
||||||
|
|
||||||
_G.shellProgramTitles.pop()
|
_G.shellProgramTitles.pop()
|
||||||
sendLcdMsg(_G.shellProgramTitles[_G.shellProgramTitles.length - 1])
|
sendLcdMsg(_G.shellProgramTitles[_G.shellProgramTitles.length - 1])
|
||||||
|
|||||||
@@ -536,36 +536,46 @@ notes are tuned as 4096 Tone-Equal Temperament. Tuning is set per-sample using t
|
|||||||
Sound Adapter MMIO
|
Sound Adapter MMIO
|
||||||
|
|
||||||
0..1 RW: Play head #1 position
|
0..1 RW: Play head #1 position
|
||||||
2 RW: Play head #1 master volume
|
2..3 RW: Play head #1 length param
|
||||||
3 RW: Play head #1 master pan
|
4 RW: Play head #1 master volume
|
||||||
4..7 RW: Play head #1 flags
|
5 RW: Play head #1 master pan
|
||||||
|
6..9 RW: Play head #1 flags
|
||||||
|
|
||||||
8..9 RW: Play head #2 position
|
10..11 RW:Play head #2 position
|
||||||
10 RW: Play head #2 master volume
|
12..13 RW:Play head #2 length param
|
||||||
11 RW: Play head #2 master pan
|
14 RW: Play head #2 master volume
|
||||||
12..15 RW:Play head #2 flags
|
15 RW: Play head #2 master pan
|
||||||
|
16..19 RW:Play head #2 flags
|
||||||
|
|
||||||
... auto-fill to Play head #4
|
... auto-fill to Play head #4
|
||||||
|
|
||||||
32 ??: ???
|
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
|
||||||
|
|
||||||
|
Length Param
|
||||||
|
PCM Mode: length of the samples to upload to the speaker
|
||||||
|
Tracker mode: unused
|
||||||
|
|
||||||
Play Head Flags
|
Play Head Flags
|
||||||
Byte 1
|
Byte 1
|
||||||
- 0b m000 0000
|
- 0b m00p 0000
|
||||||
m: mode (0 for Tracker, 1 for PCM)
|
m: mode (0 for Tracker, 1 for PCM)
|
||||||
|
p: play (0 if not -- mute all output)
|
||||||
Byte 2
|
Byte 2
|
||||||
- PCM Mode: Sampling rate multiplier in 3.5 Unsigned Minifloat (0.03125x to 126x)
|
- PCM Mode: Sampling rate multiplier in 3.5 Unsigned Minifloat (0.03125x to 126x)
|
||||||
|
|
||||||
Byte 3 (Tracker Mode)
|
Byte 3 (Tracker Mode)
|
||||||
- BPM (24 to 280. Play Data will change this register)
|
- BPM (24 to 280. Play Data will change this register)
|
||||||
Byte 4 (Tracker Mode)
|
Byte 4 (Tracker Mode)
|
||||||
- Tick Rate (Play Data will change this register)
|
- Tick Rate (Play Data will change this register)
|
||||||
|
|
||||||
Byte 3-4 (PCM Mode)
|
Byte 3-4 (PCM Mode)
|
||||||
- Signed Int16 Sampling rate difference from 30000 Hz
|
- Signed Int16 Sampling rate difference from 30000 Hz
|
||||||
|
|
||||||
Play Head Position interpretion
|
|
||||||
- Cuesheet Counter for Tracker mode
|
|
||||||
- Sample Counter for PCM mode
|
|
||||||
|
|
||||||
32768..65535 RW: Cue Sheet (2048 cues)
|
32768..65535 RW: Cue Sheet (2048 cues)
|
||||||
Byte 1..15: pattern number for voice 1..15
|
Byte 1..15: pattern number for voice 1..15
|
||||||
|
|||||||
58
tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt
Normal file
58
tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package net.torvald.tsvm
|
||||||
|
|
||||||
|
import net.torvald.tsvm.peripheral.AudioAdapter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by minjaesong on 2022-12-31.
|
||||||
|
*/
|
||||||
|
class AudioJSR223Delegate(private val vm: VM) {
|
||||||
|
|
||||||
|
private fun getFirstSnd(): AudioAdapter? = vm.findPeribyType(VM.PERITYPE_SOUND)?.peripheral as? AudioAdapter
|
||||||
|
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)?.masterVolume = volume and 255 }
|
||||||
|
fun getMasterVolume(playhead: Int) = getPlayhead(playhead)?.masterVolume
|
||||||
|
|
||||||
|
fun setMasterPan(playhead: Int, pan: Int) { getPlayhead(playhead)?.masterPan = pan and 255 }
|
||||||
|
fun getMasterPan(playhead: Int) = getPlayhead(playhead)?.masterPan
|
||||||
|
|
||||||
|
fun play(playhead: Int) { getPlayhead(playhead)?.isPlaying = true }
|
||||||
|
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 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 setSamplingRate(playhead: Int, rate: Int) { getPlayhead(playhead)?.setSamplingRate(rate) }
|
||||||
|
fun getSamplingRate(playhead: Int) = getPlayhead(playhead)?.getSamplingRate()
|
||||||
|
|
||||||
|
fun setSamplingRateMult(playhead: Int, mult: Float) { getPlayhead(playhead)?.samplingRateMult = ThreeFiveMiniUfloat(mult) }
|
||||||
|
fun getSamplingRateMult(playhead: Int) = getPlayhead(playhead)?.samplingRateMult?.toFloat()
|
||||||
|
|
||||||
|
fun setBPM(playhead: Int, bpm: Int) { getPlayhead(playhead)?.bpm = (bpm - 24).and(255) + 24 }
|
||||||
|
fun getBPM(playhead: Int) = getPlayhead(playhead)?.bpm
|
||||||
|
|
||||||
|
fun setTickRate(playhead: Int, rate: Int) { getPlayhead(playhead)?.tickRate = rate and 255 }
|
||||||
|
fun getTickRate(playhead: Int) = getPlayhead(playhead)?.tickRate
|
||||||
|
|
||||||
|
fun putPcmDataByPtr(ptr: Int, length: Int, destOffset: Int) {
|
||||||
|
getFirstSnd()?.let {
|
||||||
|
val vkMult = if (ptr >= 0) 1 else -1
|
||||||
|
for (k in 0L until length) {
|
||||||
|
val vk = k * vkMult
|
||||||
|
it.pcmBin[k + destOffset] = vm.peek(ptr + vk)!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun getPcmData(index: Int) = getFirstSnd()?.pcmBin?.get(index.toLong())
|
||||||
|
|
||||||
|
}
|
||||||
@@ -149,6 +149,7 @@ class VM(
|
|||||||
val USER_SPACE_SIZE = 8192.kB()
|
val USER_SPACE_SIZE = 8192.kB()
|
||||||
|
|
||||||
const val PERITYPE_GPU_AND_TERM = "gpu"
|
const val PERITYPE_GPU_AND_TERM = "gpu"
|
||||||
|
const val PERITYPE_SOUND = "snd"
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun translateAddr(addr: Long): Pair<Any?, Long> {
|
internal fun translateAddr(addr: Long): Pair<Any?, Long> {
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ object VMRunnerFactory {
|
|||||||
bind.putMember("base64", Base64Delegate)
|
bind.putMember("base64", Base64Delegate)
|
||||||
bind.putMember("com", SerialHelperDelegate(vm))
|
bind.putMember("com", SerialHelperDelegate(vm))
|
||||||
bind.putMember("dma", DMADelegate(vm))
|
bind.putMember("dma", DMADelegate(vm))
|
||||||
|
bind.putMember("audio", AudioJSR223Delegate(vm))
|
||||||
bind.putMember("parallel", ringOneParallel)
|
bind.putMember("parallel", ringOneParallel)
|
||||||
|
|
||||||
val fr = FileReader("$assetsRoot/JS_INIT.js")
|
val fr = FileReader("$assetsRoot/JS_INIT.js")
|
||||||
|
|||||||
@@ -1,23 +1,102 @@
|
|||||||
package net.torvald.tsvm.peripheral
|
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 net.torvald.UnsafeHelper
|
import net.torvald.UnsafeHelper
|
||||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toUint
|
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.toUint
|
||||||
import net.torvald.tsvm.ThreeFiveMiniUfloat
|
import net.torvald.tsvm.ThreeFiveMiniUfloat
|
||||||
import net.torvald.tsvm.VM
|
import net.torvald.tsvm.VM
|
||||||
|
import org.lwjgl.openal.AL11
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
private fun Boolean.toInt() = if (this) 1 else 0
|
private fun Boolean.toInt() = if (this) 1 else 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by minjaesong on 2022-12-30.
|
* Created by minjaesong on 2022-12-30.
|
||||||
*/
|
*/
|
||||||
class SoundAdapter(val vm: VM) : PeriBase {
|
class AudioAdapter(val vm: VM) : PeriBase {
|
||||||
|
|
||||||
private val sampleBin = UnsafeHelper.allocate(114687L)
|
companion object {
|
||||||
private val instruments = Array(256) { TaudInst() }
|
const val SAMPLING_RATE = 30000
|
||||||
private val playdata = Array(256) { Array(64) { TaudPlayData(0,0,0,0,0,0,0,0) } }
|
}
|
||||||
private val playheads = Array(4) { Playhead() }
|
|
||||||
private val cueSheet = Array(2048) { PlayCue() }
|
internal val sampleBin = UnsafeHelper.allocate(114687L)
|
||||||
private val pcmBin = UnsafeHelper.allocate(65536L)
|
internal val instruments = Array(256) { TaudInst() }
|
||||||
|
internal val playdata = Array(256) { Array(64) { TaudPlayData(0,0,0,0,0,0,0,0) } }
|
||||||
|
internal val playheads = Array(4) { Playhead() }
|
||||||
|
internal val cueSheet = Array(2048) { PlayCue() }
|
||||||
|
internal val pcmBin = UnsafeHelper.allocate(65536L)
|
||||||
|
|
||||||
|
private lateinit var audioDevices: Array<AudioDevice>
|
||||||
|
private val renderThreads = Array(4) { Thread(getRenderFun(it)) }
|
||||||
|
|
||||||
|
/*private val alSources = Array(4) {
|
||||||
|
val audioField = OpenALAudioDevice::class.java.getDeclaredField("audio")
|
||||||
|
audioField.isAccessible = true
|
||||||
|
val audio = audioField.get(audioDevices[it]) as OpenALLwjgl3Audio
|
||||||
|
|
||||||
|
val obtainSourceMethod = OpenALLwjgl3Audio::class.java.getDeclaredMethod("obtainSource", java.lang.Boolean.TYPE)
|
||||||
|
obtainSourceMethod.isAccessible = true
|
||||||
|
val alSource = obtainSourceMethod.invoke(audio, true) as Int
|
||||||
|
|
||||||
|
alSource
|
||||||
|
}
|
||||||
|
|
||||||
|
private val alBuffers = Array(4) {
|
||||||
|
val buffers = IntArray(3)
|
||||||
|
AL11.alGenBuffers(buffers)
|
||||||
|
buffers
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun freeAlSources() {
|
||||||
|
audioDevices.forEachIndexed { index, adev ->
|
||||||
|
val audioField = OpenALAudioDevice::class.java.getDeclaredField("audio")
|
||||||
|
audioField.isAccessible = true
|
||||||
|
val audio = audioField.get(adev) as OpenALLwjgl3Audio
|
||||||
|
|
||||||
|
val freeSourceMethod = OpenALLwjgl3Audio::class.java.getDeclaredMethod("freeSource", java.lang.Integer.TYPE)
|
||||||
|
freeSourceMethod.isAccessible = true
|
||||||
|
freeSourceMethod.invoke(audio, alSources[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun enqueuePacket(alSource: Int, alBuffer: Int, data: ByteBuffer) {
|
||||||
|
AL11.alBufferData(alBuffer, AL11.AL_FORMAT_STEREO8, data, SAMPLING_RATE)
|
||||||
|
AL11.alSourceQueueBuffers(alSource, alBuffer)
|
||||||
|
|
||||||
|
}*/
|
||||||
|
|
||||||
|
private val pcmCurrentPosInSamples = ShortArray(4)
|
||||||
|
|
||||||
|
private var pcmPlaybackWatchdogs = Array(4) { Thread {
|
||||||
|
|
||||||
|
} }
|
||||||
|
|
||||||
|
private fun getRenderFun(pheadNum: Int): () -> Unit = { while (true) {
|
||||||
|
render(playheads[pheadNum], pheadNum)
|
||||||
|
Thread.sleep(1)
|
||||||
|
} }
|
||||||
|
|
||||||
|
init {
|
||||||
|
|
||||||
|
val deviceBufferSize = Gdx.audio.javaClass.getDeclaredField("deviceBufferSize").let {
|
||||||
|
it.isAccessible = true
|
||||||
|
it.get(Gdx.audio) as Int
|
||||||
|
}
|
||||||
|
val deviceBufferCount = Gdx.audio.javaClass.getDeclaredField("deviceBufferCount").let {
|
||||||
|
it.isAccessible = true
|
||||||
|
it.get(Gdx.audio) as Int
|
||||||
|
}
|
||||||
|
|
||||||
|
audioDevices = Array(4) { OpenALBufferedAudioDevice(Gdx.audio as OpenALLwjgl3Audio, SAMPLING_RATE, false, deviceBufferSize, deviceBufferCount) }
|
||||||
|
|
||||||
|
|
||||||
|
// println("AudioAdapter latency: ${audioDevice.latency}")
|
||||||
|
renderThreads.forEach { it.start() }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
override fun peek(addr: Long): Byte {
|
override fun peek(addr: Long): Byte {
|
||||||
return when (val adi = addr.toInt()) {
|
return when (val adi = addr.toInt()) {
|
||||||
@@ -41,10 +120,10 @@ class SoundAdapter(val vm: VM) : PeriBase {
|
|||||||
override fun mmio_read(addr: Long): Byte {
|
override fun mmio_read(addr: Long): Byte {
|
||||||
val adi = addr.toInt()
|
val adi = addr.toInt()
|
||||||
return when (adi) {
|
return when (adi) {
|
||||||
in 0..15 -> playheads[0].read(adi)
|
in 0..9 -> playheads[0].read(adi)
|
||||||
in 16..31 -> playheads[1].read(adi - 16)
|
in 10..19 -> playheads[1].read(adi - 10)
|
||||||
in 32..47 -> playheads[2].read(adi - 32)
|
in 20..29 -> playheads[2].read(adi - 20)
|
||||||
in 48..63 -> playheads[3].read(adi - 48)
|
in 30..39 -> playheads[3].read(adi - 30)
|
||||||
in 32768..65535 -> (adi - 32768).let {
|
in 32768..65535 -> (adi - 32768).let {
|
||||||
cueSheet[it / 16].read(it % 15)
|
cueSheet[it / 16].read(it % 15)
|
||||||
}
|
}
|
||||||
@@ -57,10 +136,10 @@ class SoundAdapter(val vm: VM) : PeriBase {
|
|||||||
val adi = addr.toInt()
|
val adi = addr.toInt()
|
||||||
val bi = byte.toUint()
|
val bi = byte.toUint()
|
||||||
when (adi) {
|
when (adi) {
|
||||||
in 0..15 -> { playheads[0].write(adi, bi) }
|
in 0..9 -> { playheads[0].write(adi, bi) }
|
||||||
in 16..31 -> { playheads[1].write(adi - 16, bi) }
|
in 10..19 -> { playheads[1].write(adi - 10, bi) }
|
||||||
in 32..47 -> { playheads[2].write(adi - 32, bi) }
|
in 20..29 -> { playheads[2].write(adi - 20, bi) }
|
||||||
in 48..63 -> { playheads[3].write(adi - 48, bi) }
|
in 30..39 -> { playheads[3].write(adi - 30, bi) }
|
||||||
in 32768..65535 -> { (adi - 32768).let {
|
in 32768..65535 -> { (adi - 32768).let {
|
||||||
cueSheet[it / 16].write(it % 15, bi)
|
cueSheet[it / 16].write(it % 15, bi)
|
||||||
} }
|
} }
|
||||||
@@ -69,6 +148,9 @@ class SoundAdapter(val vm: VM) : PeriBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
|
renderThreads.forEach { it.interrupt() }
|
||||||
|
audioDevices.forEach { it.dispose() }
|
||||||
|
// freeAlSources()
|
||||||
sampleBin.destroy()
|
sampleBin.destroy()
|
||||||
pcmBin.destroy()
|
pcmBin.destroy()
|
||||||
}
|
}
|
||||||
@@ -80,17 +162,37 @@ class SoundAdapter(val vm: VM) : PeriBase {
|
|||||||
/**
|
/**
|
||||||
* Put this function into a separate thread and keep track of the delta time by yourself
|
* Put this function into a separate thread and keep track of the delta time by yourself
|
||||||
*/
|
*/
|
||||||
open fun render(delta: Float) {
|
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 = "AUDI"
|
override val typestring = VM.PERITYPE_SOUND
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
internal data class PlayCue(
|
||||||
private data class PlayCue(
|
|
||||||
val patterns: IntArray = IntArray(15) { it },
|
val patterns: IntArray = IntArray(15) { it },
|
||||||
var instruction: PlayInstruction = PlayInstNop
|
var instruction: PlayInstruction = PlayInstNop
|
||||||
) {
|
) {
|
||||||
@@ -124,18 +226,19 @@ class SoundAdapter(val vm: VM) : PeriBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private open class PlayInstruction(val arg: Int)
|
internal open class PlayInstruction(val arg: Int)
|
||||||
private class PlayInstGoBack(arg: Int) : PlayInstruction(arg)
|
internal class PlayInstGoBack(arg: Int) : PlayInstruction(arg)
|
||||||
private class PlayInstSkip(arg: Int) : PlayInstruction(arg)
|
internal class PlayInstSkip(arg: Int) : PlayInstruction(arg)
|
||||||
private object PlayInstNop : PlayInstruction(0)
|
internal object PlayInstNop : PlayInstruction(0)
|
||||||
|
|
||||||
private data class Playhead(
|
internal data class Playhead(
|
||||||
var position: Int = 0,
|
var position: Int = 0,
|
||||||
|
var pcmUploadLength: Int = 0,
|
||||||
var masterVolume: Int = 0,
|
var masterVolume: Int = 0,
|
||||||
var masterPan: Int = 0,
|
var masterPan: Int = 0,
|
||||||
// flags
|
// flags
|
||||||
var isPcmMode: Boolean = false,
|
var isPcmMode: Boolean = false,
|
||||||
var loopMode: Int = 0,
|
var isPlaying: Boolean = false,
|
||||||
var samplingRateMult: ThreeFiveMiniUfloat = ThreeFiveMiniUfloat(32),
|
var samplingRateMult: ThreeFiveMiniUfloat = ThreeFiveMiniUfloat(32),
|
||||||
var bpm: Int = 120, // "stored" as 96
|
var bpm: Int = 120, // "stored" as 96
|
||||||
var tickRate: Int = 6
|
var tickRate: Int = 6
|
||||||
@@ -143,32 +246,43 @@ class SoundAdapter(val vm: VM) : PeriBase {
|
|||||||
fun read(index: Int): Byte = when (index) {
|
fun read(index: Int): Byte = when (index) {
|
||||||
0 -> position.toByte()
|
0 -> position.toByte()
|
||||||
1 -> position.ushr(8).toByte()
|
1 -> position.ushr(8).toByte()
|
||||||
2 -> masterVolume.toByte()
|
2 -> pcmUploadLength.toByte()
|
||||||
3 -> masterPan.toByte()
|
3 -> pcmUploadLength.ushr(8).toByte()
|
||||||
4 -> (isPcmMode.toInt().shl(7) or loopMode.and(3)).toByte()
|
4 -> masterVolume.toByte()
|
||||||
5 -> samplingRateMult.index.toByte()
|
5 -> masterPan.toByte()
|
||||||
6 -> (bpm - 24).toByte()
|
6 -> (isPcmMode.toInt().shl(7) or isPlaying.toInt().shl(4)).toByte()
|
||||||
7 -> tickRate.toByte()
|
7 -> samplingRateMult.index.toByte()
|
||||||
|
8 -> (bpm - 24).toByte()
|
||||||
|
9 -> tickRate.toByte()
|
||||||
else -> throw InternalError("Bad offset $index")
|
else -> throw InternalError("Bad offset $index")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun write(index: Int, byte: Int) = when (index) {
|
fun write(index: Int, byte: Int) = when (index) {
|
||||||
0 -> { position = position.and(0xff00) or position }
|
0 -> { position = position.and(0xff00) or position }
|
||||||
1 -> { position = position.and(0x00ff) or position.shl(8) }
|
1 -> { position = position.and(0x00ff) or position.shl(8) }
|
||||||
2 -> { masterVolume = byte }
|
2 -> { pcmUploadLength = pcmUploadLength.and(0xff00) or pcmUploadLength }
|
||||||
3 -> { masterPan = byte }
|
3 -> { pcmUploadLength = pcmUploadLength.and(0x00ff) or pcmUploadLength.shl(8) }
|
||||||
4 -> { byte.let {
|
4 -> { masterVolume = byte }
|
||||||
|
5 -> { masterPan = byte }
|
||||||
|
6 -> { byte.let {
|
||||||
isPcmMode = (it and 0b10000000) != 0
|
isPcmMode = (it and 0b10000000) != 0
|
||||||
loopMode = (it and 3)
|
isPlaying = (it and 0b00010000) != 0
|
||||||
} }
|
} }
|
||||||
5 -> { samplingRateMult = ThreeFiveMiniUfloat(byte) }
|
7 -> { samplingRateMult = ThreeFiveMiniUfloat(byte) }
|
||||||
6 -> { bpm = byte + 24 }
|
8 -> { bpm = byte + 24 }
|
||||||
7 -> { tickRate = byte }
|
9 -> { tickRate = byte }
|
||||||
else -> throw InternalError("Bad offset $index")
|
else -> throw InternalError("Bad offset $index")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getSamplingRate() = 30000 - ((bpm - 24).and(255) or tickRate.and(255).shl(8)).toShort().toInt()
|
||||||
|
fun setSamplingRate(rate: Int) {
|
||||||
|
val rateDiff = (rate.coerceIn(0, 95535) - 30000).toShort().toInt()
|
||||||
|
bpm = rateDiff.and(255) + 24
|
||||||
|
tickRate = rateDiff.ushr(8).and(255)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class TaudPlayData(
|
internal data class TaudPlayData(
|
||||||
var note: Int, // 0..65535
|
var note: Int, // 0..65535
|
||||||
var instrment: Int, // 0..255
|
var instrment: Int, // 0..255
|
||||||
var volume: Int, // 0..63
|
var volume: Int, // 0..63
|
||||||
@@ -204,8 +318,8 @@ class SoundAdapter(val vm: VM) : PeriBase {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class TaudInstVolEnv(var volume: Int, var offset: ThreeFiveMiniUfloat)
|
internal data class TaudInstVolEnv(var volume: Int, var offset: ThreeFiveMiniUfloat)
|
||||||
private data class TaudInst(
|
internal data class TaudInst(
|
||||||
var samplePtr: Int, // 17-bit number
|
var samplePtr: Int, // 17-bit number
|
||||||
var sampleLength: Int,
|
var sampleLength: Int,
|
||||||
var samplingRate: Int,
|
var samplingRate: Int,
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
package net.torvald.tsvm.peripheral
|
||||||
|
|
||||||
|
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.math.MathUtils
|
||||||
|
import com.badlogic.gdx.utils.GdxRuntimeException
|
||||||
|
import org.lwjgl.BufferUtils
|
||||||
|
import org.lwjgl.openal.AL10
|
||||||
|
import org.lwjgl.openal.AL11
|
||||||
|
import java.nio.Buffer
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.IntBuffer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by minjaesong on 2023-01-01.
|
||||||
|
*/
|
||||||
|
/*******************************************************************************
|
||||||
|
* Copyright 2011 See AUTHORS file.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
/** @author Nathan Sweet
|
||||||
|
*/
|
||||||
|
class OpenALBufferedAudioDevice(
|
||||||
|
private val audio: OpenALLwjgl3Audio,
|
||||||
|
val rate: Int,
|
||||||
|
isMono: Boolean,
|
||||||
|
private val bufferSize: Int,
|
||||||
|
private val bufferCount: Int
|
||||||
|
) : AudioDevice {
|
||||||
|
private val channels: Int
|
||||||
|
private var buffers: IntBuffer? = null
|
||||||
|
private var sourceID = -1
|
||||||
|
private val format: Int
|
||||||
|
private var isPlaying = false
|
||||||
|
private var volume = 1f
|
||||||
|
private var renderedSeconds = 0f
|
||||||
|
private val secondsPerBuffer: Float
|
||||||
|
private var bytes: ByteArray? = null
|
||||||
|
private val tempBuffer: ByteBuffer
|
||||||
|
|
||||||
|
init {
|
||||||
|
channels = if (isMono) 1 else 2
|
||||||
|
format = if (channels > 1) AL10.AL_FORMAT_STEREO16 else AL10.AL_FORMAT_MONO16
|
||||||
|
secondsPerBuffer = bufferSize.toFloat() / bytesPerSample / channels / rate
|
||||||
|
tempBuffer = BufferUtils.createByteBuffer(bufferSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeSamples(samples: ShortArray, offset: Int, numSamples: Int) {
|
||||||
|
if (bytes == null || bytes!!.size < numSamples * 2) bytes = ByteArray(numSamples * 2)
|
||||||
|
val end = Math.min(offset + numSamples, samples.size)
|
||||||
|
var i = offset
|
||||||
|
var ii = 0
|
||||||
|
while (i < end) {
|
||||||
|
val sample = samples[i]
|
||||||
|
bytes!![ii++] = (sample.toInt() and 0xFF).toByte()
|
||||||
|
bytes!![ii++] = (sample.toInt() shr 8 and 0xFF).toByte()
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
writeSamples(bytes!!, 0, numSamples * 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeSamples(samples: FloatArray, offset: Int, numSamples: Int) {
|
||||||
|
if (bytes == null || bytes!!.size < numSamples * 2) bytes = ByteArray(numSamples * 2)
|
||||||
|
val end = Math.min(offset + numSamples, samples.size)
|
||||||
|
var i = offset
|
||||||
|
var ii = 0
|
||||||
|
while (i < end) {
|
||||||
|
var floatSample = samples[i]
|
||||||
|
floatSample = MathUtils.clamp(floatSample, -1f, 1f)
|
||||||
|
val intSample = (floatSample * 32767).toInt()
|
||||||
|
bytes!![ii++] = (intSample and 0xFF).toByte()
|
||||||
|
bytes!![ii++] = (intSample shr 8 and 0xFF).toByte()
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
writeSamples(bytes!!, 0, numSamples * 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun audioObtainSource(isMusic: Boolean): Int {
|
||||||
|
val obtainSourceMethod = OpenALLwjgl3Audio::class.java.getDeclaredMethod("obtainSource", java.lang.Boolean.TYPE)
|
||||||
|
obtainSourceMethod.isAccessible = true
|
||||||
|
return obtainSourceMethod.invoke(audio, isMusic) as Int
|
||||||
|
}
|
||||||
|
private fun audioFreeSource(sourceID: Int) {
|
||||||
|
val freeSourceMethod = OpenALLwjgl3Audio::class.java.getDeclaredMethod("freeSource", java.lang.Integer.TYPE)
|
||||||
|
freeSourceMethod.isAccessible = true
|
||||||
|
freeSourceMethod.invoke(audio, sourceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeSamples(data: ByteArray, offset: Int, length: Int) {
|
||||||
|
var offset = offset
|
||||||
|
var length = length
|
||||||
|
require(length >= 0) { "length cannot be < 0." }
|
||||||
|
if (sourceID == -1) {
|
||||||
|
sourceID = audioObtainSource(true)
|
||||||
|
if (sourceID == -1) return
|
||||||
|
if (buffers == null) {
|
||||||
|
buffers = BufferUtils.createIntBuffer(bufferCount)
|
||||||
|
AL10.alGenBuffers(buffers)
|
||||||
|
if (AL10.alGetError() != AL10.AL_NO_ERROR) throw GdxRuntimeException("Unabe to allocate audio buffers.")
|
||||||
|
}
|
||||||
|
AL10.alSourcei(sourceID, AL10.AL_LOOPING, AL10.AL_FALSE)
|
||||||
|
AL10.alSourcef(sourceID, AL10.AL_GAIN, volume)
|
||||||
|
// Fill initial buffers.
|
||||||
|
var queuedBuffers = 0
|
||||||
|
for (i in 0 until bufferCount) {
|
||||||
|
val bufferID = buffers!![i]
|
||||||
|
val written = Math.min(bufferSize, length)
|
||||||
|
(tempBuffer as Buffer).clear()
|
||||||
|
(tempBuffer.put(data, offset, written) as Buffer).flip()
|
||||||
|
AL10.alBufferData(bufferID, format, tempBuffer, rate)
|
||||||
|
AL10.alSourceQueueBuffers(sourceID, bufferID)
|
||||||
|
length -= written
|
||||||
|
offset += written
|
||||||
|
queuedBuffers++
|
||||||
|
}
|
||||||
|
// Queue rest of buffers, empty.
|
||||||
|
(tempBuffer as Buffer).clear().flip()
|
||||||
|
for (i in queuedBuffers until bufferCount) {
|
||||||
|
val bufferID = buffers!![i]
|
||||||
|
AL10.alBufferData(bufferID, format, tempBuffer, rate)
|
||||||
|
AL10.alSourceQueueBuffers(sourceID, bufferID)
|
||||||
|
}
|
||||||
|
AL10.alSourcePlay(sourceID)
|
||||||
|
isPlaying = true
|
||||||
|
}
|
||||||
|
while (length > 0) {
|
||||||
|
val written = fillBuffer(data, offset, length)
|
||||||
|
length -= written
|
||||||
|
offset += written
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Blocks until some of the data could be buffered. */
|
||||||
|
private fun fillBuffer(data: ByteArray, offset: Int, length: Int): Int {
|
||||||
|
val written = Math.min(bufferSize, length)
|
||||||
|
outer@ while (true) {
|
||||||
|
var buffers = AL10.alGetSourcei(sourceID, AL10.AL_BUFFERS_PROCESSED)
|
||||||
|
while (buffers-- > 0) {
|
||||||
|
val bufferID = AL10.alSourceUnqueueBuffers(sourceID)
|
||||||
|
if (bufferID == AL10.AL_INVALID_VALUE) break
|
||||||
|
renderedSeconds += secondsPerBuffer
|
||||||
|
(tempBuffer as Buffer).clear()
|
||||||
|
(tempBuffer.put(data, offset, written) as Buffer).flip()
|
||||||
|
AL10.alBufferData(bufferID, format, tempBuffer, rate)
|
||||||
|
AL10.alSourceQueueBuffers(sourceID, bufferID)
|
||||||
|
break@outer
|
||||||
|
}
|
||||||
|
// Wait for buffer to be free.
|
||||||
|
try {
|
||||||
|
Thread.sleep((1000 * secondsPerBuffer).toLong())
|
||||||
|
}
|
||||||
|
catch (ignored: InterruptedException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A buffer underflow will cause the source to stop.
|
||||||
|
if (!isPlaying || AL10.alGetSourcei(sourceID, AL10.AL_SOURCE_STATE) != AL10.AL_PLAYING) {
|
||||||
|
AL10.alSourcePlay(sourceID)
|
||||||
|
isPlaying = true
|
||||||
|
}
|
||||||
|
return written
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
if (sourceID == -1) return
|
||||||
|
audioFreeSource(sourceID)
|
||||||
|
sourceID = -1
|
||||||
|
renderedSeconds = 0f
|
||||||
|
isPlaying = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isPlaying(): Boolean {
|
||||||
|
return if (sourceID == -1) false else isPlaying
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setVolume(volume: Float) {
|
||||||
|
this.volume = volume
|
||||||
|
if (sourceID != -1) AL10.alSourcef(sourceID, AL10.AL_GAIN, volume)
|
||||||
|
}
|
||||||
|
|
||||||
|
var position: Float
|
||||||
|
get() = if (sourceID == -1) 0f else renderedSeconds + AL10.alGetSourcef(sourceID, AL11.AL_SEC_OFFSET)
|
||||||
|
set(position) {
|
||||||
|
renderedSeconds = position
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getChannels(): Int {
|
||||||
|
return if (format == AL10.AL_FORMAT_STEREO16) 2 else 1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
if (buffers == null) return
|
||||||
|
if (sourceID != -1) {
|
||||||
|
audioFreeSource(sourceID)
|
||||||
|
sourceID = -1
|
||||||
|
}
|
||||||
|
AL10.alDeleteBuffers(buffers)
|
||||||
|
buffers = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isMono(): Boolean {
|
||||||
|
return channels == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLatency(): Int {
|
||||||
|
return (secondsPerBuffer * bufferCount * 1000).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val bytesPerSample = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -76,6 +76,17 @@ class TestDiskDrive(private val vm: VM, private val driveNum: Int, theRootPath:
|
|||||||
private val messageComposeBuffer = ByteArrayOutputStream(BLOCK_SIZE) // always use this and don't alter blockSendBuffer please
|
private val messageComposeBuffer = ByteArrayOutputStream(BLOCK_SIZE) // always use this and don't alter blockSendBuffer please
|
||||||
private var blockSendBuffer = ByteArray(1)
|
private var blockSendBuffer = ByteArray(1)
|
||||||
private var blockSendCount = 0
|
private var blockSendCount = 0
|
||||||
|
/*set(value) {
|
||||||
|
println("[TestDiskDrive] blockSendCount $field -> $value")
|
||||||
|
val indentation = " ".repeat(this.javaClass.simpleName.length + 4)
|
||||||
|
Thread.currentThread().stackTrace.forEachIndexed { index, it ->
|
||||||
|
if (index == 1)
|
||||||
|
println("[${this.javaClass.simpleName}]> $it")
|
||||||
|
else if (index in 1..8)
|
||||||
|
println("$indentation$it")
|
||||||
|
}
|
||||||
|
field = value
|
||||||
|
}*/
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -112,6 +123,8 @@ class TestDiskDrive(private val vm: VM, private val driveNum: Int, theRootPath:
|
|||||||
blockSendBuffer.size % BLOCK_SIZE
|
blockSendBuffer.size % BLOCK_SIZE
|
||||||
else BLOCK_SIZE
|
else BLOCK_SIZE
|
||||||
|
|
||||||
|
// println("blockSendCount = ${blockSendCount}; sendSize = $sendSize; blockSendBuffer.size = ${blockSendBuffer.size}")
|
||||||
|
|
||||||
recipient.writeout(ByteArray(sendSize) {
|
recipient.writeout(ByteArray(sendSize) {
|
||||||
blockSendBuffer[blockSendCount * BLOCK_SIZE + it]
|
blockSendBuffer[blockSendCount * BLOCK_SIZE + it]
|
||||||
})
|
})
|
||||||
@@ -163,6 +176,8 @@ class TestDiskDrive(private val vm: VM, private val driveNum: Int, theRootPath:
|
|||||||
else {
|
else {
|
||||||
val inputString = inputData.trimNull().toString(VM.CHARSET)
|
val inputString = inputData.trimNull().toString(VM.CHARSET)
|
||||||
|
|
||||||
|
// println("[TestDiskDrive] $inputString")
|
||||||
|
|
||||||
if (inputString.startsWith("DEVRST\u0017")) {
|
if (inputString.startsWith("DEVRST\u0017")) {
|
||||||
printdbg("Device Reset")
|
printdbg("Device Reset")
|
||||||
//readModeLength = -1
|
//readModeLength = -1
|
||||||
|
|||||||
Reference in New Issue
Block a user