diff options
Diffstat (limited to 'src/main/kotlin/space/autistic/radio/cli/OfflineSimulator.kt')
-rw-r--r-- | src/main/kotlin/space/autistic/radio/cli/OfflineSimulator.kt | 237 |
1 files changed, 0 insertions, 237 deletions
diff --git a/src/main/kotlin/space/autistic/radio/cli/OfflineSimulator.kt b/src/main/kotlin/space/autistic/radio/cli/OfflineSimulator.kt deleted file mode 100644 index bc16814..0000000 --- a/src/main/kotlin/space/autistic/radio/cli/OfflineSimulator.kt +++ /dev/null @@ -1,237 +0,0 @@ -package space.autistic.radio.cli - -import org.joml.Vector2f -import space.autistic.radio.complex.cmul -import space.autistic.radio.fmsim.FmFullConstants -import space.autistic.radio.fmsim.FmFullDemodulator -import space.autistic.radio.fmsim.FmFullModulator -import java.io.FileInputStream -import java.io.FileOutputStream -import java.io.InputStream -import java.net.URI -import java.nio.ByteBuffer -import java.nio.ByteOrder -import java.nio.FloatBuffer -import kotlin.io.path.inputStream -import kotlin.io.path.toPath -import kotlin.math.min -import kotlin.system.exitProcess - -fun printUsage() { - println("Usage: OfflineSimulator <-o|-O> OUTFILE.raw {[-p POWER] [-l|-h] [-m] file:///FILE.raw} [-m]") - println(" file:///FILE.raw (or ./FILE.raw - the ./ is required)") - println(" The raw input file. two-channel (even with -m), 48kHz 32-bit float.") - println(" -o OUTFILE.raw") - println(" The raw RF stream to output, 2x300kHz 32-bit float.") - println(" -O OUTFILE.raw") - println(" The raw audio stream to output, 2x48kHz 32-bit float.") - println(" -p POWER") - println(" The signal amplitude (power level), e.g. 1.0.") - println(" -l") - println(" Simulate a partial overlap on the lower half of the tuned-into frequency.") - println(" -h") - println(" Simulate a partial overlap on the upper half of the tuned-into frequency.") - println(" -m") - println(" Downconvert to mono. As the last option, demodulate as mono.") -} - -class SimFile(val power: Float, val band: Int, val filename: String, val stereo: Boolean) { - var closed: Boolean = false - val buffer: FloatBuffer = FloatBuffer.allocate(8192) - val modulator = FmFullModulator() - var stream: InputStream? = null -} - -fun main(args: Array<String>) { - if (args.isEmpty()) { - printUsage() - exitProcess(1) - } - var hasOutput = false - var inArg = "" - var output = "" - var rfOutput = true - var power = 1.0f - var band = 2 - var stereo = FmFullConstants.STEREO - val files: ArrayList<SimFile> = ArrayList() - for (arg in args) { - if (!hasOutput) { - if (arg == "-o" || arg == "-O") { - hasOutput = true - inArg = arg - } else { - printUsage() - exitProcess(1) - } - } else { - when (inArg) { - "-o" -> { - output = arg - rfOutput = true - inArg = "" - } - - "-O" -> { - output = arg - rfOutput = false - inArg = "" - } - - "-p" -> { - power = arg.toFloatOrNull() ?: run { - println("Error processing -p argument: not a valid float") - printUsage() - exitProcess(1) - } - inArg = "" - } - - "" -> { - if (!arg.startsWith("-")) { - files.add(SimFile(power, band, arg, stereo)) - inArg = "" - band = 2 - power = 1.0f - stereo = FmFullConstants.STEREO - } else { - when (arg) { - "-p" -> inArg = "-p" - "-l" -> band = 1 - "-h" -> band = 3 - "-m" -> stereo = FmFullConstants.MONO - else -> { - println("Unknown option") - printUsage() - exitProcess(1) - } - } - } - } - - else -> throw NotImplementedError(inArg) - } - } - } - - if (files.isEmpty()) { - printUsage() - exitProcess(1) - } - - println(ProcessHandle.current().pid()) - - FileOutputStream(output).buffered().use { outputStream -> - for (inputFile in files) { - if (inputFile.filename != "file:///dev/zero") { - if (inputFile.filename.startsWith("./")) { - inputFile.stream = FileInputStream(inputFile.filename) - } else { - inputFile.stream = URI(inputFile.filename).toPath().inputStream() - } - } - } - - val buffer = ByteBuffer.allocate(2 * 4 * FmFullConstants.FFT_DATA_BLOCK_SIZE_LPF_48K_15K_3K1) - val plus100k = FloatBuffer.wrap(FmFullConstants.CBUFFER_100K_300K) - val minus100k = FloatBuffer.wrap(FmFullConstants.CBUFFER_100K_300K) - val demodulator = FmFullDemodulator() - var lastStereoPilot = false - while (true) { - // initialized to maximum buffer size, trimmed down later - var minBuffer = 8192 - for (inputFile in files) { - val stream = inputFile.stream - if (stream == null) { - if (inputFile.buffer.remaining() > 2 * FmFullConstants.FFT_DATA_BLOCK_SIZE_48K_300K) { - inputFile.modulator.flush(inputFile.power, inputFile.stereo) { - inputFile.buffer.put(it) - } - } - } else { - val bytes = stream.read(buffer.array()) - if (bytes <= 0) { - stream.close() - inputFile.stream = null - inputFile.closed = true - inputFile.modulator.flush(inputFile.power, inputFile.stereo) { - inputFile.buffer.put(it) - } - } else { - val floats = buffer.slice(0, bytes).order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer() - var shouldFlush = true - inputFile.modulator.process(floats, inputFile.power, inputFile.stereo) { - inputFile.buffer.put(it) - shouldFlush = false - } - if (shouldFlush) { - inputFile.modulator.flush(inputFile.power, inputFile.stereo) { - inputFile.buffer.put(it) - } - } - } - } - minBuffer = min(minBuffer, inputFile.buffer.position()) - } - - val outputBuffer = ByteBuffer.allocate(minBuffer * 4) - val floatView = outputBuffer.order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer() - val floatBufferLo = FloatBuffer.allocate(minBuffer) - val floatBufferHi = FloatBuffer.allocate(minBuffer) - for (inputFile in files) { - inputFile.buffer.flip() - val floatBuffer = when (inputFile.band) { - 1 -> floatBufferLo - 2 -> floatView - 3 -> floatBufferHi - else -> throw IllegalStateException() - } - for (i in 0 until floatBuffer.capacity()) { - floatBuffer.put(i, floatBuffer.get(i) + inputFile.buffer.get()) - } - inputFile.buffer.compact() - } - val z = Vector2f() - val w = Vector2f() - for (i in 0 until floatBufferHi.capacity() step 2) { - z.x = floatBufferHi.get(i) - z.y = floatBufferHi.get(i + 1) - if (!plus100k.hasRemaining()) { - plus100k.clear() - } - w.x = plus100k.get() - w.y = plus100k.get() - z.cmul(w) - floatView.put(i, floatView.get(i) + z.x) - floatView.put(i, floatView.get(i) + z.y) - } - for (i in 0 until floatBufferLo.capacity() step 2) { - z.x = floatBufferLo.get(i) - z.y = floatBufferLo.get(i + 1) - if (!minus100k.hasRemaining()) { - minus100k.clear() - } - w.x = minus100k.get() - w.y = -minus100k.get() - z.cmul(w) - floatView.put(i, floatView.get(i) + z.x) - floatView.put(i, floatView.get(i) + z.y) - } - if (rfOutput) { - outputStream.write(outputBuffer.array()) - } else { - demodulator.process(floatView, stereo) { stereoPilot, it -> - if (stereoPilot != lastStereoPilot) { - println(if (stereoPilot) "stereo" else "mono") - } - lastStereoPilot = stereoPilot - buffer.order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer().put(0, it.array()) - outputStream.write(buffer.array()) - } - } - if (files.all { it.closed }) { - break - } - } - } -} \ No newline at end of file |