one idea down, one last idea to go

This commit is contained in:
minjaesong
2023-01-01 02:02:34 +09:00
parent cecf48178c
commit bb7bf2c6f1
9 changed files with 607 additions and 54 deletions

View 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())
}

View File

@@ -149,6 +149,7 @@ class VM(
val USER_SPACE_SIZE = 8192.kB()
const val PERITYPE_GPU_AND_TERM = "gpu"
const val PERITYPE_SOUND = "snd"
}
internal fun translateAddr(addr: Long): Pair<Any?, Long> {

View File

@@ -75,6 +75,7 @@ object VMRunnerFactory {
bind.putMember("base64", Base64Delegate)
bind.putMember("com", SerialHelperDelegate(vm))
bind.putMember("dma", DMADelegate(vm))
bind.putMember("audio", AudioJSR223Delegate(vm))
bind.putMember("parallel", ringOneParallel)
val fr = FileReader("$assetsRoot/JS_INIT.js")

View File

@@ -1,23 +1,102 @@
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.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
/**
* 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)
private val instruments = Array(256) { TaudInst() }
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() }
private val pcmBin = UnsafeHelper.allocate(65536L)
companion object {
const val SAMPLING_RATE = 30000
}
internal val sampleBin = UnsafeHelper.allocate(114687L)
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 {
return when (val adi = addr.toInt()) {
@@ -41,10 +120,10 @@ class SoundAdapter(val vm: VM) : PeriBase {
override fun mmio_read(addr: Long): Byte {
val adi = addr.toInt()
return when (adi) {
in 0..15 -> playheads[0].read(adi)
in 16..31 -> playheads[1].read(adi - 16)
in 32..47 -> playheads[2].read(adi - 32)
in 48..63 -> playheads[3].read(adi - 48)
in 0..9 -> playheads[0].read(adi)
in 10..19 -> playheads[1].read(adi - 10)
in 20..29 -> playheads[2].read(adi - 20)
in 30..39 -> playheads[3].read(adi - 30)
in 32768..65535 -> (adi - 32768).let {
cueSheet[it / 16].read(it % 15)
}
@@ -57,10 +136,10 @@ class SoundAdapter(val vm: VM) : PeriBase {
val adi = addr.toInt()
val bi = byte.toUint()
when (adi) {
in 0..15 -> { playheads[0].write(adi, bi) }
in 16..31 -> { playheads[1].write(adi - 16, bi) }
in 32..47 -> { playheads[2].write(adi - 32, bi) }
in 48..63 -> { playheads[3].write(adi - 48, bi) }
in 0..9 -> { playheads[0].write(adi, bi) }
in 10..19 -> { playheads[1].write(adi - 10, bi) }
in 20..29 -> { playheads[2].write(adi - 20, bi) }
in 30..39 -> { playheads[3].write(adi - 30, bi) }
in 32768..65535 -> { (adi - 32768).let {
cueSheet[it / 16].write(it % 15, bi)
} }
@@ -69,6 +148,9 @@ class SoundAdapter(val vm: VM) : PeriBase {
}
override fun dispose() {
renderThreads.forEach { it.interrupt() }
audioDevices.forEach { it.dispose() }
// freeAlSources()
sampleBin.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
*/
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
private data class PlayCue(
internal data class PlayCue(
val patterns: IntArray = IntArray(15) { it },
var instruction: PlayInstruction = PlayInstNop
) {
@@ -124,18 +226,19 @@ class SoundAdapter(val vm: VM) : PeriBase {
}
}
private open class PlayInstruction(val arg: Int)
private class PlayInstGoBack(arg: Int) : PlayInstruction(arg)
private class PlayInstSkip(arg: Int) : PlayInstruction(arg)
private object PlayInstNop : PlayInstruction(0)
internal open class PlayInstruction(val arg: Int)
internal class PlayInstGoBack(arg: Int) : PlayInstruction(arg)
internal class PlayInstSkip(arg: Int) : PlayInstruction(arg)
internal object PlayInstNop : PlayInstruction(0)
private data class Playhead(
internal data class Playhead(
var position: Int = 0,
var pcmUploadLength: Int = 0,
var masterVolume: Int = 0,
var masterPan: Int = 0,
// flags
var isPcmMode: Boolean = false,
var loopMode: Int = 0,
var isPlaying: Boolean = false,
var samplingRateMult: ThreeFiveMiniUfloat = ThreeFiveMiniUfloat(32),
var bpm: Int = 120, // "stored" as 96
var tickRate: Int = 6
@@ -143,32 +246,43 @@ class SoundAdapter(val vm: VM) : PeriBase {
fun read(index: Int): Byte = when (index) {
0 -> position.toByte()
1 -> position.ushr(8).toByte()
2 -> masterVolume.toByte()
3 -> masterPan.toByte()
4 -> (isPcmMode.toInt().shl(7) or loopMode.and(3)).toByte()
5 -> samplingRateMult.index.toByte()
6 -> (bpm - 24).toByte()
7 -> tickRate.toByte()
2 -> pcmUploadLength.toByte()
3 -> pcmUploadLength.ushr(8).toByte()
4 -> masterVolume.toByte()
5 -> masterPan.toByte()
6 -> (isPcmMode.toInt().shl(7) or isPlaying.toInt().shl(4)).toByte()
7 -> samplingRateMult.index.toByte()
8 -> (bpm - 24).toByte()
9 -> tickRate.toByte()
else -> throw InternalError("Bad offset $index")
}
fun write(index: Int, byte: Int) = when (index) {
0 -> { position = position.and(0xff00) or position }
1 -> { position = position.and(0x00ff) or position.shl(8) }
2 -> { masterVolume = byte }
3 -> { masterPan = byte }
4 -> { byte.let {
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 {
isPcmMode = (it and 0b10000000) != 0
loopMode = (it and 3)
isPlaying = (it and 0b00010000) != 0
} }
5 -> { samplingRateMult = ThreeFiveMiniUfloat(byte) }
6 -> { bpm = byte + 24 }
7 -> { tickRate = byte }
7 -> { samplingRateMult = ThreeFiveMiniUfloat(byte) }
8 -> { bpm = byte + 24 }
9 -> { tickRate = byte }
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 instrment: Int, // 0..255
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)
private data class TaudInst(
internal data class TaudInstVolEnv(var volume: Int, var offset: ThreeFiveMiniUfloat)
internal data class TaudInst(
var samplePtr: Int, // 17-bit number
var sampleLength: Int,
var samplingRate: Int,

View File

@@ -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
}
}

View File

@@ -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 var blockSendBuffer = ByteArray(1)
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 {
@@ -112,6 +123,8 @@ class TestDiskDrive(private val vm: VM, private val driveNum: Int, theRootPath:
blockSendBuffer.size % BLOCK_SIZE
else BLOCK_SIZE
// println("blockSendCount = ${blockSendCount}; sendSize = $sendSize; blockSendBuffer.size = ${blockSendBuffer.size}")
recipient.writeout(ByteArray(sendSize) {
blockSendBuffer[blockSendCount * BLOCK_SIZE + it]
})
@@ -163,6 +176,8 @@ class TestDiskDrive(private val vm: VM, private val driveNum: Int, theRootPath:
else {
val inputString = inputData.trimNull().toString(VM.CHARSET)
// println("[TestDiskDrive] $inputString")
if (inputString.startsWith("DEVRST\u0017")) {
printdbg("Device Reset")
//readModeLength = -1