diff options
Diffstat (limited to 'src/main/kotlin/space/autistic/radio/fmsim/FmFullModulator.kt')
-rw-r--r-- | src/main/kotlin/space/autistic/radio/fmsim/FmFullModulator.kt | 171 |
1 files changed, 0 insertions, 171 deletions
diff --git a/src/main/kotlin/space/autistic/radio/fmsim/FmFullModulator.kt b/src/main/kotlin/space/autistic/radio/fmsim/FmFullModulator.kt deleted file mode 100644 index 1f3849e..0000000 --- a/src/main/kotlin/space/autistic/radio/fmsim/FmFullModulator.kt +++ /dev/null @@ -1,171 +0,0 @@ -package space.autistic.radio.fmsim - -import org.joml.Vector2f -import space.autistic.radio.complex.cmul -import space.autistic.radio.complex.conjugate -import space.autistic.radio.dsp.Biquad1stOrder -import java.nio.FloatBuffer -import java.util.function.Consumer -import org.jtransforms.fft.FloatFFT_1D -import space.autistic.radio.complex.I -import kotlin.math.max -import kotlin.math.min -import kotlin.math.sqrt - -class FmFullModulator { - private val leftPlusRight = FloatBuffer.allocate(FmFullConstants.FFT_SIZE_LPF_48K_15K_3K1) - private val leftMinusRight = FloatBuffer.allocate(FmFullConstants.FFT_SIZE_LPF_48K_15K_3K1) - private val biquadLeft = Biquad1stOrder( - FmFullConstants.FM_PREEMPAHSIS_B0_48K, - FmFullConstants.FM_PREEMPHASIS_B1_48K, - FmFullConstants.FM_PREEMPHASIS_A1_48K - ) - private val biquadRight = Biquad1stOrder( - FmFullConstants.FM_PREEMPAHSIS_B0_48K, - FmFullConstants.FM_PREEMPHASIS_B1_48K, - FmFullConstants.FM_PREEMPHASIS_A1_48K - ) - private val fft48kBuffer = FloatBuffer.allocate(FmFullConstants.FFT_SIZE_LPF_48K_15K_3K1) - private val mixingBuffer = FloatBuffer.allocate(FmFullConstants.FFT_SIZE_48K_300K) - private val outputBuffer = FloatBuffer.allocate(2 * FmFullConstants.FFT_DATA_BLOCK_SIZE_48K_300K) - private val stereoPilot = FloatBuffer.wrap(FmFullConstants.BUFFER_19K_300K) - - private val cycle19k = Vector2f(0f, 1f) - private var lastSum = 0f - - init { - // pre-pad the buffers - leftPlusRight.position(FmFullConstants.FFT_OVERLAP_LPF_48K_15K_3K1) - leftMinusRight.position(FmFullConstants.FFT_OVERLAP_LPF_48K_15K_3K1) - } - - /** - * Takes in samples at 48kHz, interleaved stereo (even when set to MONO), and processes them for output. - * - * Calls consumer with processed samples at 300kHz in I/Q format. - */ - fun process(input: FloatBuffer, power: Float, stereo: Boolean, consumer: Consumer<FloatBuffer>) { - while (input.remaining() >= 2) { - while (input.remaining() >= 2 && leftPlusRight.hasRemaining()) { - // FIXME AGC (currently clamping/clipping) - val left = min(max(biquadLeft.process(input.get()), -1f), 1f) - val right = min(max(biquadRight.process(input.get()), -1f), 1f) - leftPlusRight.put(left + right) - leftMinusRight.put(left - right) - } - if (!leftPlusRight.hasRemaining()) { - // zero the mixing buffer - for (i in 0 until mixingBuffer.capacity()) { - mixingBuffer.put(i, 0f) - } - fft48kBuffer.put(0, leftPlusRight, 0, FmFullConstants.FFT_SIZE_LPF_48K_15K_3K1) - fft48k.realForward(fft48kBuffer.array()) - fft48kBuffer.array().forEachIndexed { index, fl -> - fft48kBuffer.put( - index, - 0.4f / FmFullConstants.FFT_SIZE_LPF_48K_15K_3K1 * fl - ) - } - val z = Vector2f() - val w = Vector2f() - for (i in 2 until (FmFullConstants.FREQUENCY_MIXING_BINS_38K - 2 and 1.inv()) step 2) { - z.x = fft48kBuffer.get(i) - z.y = fft48kBuffer.get(i + 1) - w.x = fir48kLpf.get(i) - w.y = fir48kLpf.get(i + 1) - z.cmul(w) - mixingBuffer.put(i, z.x) - mixingBuffer.put(i + 1, z.y) - } - mixingBuffer.put(0, fft48kBuffer.get(0) * fir48kLpf.get(0)) - if (stereo) { - fft48kBuffer.put(0, leftMinusRight, 0, FmFullConstants.FFT_SIZE_LPF_48K_15K_3K1) - fft48k.realForward(fft48kBuffer.array()) - fft48kBuffer.array().forEachIndexed { index, fl -> - fft48kBuffer.put( - index, - 0.2f / FmFullConstants.FFT_SIZE_LPF_48K_15K_3K1 * fl - ) - } - val base = FmFullConstants.FREQUENCY_MIXING_BINS_38K * 2 - for (i in 2 until (FmFullConstants.FREQUENCY_MIXING_BINS_38K - 2 and 1.inv()) step 2) { - z.x = fft48kBuffer.get(i) - z.y = fft48kBuffer.get(i + 1) - w.x = fir48kLpf.get(i) - w.y = fir48kLpf.get(i + 1) - z.cmul(w) - mixingBuffer.put(base + i, z.x) - mixingBuffer.put(base + i + 1, z.y) - } - mixingBuffer.put(base, fft48kBuffer.get(0) * fir48kLpf.get(0)) - // cycle (phase offset) is frequency-doubled the 19k carrier - // but we need to add a 90deg rotation because ??? - // TODO check if this is mathematically sound - val cycle = cycle19k.cmul(cycle19k, Vector2f()).cmul(I) - // bandwidth we care about is about half of 38k, so just, well, half it - for (i in 2 until (FmFullConstants.FREQUENCY_MIXING_BINS_38K - 2 and 1.inv()) step 2) { - z.x = mixingBuffer.get(base + i) - z.y = mixingBuffer.get(base + i + 1) - // we also need the conjugate - z.conjugate(w) - z.cmul(cycle) - w.cmul(cycle) - mixingBuffer.put(base + i, z.x) - mixingBuffer.put(base + i + 1, z.y) - mixingBuffer.put(base - i, mixingBuffer.get(base - i) + w.x) - mixingBuffer.put(base - i + 1, mixingBuffer.get(base - i + 1) + w.y) - } - // handle 38kHz itself - z.x = mixingBuffer.get(base) - z.y = mixingBuffer.get(base + 1) - z.cmul(cycle) - mixingBuffer.put(base, z.x) - mixingBuffer.put(base + 1, z.y) - // add pilot - mixingBuffer.put( - FmFullConstants.FREQUENCY_MIXING_BINS_38K, - 75f / FmFullConstants.FFT_SIZE_48K_300K * cycle19k.x - ) - mixingBuffer.put( - FmFullConstants.FREQUENCY_MIXING_BINS_38K + 1, - 75f / FmFullConstants.FFT_SIZE_48K_300K * cycle19k.y - ) - // phase correction factors (due to dropping 225 bins) - cycle19k.cmul(I.conjugate()) - } - // mark data block as processed - leftPlusRight.position(FmFullConstants.FFT_DATA_BLOCK_SIZE_LPF_48K_15K_3K1) - leftMinusRight.position(FmFullConstants.FFT_DATA_BLOCK_SIZE_LPF_48K_15K_3K1) - leftPlusRight.compact() - leftMinusRight.compact() - fft300k.realInverse(mixingBuffer.array(), false) - outputBuffer.clear() - var sum = lastSum - for (i in FmFullConstants.FFT_OVERLAP_48K_300K until FmFullConstants.FFT_SIZE_48K_300K) { - sum += mixingBuffer.get(i) * FmFullConstants.CORRECTION_FACTOR - outputBuffer.put(org.joml.Math.cos(sum) * power) - outputBuffer.put(org.joml.Math.sin(sum) * power) - } - lastSum = sum % (2 * Math.PI).toFloat() - outputBuffer.clear() - consumer.accept(outputBuffer) - } - } - input.compact() - } - - fun flush(power: Float, stereo: Boolean, consumer: Consumer<FloatBuffer>) { - process(FloatBuffer.allocate(2 * leftPlusRight.remaining()), power, stereo, consumer) - } - - companion object { - private val fft48k = FloatFFT_1D(FmFullConstants.FFT_SIZE_LPF_48K_15K_3K1.toLong()) - private val fft300k = FloatFFT_1D(FmFullConstants.FFT_SIZE_48K_300K.toLong()) - private val fir48kLpf = FloatBuffer.allocate(FmFullConstants.FFT_SIZE_LPF_48K_15K_3K1) - - init { - fir48kLpf.put(0, FmFullConstants.FIR_LPF_48K_15K_3K1) - fft48k.realForward(fir48kLpf.array()) - } - } -} \ No newline at end of file |