mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-07 19:51:51 +09:00
one idea down, one last idea to go
This commit is contained in:
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()
|
||||
|
||||
const val PERITYPE_GPU_AND_TERM = "gpu"
|
||||
const val PERITYPE_SOUND = "snd"
|
||||
}
|
||||
|
||||
internal fun translateAddr(addr: Long): Pair<Any?, Long> {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
@@ -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 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
|
||||
|
||||
Reference in New Issue
Block a user