http, pcspeaker driver

Former-commit-id: a3ad38695e8c1e1040a6a3f4b8470217e8a98489
Former-commit-id: cd3809edcae7b26e7b3d846dab17f1f63761f30f
This commit is contained in:
Song Minjae
2016-09-29 23:04:04 +09:00
parent 793bca4b55
commit 1b4aca320f
35 changed files with 678 additions and 385 deletions

View File

@@ -20,8 +20,8 @@ import org.newdawn.slick.state.StateBasedGame
class StateVTTest : BasicGameState() {
// HiRes: 100x64, LoRes: 80x25
val computerInside = BaseTerrarumComputer()
val vt = SimpleTextTerminal(SimpleTextTerminal.WHITE, 80, 25,
val computerInside = BaseTerrarumComputer(8)
val vt = SimpleTextTerminal(SimpleTextTerminal.GREEN, 80, 25,
computerInside, colour = false, hires = false)

View File

@@ -1,6 +1,5 @@
package net.torvald.terrarum.virtualcomputer.computer
import com.jme3.math.FastMath
import org.luaj.vm2.Globals
import org.luaj.vm2.LuaError
import org.luaj.vm2.LuaTable
@@ -11,6 +10,7 @@ import org.luaj.vm2.lib.jse.JsePlatform
import net.torvald.terrarum.KVHashMap
import net.torvald.terrarum.gameactors.roundInt
import net.torvald.terrarum.virtualcomputer.luaapi.*
import net.torvald.terrarum.virtualcomputer.peripheral.*
import net.torvald.terrarum.virtualcomputer.terminal.*
import net.torvald.terrarum.virtualcomputer.worldobject.ComputerPartsCodex
import org.lwjgl.BufferUtils
@@ -30,10 +30,13 @@ import java.util.*
* @param term : terminal that is connected to the computer fixtures, null if not connected any.
* Created by minjaesong on 16-09-10.
*/
class BaseTerrarumComputer() {
class BaseTerrarumComputer(peripheralSlots: Int) {
var maxPeripherals: Int = peripheralSlots
private set
val DEBUG_UNLIMITED_MEM = false
val DEBUG = false
val DEBUG = true
lateinit var luaJ_globals: Globals
@@ -71,6 +74,8 @@ class BaseTerrarumComputer() {
lateinit var term: Teletype
private set
val peripheralTable = ArrayList<Peripheral>()
// os-related functions. These are called "machine" library-wise.
private val startupTimestamp: Long = System.currentTimeMillis()
/** Time elapsed since the power is on. */
@@ -102,6 +107,28 @@ class BaseTerrarumComputer() {
computerValue["boot"] = computerValue.getAsString("hda")!!
}
fun attachPeripheral(peri: Peripheral) {
if (peripheralTable.size < maxPeripherals) {
peripheralTable.add(peri)
peri.loadLib()
println("[BaseTerrarumComputer] loading peripheral $peri")
}
else {
throw Error("No vacant peripheral slot")
}
}
fun detachPeripheral(peri: Peripheral) {
if (peripheralTable.contains(peri)) {
peripheralTable.remove(peri)
peri.unloadLib()
println("[BaseTerrarumComputer] unloading peripheral $peri")
}
else {
throw IllegalArgumentException("Peripheral not exists: $peri")
}
}
fun attachTerminal(term: Teletype) {
this.term = term
initSandbox(term)
@@ -126,7 +153,7 @@ class BaseTerrarumComputer() {
Filesystem(luaJ_globals, this)
HostAccessProvider(luaJ_globals, this)
Input(luaJ_globals, this)
Http(luaJ_globals, this)
PeripheralInternet(luaJ_globals, this)
PcSpeakerDriver(luaJ_globals, this)
WorldInformationProvider(luaJ_globals)
@@ -146,7 +173,16 @@ class BaseTerrarumComputer() {
luaJ_globals["computer"] = LuaTable()
// rest of the "computer" APIs should be implemented in BOOT.lua
if (DEBUG) luaJ_globals["emittone"] = ComputerEmitTone(this)
// load every peripheral if we're in DEBUG
if (DEBUG) {
maxPeripherals = 32
attachPeripheral(PeripheralInternet(luaJ_globals, this))
attachPeripheral(PeripheralPSG(luaJ_globals, this))
// ...
}
}
var threadTimer = 0
@@ -253,7 +289,7 @@ class BaseTerrarumComputer() {
class ComputerEmitTone(val computer: BaseTerrarumComputer) : TwoArgFunction() {
override fun call(millisec: LuaValue, freq: LuaValue): LuaValue {
computer.playTone(millisec.toint(), freq.tofloat())
computer.playTone(millisec.checkint(), freq.checkdouble())
return LuaValue.NONE
}
}
@@ -264,7 +300,7 @@ class BaseTerrarumComputer() {
private val beepMaxLen = 10000
// let's regard it as a tracker...
private val beepQueue = ArrayList<Pair<Int, Float>>()
private val beepQueue = ArrayList<Pair<Int, Double>>()
private var beepCursor = -1
private var beepQueueLineExecTimer = 0 // millisec
private var beepQueueFired = false
@@ -285,7 +321,8 @@ class BaseTerrarumComputer() {
// complete emitTone queue
if (beepCursor >= beepQueue.size) {
clearBeepQueue()
if (DEBUG) println("!! Beep queue clear")
AL.destroy()
if (DEBUG) println("[BaseTerrarumComputer] !! Beep queue clear")
}
// actually play queue
@@ -303,7 +340,7 @@ class BaseTerrarumComputer() {
beepQueueLineExecTimer = 0
}
fun enqueueBeep(duration: Int, freq: Float) {
fun enqueueBeep(duration: Int, freq: Double) {
beepQueue.add(Pair(Math.min(duration, beepMaxLen), freq))
}
@@ -326,48 +363,48 @@ class BaseTerrarumComputer() {
*
* ,---. (true, true) ,---- (true, false) ----. (false, true) ----- (false, false)
*/
private fun makeAudioData(duration: Int, freq: Float,
private fun makeAudioData(duration: Int, freq: Double,
rampUp: Boolean = true, rampDown: Boolean = true): ByteBuffer {
val audioData = BufferUtils.createByteBuffer(duration.times(sampleRate).div(1000))
val realDuration = duration * sampleRate / 1000
val chopSize = freq / sampleRate
val amp = Math.max(4600f / freq, 1f)
val nHarmonics = if (freq >= 22050f) 1
else if (freq >= 11025f) 2
else if (freq >= 5512.5f) 3
else if (freq >= 2756.25f) 4
else if (freq >= 1378.125f) 5
else if (freq >= 689.0625f) 6
val amp = Math.max(4600.0 / freq, 1.0)
val nHarmonics = if (freq >= 22050.0) 1
else if (freq >= 11025.0) 2
else if (freq >= 5512.5) 3
else if (freq >= 2756.25) 4
else if (freq >= 1378.125) 5
else if (freq >= 689.0625) 6
else 7
val transitionThre = 974.47218f
val transitionThre = 974.47218
// TODO volume ramping?
if (freq == 0f) {
if (freq == 0.0) {
for (x in 0..realDuration - 1) {
audioData.put(0x00.toByte())
}
}
else if (freq < transitionThre) { // chopper generator (for low freq)
for (x in 0..realDuration - 1) {
var sine: Float = amp * FastMath.cos(FastMath.TWO_PI * x * chopSize)
if (sine > 0.79f) sine = 0.79f
else if (sine < -0.79f) sine = -0.79f
var sine: Double = amp * Math.cos(Math.PI * 2 * x * chopSize)
if (sine > 0.79) sine = 0.79
else if (sine < -0.79) sine = -0.79
audioData.put(
(0.5f + 0.5f * sine).times(0xFF).roundInt().toByte()
(0.5 + 0.5 * sine).times(0xFF).roundInt().toByte()
)
}
}
else { // harmonics generator (for high freq)
for (x in 0..realDuration - 1) {
var sine: Float = 0f
var sine: Double = 0.0
for (k in 1..nHarmonics) { // mix only odd harmonics in order to make a squarewave
sine += FastMath.sin(FastMath.TWO_PI * (2*k - 1) * chopSize * x) / (2*k - 1)
sine += Math.sin(Math.PI * 2 * (2*k - 1) * chopSize * x) / (2*k - 1)
}
audioData.put(
(0.5f + 0.5f * sine).times(0xFF).roundInt().toByte()
(0.5 + 0.5 * sine).times(0xFF).roundInt().toByte()
)
}
}
@@ -377,7 +414,7 @@ class BaseTerrarumComputer() {
return audioData
}
private fun playTone(leninmilli: Int, freq: Float) {
private fun playTone(leninmilli: Int, freq: Double) {
audioData = makeAudioData(leninmilli, freq)

View File

@@ -1,12 +0,0 @@
package net.torvald.terrarum.virtualcomputer.luaapi
import org.luaj.vm2.Globals
import net.torvald.terrarum.virtualcomputer.computer.BaseTerrarumComputer
/**
* Provides internet access, if @param computer has internet card(s).
*
* Created by minjaesong on 16-09-24.
*/
internal class Http(globals: Globals, computer: BaseTerrarumComputer) {
}

View File

@@ -1,9 +0,0 @@
package net.torvald.terrarum.virtualcomputer.luaapi
/**
* Virtual driver for 4-track squarewave soundcard
*
* Created by minjaesong on 16-09-27.
*/
class Kukeiha {
}

View File

@@ -6,23 +6,79 @@ import org.luaj.vm2.LuaValue
import org.luaj.vm2.lib.TwoArgFunction
import org.luaj.vm2.lib.ZeroArgFunction
import net.torvald.terrarum.virtualcomputer.computer.BaseTerrarumComputer
import org.luaj.vm2.lib.OneArgFunction
/**
* PC Speaker driver and arpeggiator (MONOTONE-style 4 channels)
*
* Notes are tuned to A440, equal temperament. This is an ISO standard.
*
* Created by minjaesong on 16-09-27.
*/
class PcSpeakerDriver(globals: Globals, host: BaseTerrarumComputer) {
class PcSpeakerDriver(val globals: Globals, host: BaseTerrarumComputer) {
init {
globals["speaker"] = LuaTable()
globals["speaker"]["enqueue"] = EnqueueTone(host)
globals["speaker"]["clear"] = ClearQueue(host)
globals["speaker"]["retune"] = Retune(globals)
globals["speaker"]["resetTune"] = ResetTune(globals)
globals["speaker"]["toFreq"] = StringToFrequency(globals)
globals["speaker"]["__basefreq__"] = LuaValue.valueOf(BASE_FREQ) // every other PSGs should use this very variable
// constants
// e.g. speaker.A0 returns number 1
fun Int.toNote(): String = NOTE_NAMES[this % 12] + this.plus(8).div(12).toString()
fun Int.toNoteAlt(): String = NOTE_NAMES_ALT[this % 12] + this.plus(8).div(12).toString()
for (i in 1..126) {
globals["speaker"][i.toNote()] = i // sharps
globals["speaker"][i.toNoteAlt()] = i // flats
}
}
companion object {
val BASE_FREQ = 27.5 // frequency of A0
val NOTE_NAMES = arrayOf("GS", "A", "AS", "B", "C", "CS",
"D", "DS", "E", "F", "FS", "G")
val NOTE_NAMES_ALT = arrayOf("Ab", "A", "Bb", "B", "C", "Db",
"D", "Eb", "E", "F", "Gb", "G")
/** @param basefreq: Frequency of A-0 */
fun Int.toFreq(basefreq: Double): Double = basefreq * Math.pow(2.0, (this - 1.0) / 12.0)
/** @param "A-5", "B4", "C#5", ... */
fun String.toNoteIndex(): Int {
var notestr = this.replace("-", "")
notestr = notestr.replace("#", "S")
val baseNote = if (notestr.contains("S") || notestr.contains("b"))
notestr.substring(0, 2)
else
notestr.substring(0, 1)
var note: Int = NOTE_NAMES.indexOf(baseNote) // [0-11]
if (note < 0) note = NOTE_NAMES_ALT.indexOf(baseNote) // search again
if (note < 0) throw IllegalArgumentException("Unknown note: $this") // failed to search
val octave: Int = notestr.replace(Regex("""[^0-9]"""), "").toInt()
return octave.minus(if (note >= 4) 1 else 0) * 12 + note
}
}
class EnqueueTone(val host: BaseTerrarumComputer) : TwoArgFunction() {
/**
* @param freq: number (hertz) or string (A-4, A4, B#2, ...)
*/
override fun call(millisec: LuaValue, freq: LuaValue): LuaValue {
host.enqueueBeep(millisec.checkint(), freq.tofloat())
if (freq.isnumber())
host.enqueueBeep(millisec.checkint(), freq.checkdouble())
else {
host.enqueueBeep(millisec.checkint(),
freq.checkjstring().toNoteIndex()
.toFreq(host.luaJ_globals["speaker"]["__basefreq__"].checkdouble())
)
}
return LuaValue.NONE
}
}
@@ -34,4 +90,53 @@ class PcSpeakerDriver(globals: Globals, host: BaseTerrarumComputer) {
}
}
class Retune(val globals: Globals) : OneArgFunction() {
/**
* Examples: C256, A440, A#440, ...
*/
override fun call(arg: LuaValue): LuaValue {
val tuneName = arg.checkjstring()
val baseNote = if (tuneName.contains("#") || tuneName.contains("b")) tuneName.substring(0, 2) else tuneName.substring(0, 1)
val freq = tuneName.replace(Regex("""[^0-9]"""), "").toInt()
// we're assuming C4, C#4, ... A4, A#4, B4
val diffPivot = arrayOf(11, 12, 13, 14, 3, 4, 5, 6, 7, 8, 9, 10) // 2^(12 / n)
var diff = diffPivot[NOTE_NAMES.indexOf(baseNote)]
if (diff < 0) diff = diffPivot[NOTE_NAMES_ALT.indexOf(baseNote)] // search again
if (diff < 0) throw IllegalArgumentException("Unknown note: $baseNote") // failed to search
val exp = 12.0 / diff
val basefreq = freq * Math.pow(2.0, exp) / 32.0 // converts whatever baseNote to A0
globals["speaker"]["__basefreq__"] = basefreq
return LuaValue.NONE
}
}
class ResetTune(val globals: Globals) : ZeroArgFunction() {
override fun call(): LuaValue {
globals["speaker"]["__basefreq__"] = LuaValue.valueOf(BASE_FREQ)
return LuaValue.NONE
}
}
/**
* usage = speaker.toFreq(speaker.AS5) --'S' is a substitution for '#'
*/
class StringToFrequency(val globals: Globals) : OneArgFunction() {
/**
* @param arg: number (note index) or string (A-4, A4, B#2, ...)
*/
override fun call(arg: LuaValue): LuaValue {
val note = if (arg.isint()) arg.checkint()
else {
arg.checkjstring().toNoteIndex()
}
val basefreq = globals["speaker"]["__basefreq__"].checkdouble()
return LuaValue.valueOf(note.toFreq(basefreq))
}
}
}

View File

@@ -0,0 +1,20 @@
package net.torvald.terrarum.virtualcomputer.peripheral
import org.luaj.vm2.Globals
import org.luaj.vm2.LuaTable
import org.luaj.vm2.LuaValue
/**
* Created by minjaesong on 16-09-29.
*/
open class Peripheral(val luaG: Globals, val tableName: String) {
open fun loadLib() {
luaG[tableName] = LuaTable()
}
open fun unloadLib() {
luaG[tableName] = LuaValue.NIL
}
override fun toString(): String = tableName
}

View File

@@ -0,0 +1,41 @@
package net.torvald.terrarum.virtualcomputer.peripheral
import org.luaj.vm2.Globals
import net.torvald.terrarum.virtualcomputer.computer.BaseTerrarumComputer
import org.luaj.vm2.LuaValue
import org.luaj.vm2.lib.OneArgFunction
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.URL
/**
* Provides internet access.
*
* Created by minjaesong on 16-09-24.
*/
internal class PeripheralInternet(val globals: Globals, val host: BaseTerrarumComputer)
: Peripheral(globals, "internet"){
override fun loadLib() {
super.loadLib()
globals["internet"]["fetch"] = FetchWebPage()
}
class FetchWebPage() : OneArgFunction() {
override fun call(urlstr: LuaValue): LuaValue {
val url = URL(urlstr.checkjstring())
val inputstream = BufferedReader(InputStreamReader(url.openStream()))
var inline = ""
var readline = inputstream.readLine()
while (readline != null) {
inline += readline
readline = inputstream.readLine()
}
inputstream.close()
return LuaValue.valueOf(inline)
}
}
}

View File

@@ -0,0 +1,21 @@
package net.torvald.terrarum.virtualcomputer.peripheral
import net.torvald.terrarum.virtualcomputer.computer.BaseTerrarumComputer
import org.luaj.vm2.Globals
import org.luaj.vm2.LuaTable
import org.luaj.vm2.LuaValue
/**
* Virtual driver for 4-track squarewave PSG, which has no ability of changing a duty cycle
* but has a volume control
*
* Created by minjaesong on 16-09-27.
*/
internal class PeripheralPSG(val globals: Globals, val host: BaseTerrarumComputer)
: Peripheral(globals, "psg") {
override fun loadLib() {
super.loadLib()
}
}

View File

@@ -318,7 +318,7 @@ open class SimpleTextTerminal(
* @param duration: milliseconds
* @param freg: Frequency (float)
*/
override fun emitTone(duration: Int, freq: Float) {
override fun emitTone(duration: Int, freq: Double) {
// println("!! Beep playing row $beepCursor, d ${Math.min(duration, maxDuration)} f $freq")
host.clearBeepQueue()
host.enqueueBeep(duration, freq)
@@ -328,19 +328,19 @@ open class SimpleTextTerminal(
override fun bell(pattern: String) {
host.clearBeepQueue()
val freq: Float =
val freq: Double =
if (host.luaJ_globals["computer"]["bellpitch"].isnil())
1000f
1000.0
else
host.luaJ_globals["computer"]["bellpitch"].checkdouble().toFloat()
host.luaJ_globals["computer"]["bellpitch"].checkdouble()
for (c in pattern) {
when (c) {
'.' -> { host.enqueueBeep(80, freq); host.enqueueBeep(50, 0f) }
'-' -> { host.enqueueBeep(200, freq); host.enqueueBeep(50, 0f) }
'=' -> { host.enqueueBeep(500, freq); host.enqueueBeep(50, 0f) }
' ' -> { host.enqueueBeep(200, 0f) }
',' -> { host.enqueueBeep(50, 0f) }
'.' -> { host.enqueueBeep(80, freq); host.enqueueBeep(50, 0.0) }
'-' -> { host.enqueueBeep(200, freq); host.enqueueBeep(50, 0.0) }
'=' -> { host.enqueueBeep(500, freq); host.enqueueBeep(50, 0.0) }
' ' -> { host.enqueueBeep(200, 0.0) }
',' -> { host.enqueueBeep(50, 0.0) }
else -> throw IllegalArgumentException("Unacceptable pattern: $c (from '$pattern')")
}
}

View File

@@ -61,7 +61,7 @@ interface Terminal : Teletype {
* @param duration: milliseconds
* @param freg: Frequency (float)
*/
fun emitTone(duration: Int, freq: Float)
fun emitTone(duration: Int, freq: Double)
override fun bell(pattern: String)
/** Requires keyPressed() event to be processed.

View File

@@ -14,7 +14,7 @@ import java.util.*
*/
class FixtureBasicTerminal(phosphor: Color) : FixtureBase() {
val computer = BaseTerrarumComputer()
val computer = BaseTerrarumComputer(8)
val vt: Terminal = SimpleTextTerminal(phosphor, 80, 25, computer)
val ui = UITextTerminal(vt)

View File

@@ -33,7 +33,7 @@ open class FixtureComputerBase() : FixtureBase() {
fun attachTerminal(uuid: String) {
val fetchedTerminal = getTerminalByUUID(uuid)
computerInside = BaseTerrarumComputer()
computerInside = BaseTerrarumComputer(8)
computerInside!!.attachTerminal(fetchedTerminal!!)
actorValue["computerid"] = computerInside!!.UUID
}