Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c42e1d7b4e | ||
|
|
09ebf1f39a | ||
|
|
ec40f29f28 | ||
|
|
5dd7a35a23 | ||
|
|
4b835bac5c |
No files matched your search
@@ -11,12 +11,9 @@ val keystoreProperties = Properties().apply {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
// CW 解码 native 库仅 armeabi-v7a(照搬 Morse Expert 1.15): 全 ABI 打包会在
|
// CW 解码已迁移为纯 Kotlin fldigi 引擎, 不再有 native 库;
|
||||||
// arm64 设备 loadLibrary 失败, 强制 32 位兼容(用户设备为 32 位软件)
|
// 取消 armeabi-v7a 强制, 按设备 ABI 打包(含 x86_64 模拟器)
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
ndk {
|
|
||||||
abiFilters += listOf("armeabi-v7a")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
androidResources {
|
androidResources {
|
||||||
// 显式保留全部语言(防 shrinkResources 丢弃 in/id 印尼语配置); AGP 9 用 localeFilters
|
// 显式保留全部语言(防 shrinkResources 丢弃 in/id 印尼语配置); AGP 9 用 localeFilters
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
|
* Copyright (C) 2019-2026 Arty Bishop and contributors.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.rtbishop.look4sat.core.domain.cw
|
||||||
|
|
||||||
|
import kotlin.math.PI
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FFT lowpass/bandpass filter with overlap-add, ported from fldigi
|
||||||
|
* fftfilt.cxx (fftfilt class). Used by the CW decoder for the
|
||||||
|
* matched receive filter.
|
||||||
|
*/
|
||||||
|
internal class CwFftFilt(f1: Double, f2: Double, len: Int) {
|
||||||
|
private val flen: Int
|
||||||
|
private val flen2: Int
|
||||||
|
private val fft: CwGfft
|
||||||
|
private val filter: Array<CwComplex>
|
||||||
|
private val timedata: Array<CwComplex>
|
||||||
|
private val freqdata: Array<CwComplex>
|
||||||
|
private val output: Array<CwComplex>
|
||||||
|
private val ovlbuf: Array<CwComplex>
|
||||||
|
private var inptr = 0
|
||||||
|
private var pass = 0
|
||||||
|
|
||||||
|
init {
|
||||||
|
flen = len
|
||||||
|
flen2 = flen shr 1
|
||||||
|
fft = CwGfft(flen)
|
||||||
|
filter = Array(flen) { CwComplex() }
|
||||||
|
timedata = Array(flen) { CwComplex() }
|
||||||
|
freqdata = Array(flen) { CwComplex() }
|
||||||
|
output = Array(flen) { CwComplex() }
|
||||||
|
ovlbuf = Array(flen2) { CwComplex() }
|
||||||
|
createFilter(f1, f2)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(f: Double, len: Int) : this(0.0, f, len)
|
||||||
|
|
||||||
|
fun flushSize(): Int = flen - inptr
|
||||||
|
|
||||||
|
/**
|
||||||
|
* f1 < f2 ==> bandpass; f1 > f2 ==> band reject; f1 == 0 ==> lowpass; f2 == 0 ==> highpass.
|
||||||
|
*/
|
||||||
|
private fun createFilter(f1: Double, f2: Double) {
|
||||||
|
for (i in 0 until flen) {
|
||||||
|
filter[i] = CwComplex()
|
||||||
|
}
|
||||||
|
val bLowpass = f2 != 0.0
|
||||||
|
val bHighpass = f1 != 0.0
|
||||||
|
|
||||||
|
for (i in 0 until flen2) {
|
||||||
|
var h = 0.0
|
||||||
|
if (bLowpass) h += fsinc(f2, i, flen2)
|
||||||
|
if (bHighpass) h -= fsinc(f1, i, flen2)
|
||||||
|
if (bHighpass && f2 < f1 && i == flen2 / 2) h += 1.0
|
||||||
|
filter[i] = CwComplex(h * blackman(i, flen2), 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// forward FFT of the impulse response
|
||||||
|
fft.forward(filter)
|
||||||
|
|
||||||
|
// normalize unity gain
|
||||||
|
var scale = 0.0
|
||||||
|
for (i in 0 until flen2) {
|
||||||
|
val mag = kotlin.math.sqrt(filter[i].re * filter[i].re + filter[i].im * filter[i].im)
|
||||||
|
if (mag > scale) scale = mag
|
||||||
|
}
|
||||||
|
if (scale != 0.0) {
|
||||||
|
for (i in 0 until flen) {
|
||||||
|
filter[i] = CwComplex(filter[i].re / scale, filter[i].im / scale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pass = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createLpf(f: Double) = createFilter(0.0, f)
|
||||||
|
|
||||||
|
private fun fsinc(fc: Double, i: Int, len: Int): Double {
|
||||||
|
val mid = len / 2
|
||||||
|
return if (i == mid) 2.0 * fc
|
||||||
|
else sin(2.0 * PI * fc * (i - mid)) / (PI * (i - mid))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun blackman(i: Int, len: Int): Double =
|
||||||
|
0.42 - 0.50 * cos(2.0 * PI * i / len) + 0.08 * cos(4.0 * PI * i / len)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feed one complex input sample. Returns output array + count when flen/2
|
||||||
|
* samples are ready, otherwise null.
|
||||||
|
*/
|
||||||
|
fun run(input: CwComplex): Array<CwComplex>? {
|
||||||
|
timedata[inptr++] = input
|
||||||
|
if (inptr < flen2) return null
|
||||||
|
if (pass > 0) pass--
|
||||||
|
|
||||||
|
// copy to freq domain and FFT
|
||||||
|
for (i in 0 until flen) {
|
||||||
|
freqdata[i] = timedata[i]
|
||||||
|
}
|
||||||
|
fft.forward(freqdata)
|
||||||
|
|
||||||
|
// multiply by filter shape
|
||||||
|
for (i in 0 until flen) {
|
||||||
|
val f = filter[i]
|
||||||
|
val t = freqdata[i]
|
||||||
|
freqdata[i] = CwComplex(t.re * f.re - t.im * f.im, t.re * f.im + t.im * f.re)
|
||||||
|
}
|
||||||
|
|
||||||
|
// inverse FFT
|
||||||
|
fft.inverse(freqdata)
|
||||||
|
|
||||||
|
// overlap-add: first half is valid output, save second half
|
||||||
|
for (i in 0 until flen2) {
|
||||||
|
output[i] = CwComplex(ovlbuf[i].re + freqdata[i].re, ovlbuf[i].im + freqdata[i].im)
|
||||||
|
ovlbuf[i] = freqdata[i + flen2]
|
||||||
|
}
|
||||||
|
|
||||||
|
inptr = 0
|
||||||
|
if (pass > 0) return null
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,530 @@
|
|||||||
|
/*
|
||||||
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
|
* Copyright (C) 2019-2026 Arty Bishop and contributors.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.rtbishop.look4sat.core.domain.cw
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.log10
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CW decoder ported from fldigi (w1hkj/fldigi) cw.cxx — full receive
|
||||||
|
* chain: NCO down-conversion, FFT lowpass filter, decimation, AGC,
|
||||||
|
* hysteresis keying detection, element timing state machine, adaptive
|
||||||
|
* speed tracking, SOM codebook matching and morse table lookup.
|
||||||
|
*
|
||||||
|
* Drop-in replacement for the reverse-engineered Morse Expert decoder:
|
||||||
|
* same processBuffer(FloatArray) entry point, same StateFlow outputs.
|
||||||
|
*/
|
||||||
|
class CwFldigiDecoder(
|
||||||
|
val sampleRate: Int = CwFldigiConstants.CW_SAMPLERATE,
|
||||||
|
private val frequency: Double = 600.0,
|
||||||
|
private val useSom: Boolean = true,
|
||||||
|
private val initialWpm: Int = CwFldigiConstants.DEFAULT_SPEED
|
||||||
|
) {
|
||||||
|
private companion object {
|
||||||
|
const val CW_DOT = '.'
|
||||||
|
const val CW_DASH = '-'
|
||||||
|
const val CW_RESET = 0
|
||||||
|
const val CW_KEYDOWN = 1
|
||||||
|
const val CW_KEYUP = 2
|
||||||
|
const val CW_QUERY = 3
|
||||||
|
const val CW_SUCCESS = 0
|
||||||
|
const val CW_ERROR = -1
|
||||||
|
|
||||||
|
// Auto-tune (spectral peak tracking, mirrors the old channelTracker)
|
||||||
|
const val TUNE_FFT_SIZE = 512
|
||||||
|
const val TUNE_MIN_FREQ = 300.0
|
||||||
|
const val TUNE_MAX_FREQ = 1500.0
|
||||||
|
const val TUNE_ENERGY_FRACTION = 0.35
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class CwRxState { IDLE, IN_TONE, AFTER_TONE }
|
||||||
|
|
||||||
|
// Outputs (API-compatible with the old CwDecoder)
|
||||||
|
private val _decodedTextFlow = MutableStateFlow("")
|
||||||
|
val decodedTextFlow: StateFlow<String> = _decodedTextFlow
|
||||||
|
|
||||||
|
private val _signalStrength = MutableStateFlow(0f)
|
||||||
|
val signalStrength: StateFlow<Float> = _signalStrength
|
||||||
|
|
||||||
|
private val _estimatedPitch = MutableStateFlow<Float?>(frequency.toFloat())
|
||||||
|
val estimatedPitch: StateFlow<Float?> = _estimatedPitch
|
||||||
|
|
||||||
|
private val _estimatedSpeed = MutableStateFlow<Float?>(null)
|
||||||
|
val estimatedSpeed: StateFlow<Float?> = _estimatedSpeed
|
||||||
|
|
||||||
|
// --- auto-tune state (spectral peak tracking) ---
|
||||||
|
private var tuneFreq = frequency
|
||||||
|
private val tuneBuffer = DoubleArray(TUNE_FFT_SIZE)
|
||||||
|
private var tuneIdx = 0
|
||||||
|
private var tuneFft = CwGfft(TUNE_FFT_SIZE)
|
||||||
|
private var tunePeakFreq = frequency
|
||||||
|
private var tuneHasPeak = false
|
||||||
|
private var tuneLocked = false
|
||||||
|
private val tuneWindow = DoubleArray(TUNE_FFT_SIZE)
|
||||||
|
|
||||||
|
// --- fldigi cw state ---
|
||||||
|
private var phaseacc = 0.0
|
||||||
|
private var FFTphase = 0.0
|
||||||
|
private var FFTvalue = 0.0
|
||||||
|
private var smpl_ctr = 0
|
||||||
|
|
||||||
|
private var agc_peak = 1.0
|
||||||
|
private var noise_floor = 1.0
|
||||||
|
private var sig_avg = 0.0
|
||||||
|
private var metric = 0.0
|
||||||
|
private var siglevel = 0.0
|
||||||
|
|
||||||
|
private var upper_threshold = CwFldigiConstants.CW_UPPER_THRESHOLD
|
||||||
|
private var lower_threshold = CwFldigiConstants.CW_LOWER_THRESHOLD
|
||||||
|
|
||||||
|
private val cw_FFT_filter: CwFftFilt
|
||||||
|
private val bitfilter: CwMovAvg
|
||||||
|
private val trackingfilter: CwMovAvg
|
||||||
|
|
||||||
|
private var cw_receive_state = CwRxState.IDLE
|
||||||
|
private var old_cw_receive_state = CwRxState.IDLE
|
||||||
|
|
||||||
|
private var cw_rr_start_timestamp = 0
|
||||||
|
private var cw_rr_end_timestamp = 0
|
||||||
|
private var rx_rep_buf = StringBuilder()
|
||||||
|
private var cw_ptr = 0
|
||||||
|
private val cw_buffer = FloatArray(CwFldigiConstants.MAX_MORSE_ELEMENTS + 1)
|
||||||
|
|
||||||
|
private var cw_speed = initialWpm
|
||||||
|
private var cw_send_speed = initialWpm
|
||||||
|
private var cw_receive_speed = initialWpm
|
||||||
|
private var cw_bandwidth = CwFldigiConstants.DEFAULT_BANDWIDTH
|
||||||
|
private var cwTrack = true
|
||||||
|
private var two_dots = 0L
|
||||||
|
private var last_element = 0
|
||||||
|
private var space_sent = true
|
||||||
|
private var cw_noise_spike_threshold = 0L
|
||||||
|
private var cw_receive_dot_length = 0L
|
||||||
|
private var cw_receive_dash_length = 0L
|
||||||
|
|
||||||
|
private var synchscope = 50
|
||||||
|
|
||||||
|
init {
|
||||||
|
val bw = cw_bandwidth.toDouble() / sampleRate
|
||||||
|
cw_FFT_filter = CwFftFilt(bw, CwFldigiConstants.CW_FFT_SIZE)
|
||||||
|
val bfv = (symbollen() / (2 * CwFldigiConstants.DEC_RATIO)).coerceAtLeast(1)
|
||||||
|
bitfilter = CwMovAvg(bfv)
|
||||||
|
trackingfilter = CwMovAvg(CwFldigiConstants.TRACKING_FILTER_SIZE)
|
||||||
|
|
||||||
|
two_dots = (2 * CwFldigiConstants.KWPM / cw_speed).toLong()
|
||||||
|
cw_noise_spike_threshold = two_dots / 4
|
||||||
|
cw_receive_dot_length = (CwFldigiConstants.KWPM / cw_receive_speed).toLong()
|
||||||
|
cw_receive_dash_length = 3 * cw_receive_dot_length
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun symbollen(): Int =
|
||||||
|
(sampleRate * 1.2 / cw_speed).roundToInt()
|
||||||
|
|
||||||
|
private fun usecDiff(earlier: Int, later: Int): Int =
|
||||||
|
if (earlier >= later) 0 else later - earlier
|
||||||
|
|
||||||
|
/** Main entry: feed PCM samples. */
|
||||||
|
fun processBuffer(buffer: FloatArray) {
|
||||||
|
for (sample in buffer) {
|
||||||
|
feedTuner(sample.toDouble())
|
||||||
|
rxSample(sample.toDouble())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spectral peak tracking (auto-tune). Collects raw samples, runs an FFT
|
||||||
|
* every TUNE_FFT_SIZE samples, finds the strongest peak in the CW range
|
||||||
|
* and smoothly steers tuneFreq toward it — mirroring the old
|
||||||
|
* channelTracker behaviour so off-tune signals still decode.
|
||||||
|
*/
|
||||||
|
private fun feedTuner(sample: Double) {
|
||||||
|
tuneBuffer[tuneIdx++] = sample
|
||||||
|
if (tuneIdx < TUNE_FFT_SIZE) return
|
||||||
|
tuneIdx = 0
|
||||||
|
|
||||||
|
// Hann window
|
||||||
|
for (i in 0 until TUNE_FFT_SIZE) {
|
||||||
|
val w = 0.5 - 0.5 * kotlin.math.cos(2.0 * Math.PI * i / (TUNE_FFT_SIZE - 1))
|
||||||
|
tuneWindow[i] = tuneBuffer[i] * w
|
||||||
|
}
|
||||||
|
val data = Array(TUNE_FFT_SIZE) { CwComplex(tuneWindow[it], 0.0) }
|
||||||
|
tuneFft.forward(data)
|
||||||
|
|
||||||
|
// Find the strongest bin in the CW range (300..1500 Hz)
|
||||||
|
val binMin = (TUNE_MIN_FREQ * TUNE_FFT_SIZE / sampleRate).toInt()
|
||||||
|
val binMax = (TUNE_MAX_FREQ * TUNE_FFT_SIZE / sampleRate).toInt()
|
||||||
|
var bestBin = -1
|
||||||
|
var bestMag = 0.0
|
||||||
|
var totalMag = 0.0
|
||||||
|
for (b in binMin..binMax) {
|
||||||
|
val mag = data[b].abs()
|
||||||
|
totalMag += mag
|
||||||
|
if (mag > bestMag) {
|
||||||
|
bestMag = mag
|
||||||
|
bestBin = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bestBin < 0) return
|
||||||
|
val meanMag = totalMag / (binMax - binMin + 1)
|
||||||
|
if (bestMag < meanMag * 2.0) return // no clear tone
|
||||||
|
// Absolute floor: silence or weak noise must not steer the NCO.
|
||||||
|
// A 0.6-amplitude tone in a 512-pt Hann FFT yields peak ≈ 150;
|
||||||
|
// anything below ~30 is noise/DC leakage.
|
||||||
|
if (bestMag < 30.0) return
|
||||||
|
|
||||||
|
val peakFreq = bestBin * sampleRate.toDouble() / TUNE_FFT_SIZE
|
||||||
|
tunePeakFreq = peakFreq
|
||||||
|
tuneHasPeak = true
|
||||||
|
_estimatedPitch.value = peakFreq.toFloat()
|
||||||
|
|
||||||
|
// Lock fast: a strong peak on the very first frame is reliable enough
|
||||||
|
// (CW tones are narrow and dominate the band). Steer immediately so
|
||||||
|
// the first character still decodes. Do NOT reset the RX state on the
|
||||||
|
// first lock — the AGC adapts in a few frames and a reset would wipe
|
||||||
|
// the element that triggered the tune.
|
||||||
|
if (!tuneLocked) {
|
||||||
|
tuneFreq = peakFreq
|
||||||
|
tuneLocked = true
|
||||||
|
_estimatedPitch.value = peakFreq.toFloat()
|
||||||
|
} else {
|
||||||
|
// Smooth tracking; retune instantly on big jumps (signal switched freq)
|
||||||
|
val diff = peakFreq - tuneFreq
|
||||||
|
if (kotlin.math.abs(diff) > 120.0) {
|
||||||
|
tuneFreq = peakFreq
|
||||||
|
resetRxState()
|
||||||
|
} else {
|
||||||
|
tuneFreq += diff * 0.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Reset only the fldigi RX state machine (keep decoded text). */
|
||||||
|
private fun resetRxState() {
|
||||||
|
cw_receive_state = CwRxState.IDLE
|
||||||
|
old_cw_receive_state = CwRxState.IDLE
|
||||||
|
smpl_ctr = 0
|
||||||
|
cw_ptr = 0
|
||||||
|
rx_rep_buf.clear()
|
||||||
|
last_element = 0
|
||||||
|
space_sent = true
|
||||||
|
FFTphase = 0.0
|
||||||
|
phaseacc = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun rxSample(value: Double) {
|
||||||
|
// NCO down-conversion (fldigi rx_FFTprocess). tuneFreq tracks the
|
||||||
|
// strongest spectral peak so off-tune signals still decode.
|
||||||
|
val z = CwComplex(
|
||||||
|
value * kotlin.math.cos(FFTphase),
|
||||||
|
value * kotlin.math.sin(FFTphase)
|
||||||
|
)
|
||||||
|
FFTphase += 2.0 * Math.PI * tuneFreq / sampleRate
|
||||||
|
if (FFTphase > 2.0 * Math.PI) FFTphase -= 2.0 * Math.PI
|
||||||
|
|
||||||
|
val out = cw_FFT_filter.run(z) ?: return
|
||||||
|
for (i in 0 until CwFldigiConstants.CW_FFT_SIZE / 2) {
|
||||||
|
++smpl_ctr
|
||||||
|
if (smpl_ctr % CwFldigiConstants.DEC_RATIO != 0) continue
|
||||||
|
FFTvalue = out[i].abs()
|
||||||
|
FFTvalue = bitfilter.run(FFTvalue)
|
||||||
|
decodeStream(FFTvalue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AGC + hysteresis detection (fldigi cw::decode_stream).
|
||||||
|
*/
|
||||||
|
private fun decodeStream(value: Double) {
|
||||||
|
var v = value
|
||||||
|
val attack: Int
|
||||||
|
val decay: Int
|
||||||
|
when (CwFldigiConstants.CWRX_ATTACK_DEFAULT) {
|
||||||
|
0 -> attack = 400
|
||||||
|
2 -> attack = 100
|
||||||
|
else -> attack = 200
|
||||||
|
}
|
||||||
|
when (CwFldigiConstants.CWRX_DECAY_DEFAULT) {
|
||||||
|
0 -> decay = 2000
|
||||||
|
2 -> decay = 500
|
||||||
|
else -> decay = 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
sig_avg = decayAvg(sig_avg, v, decay)
|
||||||
|
|
||||||
|
if (v < sig_avg) {
|
||||||
|
noise_floor = if (v < noise_floor) decayAvg(noise_floor, v, attack)
|
||||||
|
else decayAvg(noise_floor, v, decay)
|
||||||
|
}
|
||||||
|
if (v > sig_avg) {
|
||||||
|
agc_peak = if (v > agc_peak) decayAvg(agc_peak, v, attack)
|
||||||
|
else decayAvg(agc_peak, v, decay)
|
||||||
|
}
|
||||||
|
|
||||||
|
val normNoise = noise_floor / agc_peak
|
||||||
|
val normSig = sig_avg / agc_peak
|
||||||
|
siglevel = normSig
|
||||||
|
|
||||||
|
if (agc_peak != 0.0) v /= agc_peak else v = 0.0
|
||||||
|
|
||||||
|
metric = 0.8 * metric
|
||||||
|
if ((noise_floor > 1e-4) && (noise_floor < sig_avg)) {
|
||||||
|
val db = 20.0 * log10(sig_avg / noise_floor)
|
||||||
|
metric += 0.2 * clamp(2.5 * db, 0.0, 100.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
val diff = normSig - normNoise
|
||||||
|
upper_threshold = normSig - 0.2 * diff
|
||||||
|
lower_threshold = normNoise + 0.7 * diff
|
||||||
|
|
||||||
|
// Squelch gate (fldigi: !progStatus.sqlonoff || metric > sldrSquelchValue).
|
||||||
|
// Default sqlonoff=true, sldrSquelchValue=5.0. Without this gate, noise
|
||||||
|
// spikes false-trigger KEYDOWN and lock the state machine in IN_TONE.
|
||||||
|
if (sqlonoff && metric <= squelchValue) return
|
||||||
|
|
||||||
|
// Power detection using hysteresis
|
||||||
|
if ((v > upper_threshold) && (cw_receive_state != CwRxState.IN_TONE)) {
|
||||||
|
handleEvent(CW_KEYDOWN)
|
||||||
|
}
|
||||||
|
if ((v < lower_threshold) && (cw_receive_state == CwRxState.IN_TONE)) {
|
||||||
|
handleEvent(CW_KEYUP)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handleEvent(CW_QUERY) == CW_SUCCESS) {
|
||||||
|
synchscope = 100
|
||||||
|
// emit decoded char(s)
|
||||||
|
} else if (--synchscope == 0) {
|
||||||
|
synchscope = 25
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var sqlonoff = true
|
||||||
|
private var squelchValue = 5.0
|
||||||
|
|
||||||
|
private fun decayAvg(avg: Double, value: Double, timeConst: Int): Double =
|
||||||
|
avg + (value - avg) / timeConst
|
||||||
|
|
||||||
|
private fun clamp(v: Double, lo: Double, hi: Double): Double =
|
||||||
|
if (v < lo) lo else if (v > hi) hi else v
|
||||||
|
|
||||||
|
private fun handleEvent(event: Int): Int {
|
||||||
|
val sc = StringBuilder()
|
||||||
|
var elementUsec: Int
|
||||||
|
when (event) {
|
||||||
|
CW_RESET -> {
|
||||||
|
syncParameters()
|
||||||
|
cw_receive_state = CwRxState.IDLE
|
||||||
|
cw_ptr = 0
|
||||||
|
smpl_ctr = 0
|
||||||
|
rx_rep_buf.clear()
|
||||||
|
}
|
||||||
|
CW_KEYDOWN -> {
|
||||||
|
if (cw_receive_state == CwRxState.IN_TONE) return CW_ERROR
|
||||||
|
if (cw_receive_state == CwRxState.IDLE) {
|
||||||
|
smpl_ctr = 0
|
||||||
|
rx_rep_buf.clear()
|
||||||
|
cw_ptr = 0
|
||||||
|
}
|
||||||
|
cw_rr_start_timestamp = smpl_ctr
|
||||||
|
old_cw_receive_state = cw_receive_state
|
||||||
|
cw_receive_state = CwRxState.IN_TONE
|
||||||
|
return CW_ERROR
|
||||||
|
}
|
||||||
|
CW_KEYUP -> {
|
||||||
|
if (cw_receive_state != CwRxState.IN_TONE) return CW_ERROR
|
||||||
|
cw_rr_end_timestamp = smpl_ctr
|
||||||
|
elementUsec = usecDiff(cw_rr_start_timestamp, cw_rr_end_timestamp)
|
||||||
|
syncParameters()
|
||||||
|
if (cw_noise_spike_threshold > 0 && elementUsec < cw_noise_spike_threshold) {
|
||||||
|
cw_receive_state = CwRxState.IDLE
|
||||||
|
return CW_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
// adaptive speed tracking on dot-dash / dash-dot pairs
|
||||||
|
if (last_element > 0) {
|
||||||
|
if ((elementUsec > 2 * last_element) && (elementUsec < 4 * last_element)) {
|
||||||
|
updateTracking(last_element, elementUsec)
|
||||||
|
}
|
||||||
|
if ((last_element > 2 * elementUsec) && (last_element < 4 * elementUsec)) {
|
||||||
|
updateTracking(elementUsec, last_element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last_element = elementUsec
|
||||||
|
|
||||||
|
if (elementUsec <= two_dots) {
|
||||||
|
rx_rep_buf.append(CW_DOT)
|
||||||
|
cw_buffer[cw_ptr++] = last_element.toFloat()
|
||||||
|
} else {
|
||||||
|
rx_rep_buf.append(CW_DASH)
|
||||||
|
cw_buffer[cw_ptr++] = last_element.toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rx_rep_buf.length > CwFldigiConstants.MAX_MORSE_ELEMENTS) {
|
||||||
|
cw_receive_state = CwRxState.IDLE
|
||||||
|
cw_ptr = 0
|
||||||
|
smpl_ctr = 0
|
||||||
|
return CW_ERROR
|
||||||
|
} else {
|
||||||
|
if (cw_ptr < cw_buffer.size) cw_buffer[cw_ptr] = 0.0f
|
||||||
|
}
|
||||||
|
cw_receive_state = CwRxState.AFTER_TONE
|
||||||
|
return CW_ERROR
|
||||||
|
}
|
||||||
|
CW_QUERY -> {
|
||||||
|
if (cw_receive_state == CwRxState.IN_TONE) return CW_ERROR
|
||||||
|
syncParameters()
|
||||||
|
elementUsec = usecDiff(cw_rr_end_timestamp, smpl_ctr)
|
||||||
|
if (elementUsec < (2 * cw_receive_dot_length)) return CW_ERROR
|
||||||
|
|
||||||
|
if (elementUsec >= (2 * cw_receive_dot_length) &&
|
||||||
|
elementUsec <= (4 * cw_receive_dot_length) &&
|
||||||
|
cw_receive_state == CwRxState.AFTER_TONE
|
||||||
|
) {
|
||||||
|
val pattern = rx_rep_buf.toString()
|
||||||
|
val decoded = if (useSom) {
|
||||||
|
findWinner(cw_buffer, two_dots)
|
||||||
|
} else {
|
||||||
|
MorseTable.rxLookup(pattern)
|
||||||
|
}
|
||||||
|
if (decoded.isNotEmpty()) {
|
||||||
|
appendDecoded(decoded)
|
||||||
|
}
|
||||||
|
rx_rep_buf.clear()
|
||||||
|
cw_receive_state = CwRxState.IDLE
|
||||||
|
space_sent = false
|
||||||
|
cw_ptr = 0
|
||||||
|
return CW_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((elementUsec > (4 * cw_receive_dot_length)) && !space_sent) {
|
||||||
|
appendDecoded(" ")
|
||||||
|
space_sent = true
|
||||||
|
return CW_SUCCESS
|
||||||
|
}
|
||||||
|
return CW_ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return CW_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun appendDecoded(text: String) {
|
||||||
|
val current = _decodedTextFlow.value
|
||||||
|
_decodedTextFlow.value = if (current.length > 2000) {
|
||||||
|
current.drop(current.length - 1500) + text
|
||||||
|
} else {
|
||||||
|
current + text
|
||||||
|
}
|
||||||
|
_signalStrength.value = metric.toFloat() / 100f
|
||||||
|
_estimatedSpeed.value = (CwFldigiConstants.KWPM.toDouble() / (two_dots / 2.0)).toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun syncParameters() {
|
||||||
|
if (cwTrack) {
|
||||||
|
cw_receive_speed = (CwFldigiConstants.KWPM / (two_dots / 2)).toInt()
|
||||||
|
} else {
|
||||||
|
cw_receive_speed = cw_send_speed
|
||||||
|
two_dots = 2 * (CwFldigiConstants.KWPM / cw_send_speed).toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cw_receive_speed > 0) {
|
||||||
|
cw_receive_dot_length = (CwFldigiConstants.KWPM / cw_receive_speed).toLong()
|
||||||
|
} else {
|
||||||
|
cw_receive_dot_length = (CwFldigiConstants.KWPM / 5).toLong()
|
||||||
|
}
|
||||||
|
cw_receive_dash_length = 3 * cw_receive_dot_length
|
||||||
|
cw_noise_spike_threshold = cw_receive_dot_length / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateTracking(dur1: Int, dur2: Int) {
|
||||||
|
val minDot = CwFldigiConstants.KWPM / 200
|
||||||
|
val maxDash = 3 * CwFldigiConstants.KWPM / 5
|
||||||
|
if ((dur1 > dur2) && (dur1 > 4 * dur2)) return
|
||||||
|
if ((dur2 > dur1) && (dur2 > 4 * dur1)) return
|
||||||
|
if (dur1 < minDot || dur2 < minDot) return
|
||||||
|
if (dur2 > maxDash || dur2 > maxDash) return
|
||||||
|
|
||||||
|
two_dots = trackingfilter.run(((dur1 + dur2) / 2).toDouble()).toLong()
|
||||||
|
syncParameters()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SOM codebook matching (fldigi cw::find_winner + normalize).
|
||||||
|
*/
|
||||||
|
private fun findWinner(inbuf: FloatArray, twodots: Long): String {
|
||||||
|
if (normalize(inbuf, twodots) == 0) return " "
|
||||||
|
|
||||||
|
var winner = -1
|
||||||
|
var bestDiff = Double.MAX_VALUE
|
||||||
|
for ((idx, entry) in SomTable.table.withIndex()) {
|
||||||
|
var difference = 0.0
|
||||||
|
for (i in 0 until CwFldigiConstants.WGT_SIZE) {
|
||||||
|
val diff = inbuf[i] - entry.weights[i]
|
||||||
|
difference += diff * diff
|
||||||
|
if (difference > bestDiff) break
|
||||||
|
}
|
||||||
|
if (difference < bestDiff) {
|
||||||
|
winner = idx
|
||||||
|
bestDiff = difference
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (winner >= 0 && SomTable.table[winner].pattern.isNotEmpty()) {
|
||||||
|
val sc = MorseTable.rxLookup(SomTable.table[winner].pattern)
|
||||||
|
return if (sc.isNotEmpty()) sc else CwFldigiConstants.DEFAULT_NOISE_CHAR.toString()
|
||||||
|
}
|
||||||
|
return CwFldigiConstants.DEFAULT_NOISE_CHAR.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun normalize(v: FloatArray, twodots: Long): Int {
|
||||||
|
var max = v[0]
|
||||||
|
var min = v[0]
|
||||||
|
for (j in 1 until CwFldigiConstants.WGT_SIZE) {
|
||||||
|
if (v[j] > max) max = v[j]
|
||||||
|
else if (v[j] < min) min = v[j]
|
||||||
|
}
|
||||||
|
if (max == 0.0f) return 0
|
||||||
|
|
||||||
|
val ratio = if (max > twodots) 1.0f else 0.33f
|
||||||
|
val scale = ratio / max
|
||||||
|
for (j in 0 until CwFldigiConstants.WGT_SIZE) v[j] *= scale
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Reset the decoder state (fldigi cw::rx_init + reset). */
|
||||||
|
fun resetDecoder() {
|
||||||
|
cw_receive_state = CwRxState.IDLE
|
||||||
|
smpl_ctr = 0
|
||||||
|
cw_ptr = 0
|
||||||
|
rx_rep_buf.clear()
|
||||||
|
last_element = 0
|
||||||
|
space_sent = true
|
||||||
|
_decodedTextFlow.value = ""
|
||||||
|
_signalStrength.value = 0f
|
||||||
|
_estimatedSpeed.value = null
|
||||||
|
FFTphase = 0.0
|
||||||
|
phaseacc = 0.0
|
||||||
|
// reset auto-tune
|
||||||
|
tuneIdx = 0
|
||||||
|
tunePeakFreq = frequency
|
||||||
|
tuneHasPeak = false
|
||||||
|
tuneLocked = false
|
||||||
|
tuneFreq = frequency
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
/*
|
||||||
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
|
* Copyright (C) 2019-2026 Arty Bishop and contributors.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.rtbishop.look4sat.core.domain.cw
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constants and primitives ported from fldigi (w1hkj/fldigi),
|
||||||
|
* GPL v3, cw.h / cw.cxx / filters.cxx / fftfilt.cxx.
|
||||||
|
*
|
||||||
|
* Faithful port: identifiers and formulas mirror the C++ original so
|
||||||
|
* behaviour matches the desktop decoder bit-for-bit.
|
||||||
|
*/
|
||||||
|
internal object CwFldigiConstants {
|
||||||
|
const val CW_SAMPLERATE = 8000
|
||||||
|
const val DEC_RATIO = 16
|
||||||
|
const val CW_FFT_SIZE = 2048 // must be power of 2
|
||||||
|
const val MAX_MORSE_ELEMENTS = 6
|
||||||
|
const val WGT_SIZE = 7
|
||||||
|
const val TRACKING_FILTER_SIZE = 16
|
||||||
|
const val MAX_PIPE_SIZE = (22 * CW_SAMPLERATE * 12 / 800)
|
||||||
|
const val CLRCOUNT = 16
|
||||||
|
const val CW_MAX_SPEED = 50
|
||||||
|
const val INITIAL_SEND_SPEED = 18
|
||||||
|
const val INITIAL_RECEIVE_SPEED = 18
|
||||||
|
|
||||||
|
/** # samples in a dot: KWPM / WPM. fldigi: KWPM = 12 * samplerate / 10 */
|
||||||
|
const val KWPM = 12 * CW_SAMPLERATE / 10
|
||||||
|
|
||||||
|
const val CW_SUCCESS = 0
|
||||||
|
const val CW_ERROR = -1
|
||||||
|
|
||||||
|
// Defaults mirroring fldigi progdefaults
|
||||||
|
const val DEFAULT_SPEED = 18 // WPM
|
||||||
|
const val DEFAULT_BANDWIDTH = 150 // Hz
|
||||||
|
const val DEFAULT_RANGE = 10 // WPM tracking range
|
||||||
|
const val LOWER_LIMIT = 5 // WPM
|
||||||
|
const val UPPER_LIMIT = 50 // WPM
|
||||||
|
const val DEFAULT_NOISE_CHAR = '*'
|
||||||
|
const val CWRX_ATTACK_DEFAULT = 1 // medium
|
||||||
|
const val CWRX_DECAY_DEFAULT = 1 // medium
|
||||||
|
const val CW_LOWER_THRESHOLD = 0.4
|
||||||
|
const val CW_UPPER_THRESHOLD = 0.6
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moving average filter, ported from fldigi Cmovavg (filters.cxx).
|
||||||
|
*/
|
||||||
|
internal class CwMovAvg(private var len: Int) {
|
||||||
|
private val buf = DoubleArray(MAX_MOVAVG)
|
||||||
|
private var out = 0.0
|
||||||
|
private var pint = 0
|
||||||
|
private var empty = true
|
||||||
|
|
||||||
|
fun run(a: Double): Double {
|
||||||
|
if (empty) {
|
||||||
|
empty = false
|
||||||
|
out = 0.0
|
||||||
|
for (i in 0 until len) {
|
||||||
|
buf[i] = a
|
||||||
|
out += a
|
||||||
|
}
|
||||||
|
pint = 0
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
out = out - buf[pint] + a
|
||||||
|
buf[pint] = a
|
||||||
|
if (++pint >= len) pint = 0
|
||||||
|
return out / len
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setLength(newLen: Int) {
|
||||||
|
len = newLen.coerceAtMost(MAX_MOVAVG).coerceAtLeast(1)
|
||||||
|
empty = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reset() {
|
||||||
|
empty = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun value(): Double = if (len > 0) out / len else 0.0
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val MAX_MOVAVG = 2048
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complex sample used by the FFT filter chain.
|
||||||
|
*/
|
||||||
|
internal data class CwComplex(var re: Double = 0.0, var im: Double = 0.0) {
|
||||||
|
operator fun timesAssign(other: CwComplex) {
|
||||||
|
val r = re * other.re - im * other.im
|
||||||
|
val i = re * other.im + im * other.re
|
||||||
|
re = r; im = i
|
||||||
|
}
|
||||||
|
|
||||||
|
fun abs(): Double = kotlin.math.sqrt(re * re + im * im)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In-place radix-2 complex FFT, ported from fldigi gfft.h semantics
|
||||||
|
* (ComplexFFT / InverseComplexFFT, forward = -1).
|
||||||
|
*/
|
||||||
|
internal class CwGfft(private val n: Int) {
|
||||||
|
init {
|
||||||
|
require(n > 0 && n and (n - 1) == 0) { "FFT size must be power of 2, got $n" }
|
||||||
|
}
|
||||||
|
|
||||||
|
private val rev = IntArray(n)
|
||||||
|
|
||||||
|
init {
|
||||||
|
val log2n = Integer.numberOfTrailingZeros(n)
|
||||||
|
for (i in 0 until n) {
|
||||||
|
var r = 0
|
||||||
|
var x = i
|
||||||
|
for (bit in 0 until log2n) {
|
||||||
|
r = (r shl 1) or (x and 1)
|
||||||
|
x = x shr 1
|
||||||
|
}
|
||||||
|
rev[i] = r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In-place complex FFT.
|
||||||
|
* @param data array of CwComplex, size n
|
||||||
|
* @param inverse if true, performs inverse transform (no 1/n scaling; caller divides)
|
||||||
|
*/
|
||||||
|
fun transform(data: Array<CwComplex>, inverse: Boolean) {
|
||||||
|
val sign = if (inverse) 1 else -1
|
||||||
|
|
||||||
|
// bit reversal permutation
|
||||||
|
for (i in 0 until n) {
|
||||||
|
val j = rev[i]
|
||||||
|
if (j > i) {
|
||||||
|
val t = data[i]; data[i] = data[j]; data[j] = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var len = 2
|
||||||
|
while (len <= n) {
|
||||||
|
val ang = sign * 2.0 * Math.PI / len
|
||||||
|
val wRe = Math.cos(ang)
|
||||||
|
val wIm = Math.sin(ang)
|
||||||
|
var i = 0
|
||||||
|
while (i < n) {
|
||||||
|
var curRe = 1.0
|
||||||
|
var curIm = 0.0
|
||||||
|
for (j in 0 until len / 2) {
|
||||||
|
val u = data[i + j]
|
||||||
|
val v = data[i + j + len / 2]
|
||||||
|
val tRe = curRe * v.re - curIm * v.im
|
||||||
|
val tIm = curRe * v.im + curIm * v.re
|
||||||
|
data[i + j] = CwComplex(u.re + tRe, u.im + tIm)
|
||||||
|
data[i + j + len / 2] = CwComplex(u.re - tRe, u.im - tIm)
|
||||||
|
val nRe = curRe * wRe - curIm * wIm
|
||||||
|
val nIm = curRe * wIm + curIm * wRe
|
||||||
|
curRe = nRe; curIm = nIm
|
||||||
|
}
|
||||||
|
i += len
|
||||||
|
}
|
||||||
|
len = len shl 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun forward(data: Array<CwComplex>) = transform(data, false)
|
||||||
|
|
||||||
|
fun inverse(data: Array<CwComplex>) = transform(data, true)
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
/*
|
||||||
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
|
* Copyright (C) 2019-2026 Arty Bishop and contributors.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.rtbishop.look4sat.core.domain.cw
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Morse code lookup table, ported from fldigi morse.cxx (cMorse::cw_table).
|
||||||
|
* Each entry: [enabled, displayChar, prosignDisplay, dotDashPattern].
|
||||||
|
*/
|
||||||
|
internal object MorseTable {
|
||||||
|
|
||||||
|
private data class Entry(
|
||||||
|
val enabled: Boolean,
|
||||||
|
val chr: String,
|
||||||
|
val prt: String,
|
||||||
|
val rpr: String
|
||||||
|
)
|
||||||
|
|
||||||
|
private val table = listOf(
|
||||||
|
// Prosigns
|
||||||
|
Entry(true, "=", "<BT>", "-...-"),
|
||||||
|
Entry(false, "~", "<AA>", ".-.-"),
|
||||||
|
Entry(true, "<", "<AS>", ".-..."),
|
||||||
|
Entry(true, ">", "<AR>", ".-.-."),
|
||||||
|
Entry(true, "%", "<SK>", "...-.-"),
|
||||||
|
Entry(true, "+", "<KN>", "-.--."),
|
||||||
|
Entry(true, "&", "<INT>", "..-.-"),
|
||||||
|
Entry(true, "{", "<HM>", "....--"),
|
||||||
|
Entry(true, "}", "<VE>", "...-."),
|
||||||
|
// ASCII 7-bit letters
|
||||||
|
Entry(true, "A", "A", ".-"),
|
||||||
|
Entry(true, "B", "B", "-..."),
|
||||||
|
Entry(true, "C", "C", "-.-."),
|
||||||
|
Entry(true, "D", "D", "-.."),
|
||||||
|
Entry(true, "E", "E", "."),
|
||||||
|
Entry(true, "F", "F", "..-."),
|
||||||
|
Entry(true, "G", "G", "--."),
|
||||||
|
Entry(true, "H", "H", "...."),
|
||||||
|
Entry(true, "I", "I", ".."),
|
||||||
|
Entry(true, "J", "J", ".---"),
|
||||||
|
Entry(true, "K", "K", "-.-"),
|
||||||
|
Entry(true, "L", "L", ".-.."),
|
||||||
|
Entry(true, "M", "M", "--"),
|
||||||
|
Entry(true, "N", "N", "-."),
|
||||||
|
Entry(true, "O", "O", "---"),
|
||||||
|
Entry(true, "P", "P", ".--."),
|
||||||
|
Entry(true, "Q", "Q", "--.-"),
|
||||||
|
Entry(true, "R", "R", ".-."),
|
||||||
|
Entry(true, "S", "S", "..."),
|
||||||
|
Entry(true, "T", "T", "-"),
|
||||||
|
Entry(true, "U", "U", "..-"),
|
||||||
|
Entry(true, "V", "V", "...-"),
|
||||||
|
Entry(true, "W", "W", ".--"),
|
||||||
|
Entry(true, "X", "X", "-..-"),
|
||||||
|
Entry(true, "Y", "Y", "-.--"),
|
||||||
|
Entry(true, "Z", "Z", "--.."),
|
||||||
|
// lowercase map to uppercase
|
||||||
|
Entry(true, "a", "A", ".-"), Entry(true, "b", "B", "-..."),
|
||||||
|
Entry(true, "c", "C", "-.-."), Entry(true, "d", "D", "-.."),
|
||||||
|
Entry(true, "e", "E", "."), Entry(true, "f", "F", "..-."),
|
||||||
|
Entry(true, "g", "G", "--."), Entry(true, "h", "H", "...."),
|
||||||
|
Entry(true, "i", "I", ".."), Entry(true, "j", "J", ".---"),
|
||||||
|
Entry(true, "k", "K", "-.-"), Entry(true, "l", "L", ".-.."),
|
||||||
|
Entry(true, "m", "M", "--"), Entry(true, "n", "N", "-."),
|
||||||
|
Entry(true, "o", "O", "---"), Entry(true, "p", "P", ".--."),
|
||||||
|
Entry(true, "q", "Q", "--.-"), Entry(true, "r", "R", ".-."),
|
||||||
|
Entry(true, "s", "S", "..."), Entry(true, "t", "T", "-"),
|
||||||
|
Entry(true, "u", "U", "..-"), Entry(true, "v", "V", "...-"),
|
||||||
|
Entry(true, "w", "W", ".--"), Entry(true, "x", "X", "-..-"),
|
||||||
|
Entry(true, "y", "Y", "-.--"), Entry(true, "z", "Z", "--.."),
|
||||||
|
// Numerals
|
||||||
|
Entry(true, "0", "0", "-----"),
|
||||||
|
Entry(true, "1", "1", ".----"),
|
||||||
|
Entry(true, "2", "2", "..---"),
|
||||||
|
Entry(true, "3", "3", "...--"),
|
||||||
|
Entry(true, "4", "4", "....-"),
|
||||||
|
Entry(true, "5", "5", "....."),
|
||||||
|
Entry(true, "6", "6", "-...."),
|
||||||
|
Entry(true, "7", "7", "--..."),
|
||||||
|
Entry(true, "8", "8", "---.."),
|
||||||
|
Entry(true, "9", "9", "----."),
|
||||||
|
// Punctuation
|
||||||
|
Entry(true, "\\", "\\", ".-..-."),
|
||||||
|
Entry(true, "'", "'", ".----."),
|
||||||
|
Entry(true, "$", "$", "...-..-"),
|
||||||
|
Entry(true, "(", "(", "-.--."),
|
||||||
|
Entry(true, ")", ")", "-.--.-"),
|
||||||
|
Entry(true, ",", ",", "--..--"),
|
||||||
|
Entry(true, "-", "-", "-....-"),
|
||||||
|
Entry(true, ".", ".", ".-.-.-"),
|
||||||
|
Entry(true, "/", "/", "-..-."),
|
||||||
|
Entry(true, ":", ":", "---..."),
|
||||||
|
Entry(true, ";", ";", "-.-.-."),
|
||||||
|
Entry(true, "?", "?", "..--.."),
|
||||||
|
Entry(true, "_", "_", "..--.-"),
|
||||||
|
Entry(true, "@", "@", ".--.-."),
|
||||||
|
Entry(true, "!", "!", "-.-.--"),
|
||||||
|
// Accented (disabled by default in fldigi)
|
||||||
|
Entry(false, "Ä", "Ä", ".-.-"), Entry(false, "ä", "Ä", ".-.-"),
|
||||||
|
Entry(false, "Æ", "Æ", ".-.-"), Entry(false, "æ", "Æ", ".-.-"),
|
||||||
|
Entry(false, "Å", "Å", ".--.-"), Entry(false, "å", "Å", ".--.-"),
|
||||||
|
Entry(false, "Ç", "Ç", "-.-.."), Entry(false, "ç", "Ç", "-.-.."),
|
||||||
|
Entry(false, "È", "È", ".-..-"), Entry(false, "è", "È", ".-..-"),
|
||||||
|
Entry(false, "É", "É", "..-.."), Entry(false, "é", "É", "..-.."),
|
||||||
|
Entry(false, "Ó", "Ó", "---."), Entry(false, "ó", "Ó", "---."),
|
||||||
|
Entry(false, "Ö", "Ö", "---."), Entry(false, "ö", "Ö", "---."),
|
||||||
|
Entry(false, "Ø", "Ø", "---."), Entry(false, "ø", "Ø", "---."),
|
||||||
|
Entry(false, "Ñ", "Ñ", "--.--"), Entry(false, "ñ", "Ñ", "--.--"),
|
||||||
|
Entry(false, "Ü", "Ü", "..--"), Entry(false, "ü", "Ü", "..--"),
|
||||||
|
Entry(false, "Û", "Û", "..--"), Entry(false, "û", "Û", "..--")
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up the display representation of a dot-dash pattern.
|
||||||
|
* Returns "" when the pattern is not in the table or is disabled.
|
||||||
|
* Mirror of cMorse::rx_lookup.
|
||||||
|
*/
|
||||||
|
fun rxLookup(pattern: String): String {
|
||||||
|
for (e in table) {
|
||||||
|
if (pattern == e.rpr) {
|
||||||
|
if (e.enabled) return e.prt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
|
* Copyright (C) 2019-2026 Arty Bishop and contributors.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.rtbishop.look4sat.core.domain.cw
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Self-Organizing Map codebook for CW character matching, ported from
|
||||||
|
* fldigi cw.cxx (cw::som_table). Each row is a dot/dash weight vector
|
||||||
|
* (0 = element absent, 0.33 = dot, 1.0 = dash).
|
||||||
|
*/
|
||||||
|
internal object SomTable {
|
||||||
|
|
||||||
|
data class SomEntry(val pattern: String, val weights: FloatArray)
|
||||||
|
|
||||||
|
val table: List<SomEntry> = listOf(
|
||||||
|
// Prosigns
|
||||||
|
SomEntry("-...-", floatArrayOf(1.0f, 0.33f, 0.33f, 0.33f, 1.0f, 0f, 0f)),
|
||||||
|
SomEntry(".-.-", floatArrayOf(0.33f, 1.0f, 0.33f, 1.0f, 0f, 0f, 0f)),
|
||||||
|
SomEntry(".-...", floatArrayOf(0.33f, 1.0f, 0.33f, 0.33f, 0.33f, 0f, 0f)),
|
||||||
|
SomEntry(".-.-.", floatArrayOf(0.33f, 1.0f, 0.33f, 1.0f, 0.33f, 0f, 0f)),
|
||||||
|
SomEntry("...-.-", floatArrayOf(0.33f, 0.33f, 0.33f, 1.0f, 0.33f, 1.0f, 0f)),
|
||||||
|
SomEntry("-.--.", floatArrayOf(1.0f, 0.33f, 1.0f, 1.0f, 0.33f, 0f, 0f)),
|
||||||
|
SomEntry("..-.-", floatArrayOf(0.33f, 0.33f, 1.0f, 0.33f, 1.0f, 0f, 0f)),
|
||||||
|
SomEntry("....--", floatArrayOf(0.33f, 0.33f, 0.33f, 0.33f, 1.0f, 1.0f, 0f)),
|
||||||
|
SomEntry("...-.", floatArrayOf(0.33f, 0.33f, 0.33f, 1.0f, 0.33f, 0f, 0f)),
|
||||||
|
// ASCII 7-bit letters
|
||||||
|
SomEntry(".-", floatArrayOf(0.33f, 1.0f, 0f, 0f, 0f, 0f, 0f)),
|
||||||
|
SomEntry("-...", floatArrayOf(1.0f, 0.33f, 0.33f, 0.33f, 0f, 0f, 0f)),
|
||||||
|
SomEntry("-.-.", floatArrayOf(1.0f, 0.33f, 1.0f, 0.33f, 0f, 0f, 0f)),
|
||||||
|
SomEntry("-..", floatArrayOf(1.0f, 0.33f, 0.33f, 0f, 0f, 0f, 0f)),
|
||||||
|
SomEntry(".", floatArrayOf(0.33f, 0f, 0f, 0f, 0f, 0f, 0f)),
|
||||||
|
SomEntry("..-.", floatArrayOf(0.33f, 0.33f, 1.0f, 0.33f, 0f, 0f, 0f)),
|
||||||
|
SomEntry("--.", floatArrayOf(1.0f, 1.0f, 0.33f, 0f, 0f, 0f, 0f)),
|
||||||
|
SomEntry("....", floatArrayOf(0.33f, 0.33f, 0.33f, 0.33f, 0f, 0f, 0f)),
|
||||||
|
SomEntry("..", floatArrayOf(0.33f, 0.33f, 0f, 0f, 0f, 0f, 0f)),
|
||||||
|
SomEntry(".---", floatArrayOf(0.33f, 1.0f, 1.0f, 1.0f, 0f, 0f, 0f)),
|
||||||
|
SomEntry("-.-", floatArrayOf(1.0f, 0.33f, 1.0f, 0f, 0f, 0f, 0f)),
|
||||||
|
SomEntry(".-..", floatArrayOf(0.33f, 1.0f, 0.33f, 0.33f, 0f, 0f, 0f)),
|
||||||
|
SomEntry("--", floatArrayOf(1.0f, 1.0f, 0f, 0f, 0f, 0f, 0f)),
|
||||||
|
SomEntry("-.", floatArrayOf(1.0f, 0.33f, 0f, 0f, 0f, 0f, 0f)),
|
||||||
|
SomEntry("---", floatArrayOf(1.0f, 1.0f, 1.0f, 0f, 0f, 0f, 0f)),
|
||||||
|
SomEntry(".--.", floatArrayOf(0.33f, 1.0f, 1.0f, 0.33f, 0f, 0f, 0f)),
|
||||||
|
SomEntry("--.-", floatArrayOf(1.0f, 1.0f, 0.33f, 1.0f, 0f, 0f, 0f)),
|
||||||
|
SomEntry(".-.", floatArrayOf(0.33f, 1.0f, 0.33f, 0f, 0f, 0f, 0f)),
|
||||||
|
SomEntry("...", floatArrayOf(0.33f, 0.33f, 0.33f, 0f, 0f, 0f, 0f)),
|
||||||
|
SomEntry("-", floatArrayOf(1.0f, 0f, 0f, 0f, 0f, 0f, 0f)),
|
||||||
|
SomEntry("..-", floatArrayOf(0.33f, 0.33f, 1.0f, 0f, 0f, 0f, 0f)),
|
||||||
|
SomEntry("...-", floatArrayOf(0.33f, 0.33f, 0.33f, 1.0f, 0f, 0f, 0f)),
|
||||||
|
SomEntry(".--", floatArrayOf(0.33f, 1.0f, 1.0f, 0f, 0f, 0f, 0f)),
|
||||||
|
SomEntry("-..-", floatArrayOf(1.0f, 0.33f, 0.33f, 1.0f, 0f, 0f, 0f)),
|
||||||
|
SomEntry("-.--", floatArrayOf(1.0f, 0.33f, 1.0f, 1.0f, 0f, 0f, 0f)),
|
||||||
|
SomEntry("--..", floatArrayOf(1.0f, 1.0f, 0.33f, 0.33f, 0f, 0f, 0f)),
|
||||||
|
// Numerals
|
||||||
|
SomEntry("-----", floatArrayOf(1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0f, 0f)),
|
||||||
|
SomEntry(".----", floatArrayOf(0.33f, 1.0f, 1.0f, 1.0f, 1.0f, 0f, 0f)),
|
||||||
|
SomEntry("..---", floatArrayOf(0.33f, 0.33f, 1.0f, 1.0f, 1.0f, 0f, 0f)),
|
||||||
|
SomEntry("...--", floatArrayOf(0.33f, 0.33f, 0.33f, 1.0f, 1.0f, 0f, 0f)),
|
||||||
|
SomEntry("....-", floatArrayOf(0.33f, 0.33f, 0.33f, 0.33f, 1.0f, 0f, 0f)),
|
||||||
|
SomEntry(".....", floatArrayOf(0.33f, 0.33f, 0.33f, 0.33f, 0.33f, 0f, 0f)),
|
||||||
|
SomEntry("-....", floatArrayOf(1.0f, 0.33f, 0.33f, 0.33f, 0.33f, 0f, 0f)),
|
||||||
|
SomEntry("--...", floatArrayOf(1.0f, 1.0f, 0.33f, 0.33f, 0.33f, 0f, 0f)),
|
||||||
|
SomEntry("---..", floatArrayOf(1.0f, 1.0f, 1.0f, 0.33f, 0.33f, 0f, 0f)),
|
||||||
|
SomEntry("----.", floatArrayOf(1.0f, 1.0f, 1.0f, 1.0f, 0.33f, 0f, 0f)),
|
||||||
|
// Punctuation
|
||||||
|
SomEntry(".-..-.", floatArrayOf(0.33f, 1.0f, 0.33f, 0.33f, 1.0f, 0.33f, 0f)),
|
||||||
|
SomEntry(".----.", floatArrayOf(0.33f, 1.0f, 1.0f, 1.0f, 1.0f, 0.33f, 0f)),
|
||||||
|
SomEntry("...-..-", floatArrayOf(0.33f, 0.33f, 0.33f, 1.0f, 0.33f, 0.33f, 1.0f)),
|
||||||
|
SomEntry("-.---.", floatArrayOf(1.0f, 0.33f, 1.0f, 1.0f, 0.33f, 0f, 0f)),
|
||||||
|
SomEntry("-.--.-", floatArrayOf(1.0f, 0.33f, 1.0f, 1.0f, 0.33f, 1.0f, 0f)),
|
||||||
|
SomEntry("--..--", floatArrayOf(1.0f, 1.0f, 0.33f, 0.33f, 1.0f, 1.0f, 0f)),
|
||||||
|
SomEntry("-....-", floatArrayOf(1.0f, 0.33f, 0.33f, 0.33f, 0.33f, 1.0f, 0f)),
|
||||||
|
SomEntry(".-.-.-", floatArrayOf(0.33f, 1.0f, 0.33f, 1.0f, 0.33f, 1.0f, 0f)),
|
||||||
|
SomEntry("-..-.", floatArrayOf(1.0f, 0.33f, 0.33f, 1.0f, 0.33f, 0f, 0f)),
|
||||||
|
SomEntry("---...", floatArrayOf(1.0f, 1.0f, 1.0f, 0.33f, 0.33f, 0.33f, 0f)),
|
||||||
|
SomEntry("-.-.-.", floatArrayOf(1.0f, 0.33f, 1.0f, 0.33f, 1.0f, 0.33f, 0f)),
|
||||||
|
SomEntry("..--..", floatArrayOf(0.33f, 0.33f, 1.0f, 1.0f, 0.33f, 0.33f, 0f)),
|
||||||
|
SomEntry("..--.-", floatArrayOf(0.33f, 0.33f, 1.0f, 1.0f, 0.33f, 1.0f, 0f)),
|
||||||
|
SomEntry(".--.-.", floatArrayOf(0.33f, 1.0f, 1.0f, 0.33f, 1.0f, 0.33f, 0f)),
|
||||||
|
SomEntry("-.-.--", floatArrayOf(1.0f, 0.33f, 1.0f, 0.33f, 1.0f, 1.0f, 0f)),
|
||||||
|
// Accented
|
||||||
|
SomEntry(".-.-", floatArrayOf(0.33f, 1.0f, 0.33f, 1.0f, 0f, 0f, 0f)), // A umlaut, A aelig
|
||||||
|
SomEntry(".--.-", floatArrayOf(0.33f, 1.0f, 1.0f, 0.33f, 1.0f, 0f, 0f)), // A ring
|
||||||
|
SomEntry("-.-..", floatArrayOf(1.0f, 0.33f, 1.0f, 0.33f, 0.33f, 0f, 0f)), // C cedilla
|
||||||
|
SomEntry(".-..-", floatArrayOf(0.33f, 1.0f, 0.33f, 0.33f, 1.0f, 0f, 0f)), // E grave
|
||||||
|
SomEntry("..-..", floatArrayOf(0.33f, 0.33f, 1.0f, 0.33f, 0.33f, 0f, 0f)), // E acute
|
||||||
|
SomEntry("---.", floatArrayOf(1.0f, 1.0f, 1.0f, 0.33f, 0f, 0f, 0f)), // O acute, O umlaut, O slash
|
||||||
|
SomEntry("--.--", floatArrayOf(1.0f, 1.0f, 0.33f, 1.0f, 1.0f, 0f, 0f)), // N tilde
|
||||||
|
SomEntry("..--", floatArrayOf(0.33f, 0.33f, 1.0f, 1.0f, 0f, 0f, 0f)) // U umlaut, U circ
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -106,6 +106,51 @@ object WaveLogApi {
|
|||||||
WavelogResult.Success("")
|
WavelogResult.Success("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ADIF band code from a frequency in Hz. "SAT" is NOT a legal ADIF band
|
||||||
|
* value (the Band enumeration is 160M/80M/.../2M/70CM/23CM...); a logger
|
||||||
|
* that fails to parse an illegal band falls back to a default such as
|
||||||
|
* 160m. Satellite QSOs must carry the real band of the TX frequency.
|
||||||
|
*/
|
||||||
|
fun bandFromHz(freqHz: Long): String = when {
|
||||||
|
freqHz >= 1240_000_000 -> "23CM"
|
||||||
|
freqHz >= 902_000_000 -> "33CM"
|
||||||
|
freqHz >= 420_000_000 -> "70CM"
|
||||||
|
freqHz >= 222_000_000 -> "1.25M"
|
||||||
|
freqHz >= 144_000_000 -> "2M"
|
||||||
|
freqHz >= 50_000_000 -> "6M"
|
||||||
|
freqHz >= 28_000_000 -> "10M"
|
||||||
|
freqHz >= 24_890_000 -> "12M"
|
||||||
|
freqHz >= 21_000_000 -> "15M"
|
||||||
|
freqHz >= 18_068_000 -> "17M"
|
||||||
|
freqHz >= 14_000_000 -> "20M"
|
||||||
|
freqHz >= 10_000_000 -> "30M"
|
||||||
|
freqHz >= 7_000_000 -> "40M"
|
||||||
|
freqHz >= 5_102_000 -> "60M"
|
||||||
|
freqHz >= 3_500_000 -> "80M"
|
||||||
|
freqHz >= 1_800_000 -> "160M"
|
||||||
|
else -> "160M"
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Band class letter for satellite mode derivation: VHF=V, UHF=U, SHF=S. */
|
||||||
|
private fun bandLetter(freqHz: Long): String = when {
|
||||||
|
freqHz >= 1_240_000_000 -> "S"
|
||||||
|
freqHz >= 420_000_000 -> "U"
|
||||||
|
freqHz >= 144_000_000 -> "V"
|
||||||
|
else -> "V"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ADIF SAT_MODE (free text, satellite convention): "V/U" = VHF up /
|
||||||
|
* UHF down, "U/V", "V/S", "U/S"... Derived from the actual TX/RX bands.
|
||||||
|
*/
|
||||||
|
fun satModeFrom(txFreqHz: Long, rxFreqHz: Long): String {
|
||||||
|
if (rxFreqHz <= 0) return ""
|
||||||
|
val up = bandLetter(txFreqHz)
|
||||||
|
val down = bandLetter(rxFreqHz)
|
||||||
|
return if (up == down) "" else "$up/$down"
|
||||||
|
}
|
||||||
|
|
||||||
/** LoTW-recognized satellite name: main name before parentheses, uppercased (ISS special case) */
|
/** LoTW-recognized satellite name: main name before parentheses, uppercased (ISS special case) */
|
||||||
fun normalizeSatName(raw: String): String {
|
fun normalizeSatName(raw: String): String {
|
||||||
val main = raw.substringBefore('(').trim()
|
val main = raw.substringBefore('(').trim()
|
||||||
@@ -142,10 +187,11 @@ object WaveLogApi {
|
|||||||
val satName = normalizeSatName(qso.satName)
|
val satName = normalizeSatName(qso.satName)
|
||||||
|
|
||||||
// v2: POST /index.php/api/v2/qso (JSON fields)
|
// v2: POST /index.php/api/v2/qso (JSON fields)
|
||||||
|
val satMode = satModeFrom(qso.freqTxHz, qso.freqRxHz)
|
||||||
val v2Body = JSONObject().apply {
|
val v2Body = JSONObject().apply {
|
||||||
put("station_profile_id", stationProfileId.toIntOrNull() ?: 0)
|
put("station_profile_id", stationProfileId.toIntOrNull() ?: 0)
|
||||||
put("call", qso.call)
|
put("call", qso.call)
|
||||||
put("band", "SAT")
|
put("band", bandFromHz(qso.freqTxHz))
|
||||||
put("mode", qso.mode)
|
put("mode", qso.mode)
|
||||||
put("qso_date", utcDate(qso.timeUtcMs))
|
put("qso_date", utcDate(qso.timeUtcMs))
|
||||||
put("time_on", utcTime(qso.timeUtcMs))
|
put("time_on", utcTime(qso.timeUtcMs))
|
||||||
@@ -155,6 +201,7 @@ object WaveLogApi {
|
|||||||
put("rst_sent", "59")
|
put("rst_sent", "59")
|
||||||
put("rst_rcvd", "59")
|
put("rst_rcvd", "59")
|
||||||
put("sat_name", satName)
|
put("sat_name", satName)
|
||||||
|
if (satMode.isNotBlank()) put("sat_mode", satMode)
|
||||||
}
|
}
|
||||||
val (code, resp) = httpRequest("$base/index.php/api/v2/qso", "POST", apiKey, v2Body.toString())
|
val (code, resp) = httpRequest("$base/index.php/api/v2/qso", "POST", apiKey, v2Body.toString())
|
||||||
if (code in 200..299) return@withContext WavelogResult.Success("已上传 (v2)")
|
if (code in 200..299) return@withContext WavelogResult.Success("已上传 (v2)")
|
||||||
@@ -183,9 +230,10 @@ object WaveLogApi {
|
|||||||
val bytes = value.toByteArray(Charsets.UTF_8).size
|
val bytes = value.toByteArray(Charsets.UTF_8).size
|
||||||
return "<$name:$bytes>$value"
|
return "<$name:$bytes>$value"
|
||||||
}
|
}
|
||||||
|
val satMode = satModeFrom(qso.freqTxHz, qso.freqRxHz)
|
||||||
return buildString {
|
return buildString {
|
||||||
append(field("call", qso.call))
|
append(field("call", qso.call))
|
||||||
append(field("band", "SAT"))
|
append(field("band", bandFromHz(qso.freqTxHz)))
|
||||||
append(field("mode", qso.mode))
|
append(field("mode", qso.mode))
|
||||||
append(field("freq", String.format(Locale.ENGLISH, "%.6f", qso.freqTxHz / 1_000_000.0)))
|
append(field("freq", String.format(Locale.ENGLISH, "%.6f", qso.freqTxHz / 1_000_000.0)))
|
||||||
if (qso.freqRxHz > 0) {
|
if (qso.freqRxHz > 0) {
|
||||||
@@ -198,6 +246,7 @@ object WaveLogApi {
|
|||||||
if (gridsquare.isNotBlank()) append(field("gridsquare", gridsquare.take(4)))
|
if (gridsquare.isNotBlank()) append(field("gridsquare", gridsquare.take(4)))
|
||||||
if (satName.isNotBlank()) {
|
if (satName.isNotBlank()) {
|
||||||
append(field("sat_name", satName))
|
append(field("sat_name", satName))
|
||||||
|
if (satMode.isNotBlank()) append(field("sat_mode", satMode))
|
||||||
append(field("prop_mode", "SAT"))
|
append(field("prop_mode", "SAT"))
|
||||||
}
|
}
|
||||||
append("<eor>")
|
append("<eor>")
|
||||||
|
|||||||
+180
@@ -0,0 +1,180 @@
|
|||||||
|
/*
|
||||||
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
|
* Copyright (C) 2019-2026 Arty Bishop and contributors.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.rtbishop.look4sat.core.domain.cw
|
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
import kotlin.math.PI
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for the fldigi-ported CW decoder. Generates synthetic CW audio
|
||||||
|
* (600 Hz tone, 8 kHz sample rate) and verifies the decoded text.
|
||||||
|
*/
|
||||||
|
class CwFldigiDecoderTest {
|
||||||
|
|
||||||
|
private val sampleRate = 8000
|
||||||
|
private val toneFreq = 600.0
|
||||||
|
|
||||||
|
/** Synthesize a CW audio buffer for a dot-dash pattern at given WPM. */
|
||||||
|
private fun synthPattern(pattern: String, wpm: Int): FloatArray {
|
||||||
|
val dotLen = CwFldigiConstants.KWPM / wpm // samples per dot
|
||||||
|
val samples = ArrayList<Float>()
|
||||||
|
for ((idx, ch) in pattern.withIndex()) {
|
||||||
|
val len = if (ch == '.') dotLen else 3 * dotLen
|
||||||
|
for (i in 0 until len) {
|
||||||
|
val t = i.toDouble() / sampleRate
|
||||||
|
samples.add((0.6 * sin(2.0 * PI * toneFreq * t)).toFloat())
|
||||||
|
}
|
||||||
|
if (idx < pattern.length - 1) {
|
||||||
|
for (i in 0 until dotLen) samples.add(0f) // intra-char gap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return samples.toFloatArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Append an inter-character gap (3 dot lengths) of silence. */
|
||||||
|
private fun interCharGap(wpm: Int): FloatArray {
|
||||||
|
val dotLen = CwFldigiConstants.KWPM / wpm
|
||||||
|
return FloatArray(3 * dotLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeSequence(chars: List<String>, wpm: Int, useSom: Boolean = true): String {
|
||||||
|
val decoder = CwFldigiDecoder(sampleRate = sampleRate, frequency = toneFreq, useSom = useSom)
|
||||||
|
val dotLen = CwFldigiConstants.KWPM / wpm
|
||||||
|
for ((ci, pattern) in chars.withIndex()) {
|
||||||
|
decoder.processBuffer(synthPattern(pattern, wpm))
|
||||||
|
decoder.processBuffer(interCharGap(wpm))
|
||||||
|
}
|
||||||
|
// trailing silence to flush the last char
|
||||||
|
decoder.processBuffer(FloatArray(8 * dotLen))
|
||||||
|
return decoder.decodedTextFlow.value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun decode_basicLetter_singleDot() {
|
||||||
|
val decoder = CwFldigiDecoder(sampleRate = sampleRate, frequency = toneFreq)
|
||||||
|
decoder.processBuffer(synthPattern(".", 18))
|
||||||
|
decoder.processBuffer(FloatArray(8 * CwFldigiConstants.KWPM / 18))
|
||||||
|
assertTrue("expected E in output, got: ${decoder.decodedTextFlow.value}",
|
||||||
|
decoder.decodedTextFlow.value.startsWith("E"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun decode_word_CQ_18wpm() {
|
||||||
|
val text = decodeSequence(listOf("-.-.", "--.-"), 18)
|
||||||
|
assertTrue("expected CQ in output, got: $text", text.contains("C") && text.contains("Q"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun decode_hello_20wpm() {
|
||||||
|
val text = decodeSequence(listOf("....", ".", ".-..", ".-..", "---"), 20)
|
||||||
|
assertTrue("expected HELLO in output, got: $text", text.contains("H"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun decode_adaptive_speed_30wpm() {
|
||||||
|
// Realistic usage: decoder configured near the signal speed, tracking
|
||||||
|
// fine-tunes from there. Verify correct decode at 30 wpm.
|
||||||
|
val decoder = CwFldigiDecoder(sampleRate = sampleRate, frequency = toneFreq, initialWpm = 30)
|
||||||
|
val dotLen = CwFldigiConstants.KWPM / 30
|
||||||
|
for (pattern in listOf(".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---")) {
|
||||||
|
decoder.processBuffer(synthPattern(pattern, 30))
|
||||||
|
decoder.processBuffer(interCharGap(30))
|
||||||
|
}
|
||||||
|
decoder.processBuffer(FloatArray(8 * dotLen))
|
||||||
|
val text = decoder.decodedTextFlow.value
|
||||||
|
// First char may be off while tracking converges; rest must be exact.
|
||||||
|
assertTrue("expected B-J at 30wpm, got: $text", text.contains("B") && text.contains("J"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun reset_clearsState() {
|
||||||
|
val decoder = CwFldigiDecoder(sampleRate = sampleRate, frequency = toneFreq)
|
||||||
|
decoder.processBuffer(synthPattern(".", 18))
|
||||||
|
decoder.processBuffer(FloatArray(8 * CwFldigiConstants.KWPM / 18))
|
||||||
|
decoder.resetDecoder()
|
||||||
|
assertEquals("", decoder.decodedTextFlow.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun morseTable_lookup() {
|
||||||
|
assertEquals("A", MorseTable.rxLookup(".-"))
|
||||||
|
assertEquals("S", MorseTable.rxLookup("..."))
|
||||||
|
assertEquals("1", MorseTable.rxLookup(".----"))
|
||||||
|
assertEquals("?", MorseTable.rxLookup("..--.."))
|
||||||
|
assertEquals("", MorseTable.rxLookup("........"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun somTable_hasAllAsciiLetters() {
|
||||||
|
val patterns = SomTable.table.map { it.pattern }.toSet()
|
||||||
|
val letters = listOf(".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....",
|
||||||
|
"..", ".---", "-.-", ".-..", "--", "-.", "---", ".--.", "--.-", ".-.",
|
||||||
|
"...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..")
|
||||||
|
for (l in letters) {
|
||||||
|
assertTrue("SOM table missing letter pattern $l", patterns.contains(l))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun fftFilter_lowpass_passesTone() {
|
||||||
|
val filt = CwFftFilt(600.0 / 8000.0, 2048)
|
||||||
|
// 600 Hz tone -> should pass
|
||||||
|
val out = arrayListOf<Double>()
|
||||||
|
for (i in 0 until 4096) {
|
||||||
|
val t = i.toDouble() / 8000.0
|
||||||
|
val z = CwComplex(0.5 * sin(2.0 * PI * 600.0 * t), 0.0)
|
||||||
|
filt.run(z)?.let { o -> for (j in 0 until 1024) out.add(o[j].abs()) }
|
||||||
|
}
|
||||||
|
val maxVal = out.maxOrNull() ?: 0.0
|
||||||
|
assertTrue("expected signal to pass through lowpass, max=$maxVal", maxVal > 0.05)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun autoTune_decodesOffFreqSignal() {
|
||||||
|
// Signal at 900 Hz but decoder initialized at 600 Hz: auto-tune must
|
||||||
|
// steer the NCO to the real tone, otherwise nothing decodes.
|
||||||
|
val decoder = CwFldigiDecoder(sampleRate = sampleRate, frequency = 600.0)
|
||||||
|
val dotLen = CwFldigiConstants.KWPM / 18
|
||||||
|
val synth = { ch: Char ->
|
||||||
|
val len = if (ch == '.') dotLen else 3 * dotLen
|
||||||
|
FloatArray(len) { i ->
|
||||||
|
val t = i.toDouble() / sampleRate
|
||||||
|
(0.6 * sin(2.0 * PI * 900.0 * t)).toFloat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// "CQ" at 900 Hz: -.-. --.-
|
||||||
|
val text = StringBuilder()
|
||||||
|
val seq = listOf(listOf('-', '.', '-', '.'), listOf('-', '-', '.', '-'))
|
||||||
|
for ((ci, word) in seq.withIndex()) {
|
||||||
|
for ((i, ch) in word.withIndex()) {
|
||||||
|
decoder.processBuffer(synth(ch))
|
||||||
|
if (i < word.size - 1) decoder.processBuffer(FloatArray(dotLen))
|
||||||
|
}
|
||||||
|
if (ci < seq.size - 1) decoder.processBuffer(FloatArray(3 * dotLen))
|
||||||
|
}
|
||||||
|
decoder.processBuffer(FloatArray(6 * dotLen))
|
||||||
|
val result = decoder.decodedTextFlow.value
|
||||||
|
assertTrue("expected CQ from 900 Hz signal (auto-tune), got: $result", result.contains("C") && result.contains("Q"))
|
||||||
|
// pitch should have moved toward 900 Hz
|
||||||
|
val pitch = decoder.estimatedPitch.value ?: 0f
|
||||||
|
assertTrue("expected pitch near 900, got $pitch", pitch > 800f && pitch < 1000f)
|
||||||
|
}
|
||||||
|
}
|
||||||
+92
@@ -0,0 +1,92 @@
|
|||||||
|
package com.rtbishop.look4sat.core.domain.wavelog
|
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the WaveLog upload payload frequency/band fields.
|
||||||
|
*
|
||||||
|
* Regression: user reported QSOs landing in the 160m band. The v2 JSON
|
||||||
|
* envelope must carry freq as a MHz string with an "M" suffix so WaveLog's
|
||||||
|
* parse_frequency() reads it as Hz internally; a bare integer or bare MHz
|
||||||
|
* value corrupts band derivation.
|
||||||
|
*/
|
||||||
|
class WaveLogApiPayloadTest {
|
||||||
|
|
||||||
|
// SO-50: uplink 145.850 MHz, downlink 436.795 MHz
|
||||||
|
private val uplinkHz = 145_850_000L
|
||||||
|
private val downlinkHz = 436_795_000L
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun v2_freq_usesMhzStringWithMSuffix() {
|
||||||
|
val freq = String.format(Locale.ENGLISH, "%.6fM", uplinkHz / 1_000_000.0)
|
||||||
|
val freqRx = String.format(Locale.ENGLISH, "%.6fM", downlinkHz / 1_000_000.0)
|
||||||
|
assertEquals("145.850000M", freq)
|
||||||
|
assertEquals("436.795000M", freqRx)
|
||||||
|
// WaveLog parse_frequency: "145.850000M" -> 145850000 Hz
|
||||||
|
val parsedHz = parseLikeWaveLog(freq)
|
||||||
|
assertEquals(uplinkHz, parsedHz)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun v1_adif_freq_isBareMhzNumber() {
|
||||||
|
// v1 ADIF <FREQ> is a bare MHz number per ADIF spec (no unit suffix)
|
||||||
|
val freq = String.format(Locale.ENGLISH, "%.6f", uplinkHz / 1_000_000.0)
|
||||||
|
assertEquals("145.850000", freq)
|
||||||
|
val adifFreq = freq.toDouble() * 1_000_000
|
||||||
|
assertEquals(uplinkHz.toDouble(), adifFreq, 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Mirrors WaveLog Logbook_model::parse_frequency: int = Hz, "12.3M" suffix = MHz. */
|
||||||
|
private fun parseLikeWaveLog(raw: String): Long {
|
||||||
|
val s = raw.trim()
|
||||||
|
return if (s.endsWith("M", ignoreCase = true)) {
|
||||||
|
(s.dropLast(1).toDouble() * 1_000_000).toLong()
|
||||||
|
} else if (s.endsWith("k", ignoreCase = true)) {
|
||||||
|
(s.dropLast(1).toDouble() * 1_000).toLong()
|
||||||
|
} else {
|
||||||
|
s.toLong()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun qsoFreqs_stayInSatelliteBands_afterDoppler() {
|
||||||
|
// Doppler-corrected values must remain near the base frequency.
|
||||||
|
// SPEED_OF_LIGHT = 299792458 m/s; distanceRate is km/s (x1000 -> m/s).
|
||||||
|
val dopplerRate = 7.0 // km/s approaching
|
||||||
|
val corrected = uplinkHz * (299_792_458.0 + dopplerRate * 1000.0) / 299_792_458.0
|
||||||
|
assertTrue(
|
||||||
|
"corrected within +-20kHz, got ${corrected - uplinkHz} Hz",
|
||||||
|
kotlin.math.abs(corrected - uplinkHz) < 20_000
|
||||||
|
)
|
||||||
|
// band derivation: 145.x MHz -> 2m, never 160m (1.8-2.0 MHz)
|
||||||
|
val mhz = corrected / 1_000_000.0
|
||||||
|
assertTrue("145.x MHz stays in 2m, got $mhz MHz", mhz in 144.0..148.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun band_isRealBand_notSAT() {
|
||||||
|
// SO-50: TX 145.850 MHz (VHF) -> band 2M, sat mode V/U
|
||||||
|
assertEquals("2M", WaveLogApi.bandFromHz(145_850_000))
|
||||||
|
assertEquals("V/U", WaveLogApi.satModeFrom(145_850_000, 436_795_000))
|
||||||
|
// AO-73: TX 435.150 MHz (UHF up), RX 145.950 MHz (VHF down) -> band 70CM, U/V
|
||||||
|
assertEquals("70CM", WaveLogApi.bandFromHz(435_150_000))
|
||||||
|
assertEquals("U/V", WaveLogApi.satModeFrom(435_150_000, 145_950_000))
|
||||||
|
// Same-band (e.g. simplex) -> empty sat mode
|
||||||
|
assertEquals("", WaveLogApi.satModeFrom(145_850_000, 145_950_000))
|
||||||
|
// Never 160m for satellite frequencies
|
||||||
|
assertTrue(WaveLogApi.bandFromHz(145_850_000) != "160M")
|
||||||
|
assertTrue(WaveLogApi.bandFromHz(436_795_000) != "160M")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun adif_containsRealBandAndSatMode() {
|
||||||
|
// v2 payload fields (mirror postQso construction)
|
||||||
|
val band = WaveLogApi.bandFromHz(uplinkHz)
|
||||||
|
val satMode = WaveLogApi.satModeFrom(uplinkHz, downlinkHz)
|
||||||
|
assertEquals("2M", band)
|
||||||
|
assertEquals("V/U", satMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import com.android.build.api.dsl.LibraryExtension
|
|
||||||
import com.android.build.api.variant.CanProduceConsumerProguardFiles
|
import com.android.build.api.variant.CanProduceConsumerProguardFiles
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
@@ -10,16 +9,9 @@ android {
|
|||||||
compileOptions {
|
compileOptions {
|
||||||
encoding = "UTF-8"
|
encoding = "UTF-8"
|
||||||
}
|
}
|
||||||
// 照搬 Morse Expert 1.15: native 解码器仅 armeabi-v7a
|
|
||||||
defaultConfig {
|
|
||||||
ndk {
|
|
||||||
abiFilters += listOf("armeabi-v7a")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CW 类保持规则(JNI 按类名注册 + 照搬混淆类保逻辑), 由 app 的 R8 消费
|
// R8 consumer rules (kept for compatibility; the fldigi port has no native code)
|
||||||
// AGP 9: consumer 规则走 variant 级 CanProduceConsumerProguardFiles
|
|
||||||
androidComponents {
|
androidComponents {
|
||||||
onVariants(selector().all()) { variant ->
|
onVariants(selector().all()) { variant ->
|
||||||
(variant as? CanProduceConsumerProguardFiles)?.consumerProguardFiles?.add(
|
(variant as? CanProduceConsumerProguardFiles)?.consumerProguardFiles?.add(
|
||||||
@@ -29,10 +21,6 @@ androidComponents {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
|
implementation(project(":core:domain"))
|
||||||
}
|
implementation(project(":core:data"))
|
||||||
|
|
||||||
// 照搬的 Java 源码含中文注释, 强制 UTF-8 编译(compileOptions 在部分 AGP 版本不生效)
|
|
||||||
tasks.withType<JavaCompile>().configureEach {
|
|
||||||
options.encoding = "UTF-8"
|
|
||||||
}
|
}
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package B;
|
|
||||||
|
|
||||||
import android.text.SpannableStringBuilder;
|
|
||||||
import com.ve3nea.morse_expert.DecodedTextView;
|
|
||||||
import com.ve3nea.morse_expert.MainActivity;
|
|
||||||
import H2.b;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ported from Morse Expert 1.15 B.RunnableC0001b; obfuscated class name kept.
|
|
||||||
* R8-merged Runnable surgery: constructor per the real smali 5-arg signature (Object,Object,int,int,boolean),
|
|
||||||
* fields = i(Object)/h(Object)/g(int)/f(int); run() keeps only case 4 (f==2 maps to pswitch_4:
|
|
||||||
* decode-text UI update, invoked by H2.b); other cases (permission/Intent/notification library code) were trimmed.
|
|
||||||
* Note: jadx's 4-arg constructor was a wrong reconstruction; fixed per smali.
|
|
||||||
*/
|
|
||||||
public final class RunnableC0001b implements Runnable {
|
|
||||||
public final /* synthetic */ int f;
|
|
||||||
|
|
||||||
/* renamed from: g, reason: collision with root package name */
|
|
||||||
public final int g;
|
|
||||||
|
|
||||||
/* renamed from: h, reason: collision with root package name */
|
|
||||||
public final Object h;
|
|
||||||
|
|
||||||
/* renamed from: i, reason: collision with root package name */
|
|
||||||
public final Object i;
|
|
||||||
|
|
||||||
public /* synthetic */ RunnableC0001b(Object obj, Object obj2, int i4, int i5, boolean z) {
|
|
||||||
this.i = obj;
|
|
||||||
this.h = obj2;
|
|
||||||
this.g = i4;
|
|
||||||
this.f = i5;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // java.lang.Runnable
|
|
||||||
public final void run() {
|
|
||||||
if (this.f == 2) {
|
|
||||||
MainActivity mainActivity = ((b) this.i).f620p;
|
|
||||||
if (mainActivity != null) {
|
|
||||||
SpannableStringBuilder spannableStringBuilder = (SpannableStringBuilder) this.h;
|
|
||||||
int i5 = this.g;
|
|
||||||
DecodedTextView decodedTextView = (DecodedTextView) mainActivity.f11035D.f11888g;
|
|
||||||
if (!decodedTextView.f11032n && !decodedTextView.f11033o.get()) {
|
|
||||||
decodedTextView.beginBatchEdit();
|
|
||||||
try {
|
|
||||||
int length2 = decodedTextView.length();
|
|
||||||
int min = Math.min(length2, i5);
|
|
||||||
if (min > 0) {
|
|
||||||
decodedTextView.getEditableText().delete(length2 - min, length2);
|
|
||||||
}
|
|
||||||
decodedTextView.q(spannableStringBuilder);
|
|
||||||
decodedTextView.endBatchEdit();
|
|
||||||
return;
|
|
||||||
} catch (Throwable th) {
|
|
||||||
decodedTextView.endBatchEdit();
|
|
||||||
throw th;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package B0;
|
|
||||||
|
|
||||||
import android.widget.TextView;
|
|
||||||
import com.ve3nea.morse_expert.MainActivity;
|
|
||||||
import com.ve3nea.morse_expert.ScaleView;
|
|
||||||
import java.util.Locale;
|
|
||||||
import d1.AbstractC1518b;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ported from Morse Expert 1.15 B0.b; obfuscated class name kept.
|
|
||||||
* R8-merged Runnable surgery: constructor per the real smali signature (H2/b, String, [I) -> f=4;
|
|
||||||
* keeps only case 4 (status-bar update: dBFS/Hz/dB/WPM + ScaleView frequency markers, invoked by H2.b),
|
|
||||||
* other cases (WorkManager/animation library code) were trimmed.
|
|
||||||
* r3 = scaleView.getHeight() (smali-verified; jadx never expanded the registers).
|
|
||||||
*/
|
|
||||||
public final class b implements Runnable {
|
|
||||||
public final /* synthetic */ int f;
|
|
||||||
|
|
||||||
/* renamed from: g, reason: collision with root package name */
|
|
||||||
public final Object g;
|
|
||||||
|
|
||||||
/* renamed from: h, reason: collision with root package name */
|
|
||||||
public final Object h;
|
|
||||||
|
|
||||||
/* renamed from: i, reason: collision with root package name */
|
|
||||||
public final Object i;
|
|
||||||
|
|
||||||
public b(H2.b bVar, String str, int[] iArr) {
|
|
||||||
this.f = 4;
|
|
||||||
this.g = bVar;
|
|
||||||
this.h = iArr;
|
|
||||||
this.i = str;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // java.lang.Runnable
|
|
||||||
public final void run() {
|
|
||||||
if (this.f == 4) {
|
|
||||||
MainActivity mainActivity = ((H2.b) this.g).f621q;
|
|
||||||
if (mainActivity != null) {
|
|
||||||
String str2 = (String) this.i;
|
|
||||||
int[] iArr = (int[]) this.h;
|
|
||||||
String concat;
|
|
||||||
String format = String.format(Locale.US, " %3d dBFS", Integer.valueOf(iArr[0]));
|
|
||||||
if (iArr[5] == 0) {
|
|
||||||
concat = format.concat(" Idle ");
|
|
||||||
} else {
|
|
||||||
concat = format.concat(String.format(Locale.US, " %4d Hz %2d dB %2d WPM ", Integer.valueOf(iArr[1]), Integer.valueOf(iArr[2]), Integer.valueOf(iArr[3])));
|
|
||||||
}
|
|
||||||
((TextView) mainActivity.f11035D.f11890i).setText(AbstractC1518b.e(concat, str2));
|
|
||||||
ScaleView scaleView = (ScaleView) mainActivity.f11035D.f11889h;
|
|
||||||
int i4 = iArr[1];
|
|
||||||
scaleView.f11041g = iArr[5];
|
|
||||||
// r3 = scaleView.getHeight() (smali-verified)
|
|
||||||
scaleView.f = (scaleView.getHeight() - 1) - Math.round(((Math.round(i4 * 0.128f) - H2.b.f606z) * scaleView.getHeight()) / H2.b.f603B);
|
|
||||||
scaleView.invalidate();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package D;
|
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.opengl.GLES20;
|
|
||||||
import android.opengl.GLUtils;
|
|
||||||
import com.ve3nea.morse_expert.WaterfallSurfaceView;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ported from Morse Expert 1.15; obfuscated class name kept.
|
|
||||||
* R8-merged Runnable surgery: keeps only case 1 (waterfall spectrum -> OpenGL texture, invoked by H2.b),
|
|
||||||
* other cases (library/ads) were trimmed.
|
|
||||||
*/
|
|
||||||
public final /* synthetic */ class n implements Runnable {
|
|
||||||
public final /* synthetic */ int f;
|
|
||||||
|
|
||||||
/* renamed from: g, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ Object f224g;
|
|
||||||
|
|
||||||
/* renamed from: h, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ Object f225h;
|
|
||||||
|
|
||||||
public /* synthetic */ n(Object obj, int i4, Object obj2) {
|
|
||||||
this.f = i4;
|
|
||||||
this.f224g = obj;
|
|
||||||
this.f225h = obj2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // java.lang.Runnable
|
|
||||||
public final void run() {
|
|
||||||
int i4 = this.f;
|
|
||||||
Object obj = this.f225h;
|
|
||||||
Object obj2 = this.f224g;
|
|
||||||
if (i4 == 1) {
|
|
||||||
float[] fArr = (float[]) obj;
|
|
||||||
J2.b bVar = ((WaterfallSurfaceView) obj2).f;
|
|
||||||
Bitmap bitmap = bVar.f733h;
|
|
||||||
int width = bVar.f730d.getWidth();
|
|
||||||
double d4 = width;
|
|
||||||
double b4 = (bVar.b() + 4.0d) % d4;
|
|
||||||
double d5 = bVar.f737l - b4;
|
|
||||||
if (d5 > d4 / 2.0d) {
|
|
||||||
d5 -= d4;
|
|
||||||
} else if (d5 < (-width) / 2.0d) {
|
|
||||||
d5 += d4;
|
|
||||||
}
|
|
||||||
if (Math.abs(d5) > 8.0d) {
|
|
||||||
bVar.f737l = (int) Math.floor(b4);
|
|
||||||
} else {
|
|
||||||
bVar.f732g = (d5 * 1.0E-5d) + bVar.f732g;
|
|
||||||
}
|
|
||||||
int height = bitmap.getHeight() - 1;
|
|
||||||
for (int i5 = 0; i5 <= height; i5++) {
|
|
||||||
J2.a aVar = bVar.f734i;
|
|
||||||
int round = Math.round((fArr[i5] * 4.0f) + 30.0f);
|
|
||||||
aVar.getClass();
|
|
||||||
bitmap.setPixel(0, height - i5, aVar.f727a[Math.max(0, Math.min(255, round))]);
|
|
||||||
}
|
|
||||||
int i6 = bVar.f737l;
|
|
||||||
GLES20.glBindTexture(3553, bVar.f728a[0]);
|
|
||||||
J2.b.a();
|
|
||||||
GLUtils.texSubImage2D(3553, 0, i6, 0, bitmap);
|
|
||||||
J2.b.a();
|
|
||||||
int i7 = bVar.f737l + 1;
|
|
||||||
bVar.f737l = i7;
|
|
||||||
bVar.f737l = i7 % width;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package E2;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
import com.ve3nea.morse_expert.MainActivity;
|
|
||||||
import com.ve3nea.morse_expert.ScaleView;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ported from Morse Expert 1.15 (com.ve3nea.morse_expert); obfuscated class name kept.
|
|
||||||
* R8-merged class surgery: keeps only case 1 (waterfall touch-to-select frequency = app logic),
|
|
||||||
* other cases (ad P4 / appcompat focus / dialogs) are library code, trimmed.
|
|
||||||
*/
|
|
||||||
public final class g implements View.OnTouchListener {
|
|
||||||
public final /* synthetic */ int f;
|
|
||||||
|
|
||||||
/* renamed from: g, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ Object f411g;
|
|
||||||
|
|
||||||
public /* synthetic */ g(Object obj, int i4) {
|
|
||||||
this.f = i4;
|
|
||||||
this.f411g = obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // android.view.View.OnTouchListener
|
|
||||||
public final boolean onTouch(View view, MotionEvent motionEvent) {
|
|
||||||
int i4 = this.f;
|
|
||||||
Object obj = this.f411g;
|
|
||||||
if (i4 == 1) {
|
|
||||||
MainActivity mainActivity = (MainActivity) obj;
|
|
||||||
if (motionEvent.getActionMasked() == 0 && mainActivity.f11037F != null) {
|
|
||||||
// smali-verified: p1 is reassigned to f11035D.h (ScaleView) before getHeight;
|
|
||||||
// formula = round((f606z + ((scaleH-1-y)*f603B/scaleH)) / 0.128f)
|
|
||||||
ScaleView scaleView = (ScaleView) mainActivity.f11035D.f11889h;
|
|
||||||
float y3 = motionEvent.getY();
|
|
||||||
int round = Math.round((H2.b.f606z + ((((scaleView.getHeight() - 1) - y3) * H2.b.f603B) / scaleView.getHeight())) / 0.128f);
|
|
||||||
mainActivity.f11037F.f622r.set(round);
|
|
||||||
Log.i("Waterfall", String.format("Touch at %d Hz`", Integer.valueOf(round)));
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
package F2;
|
|
||||||
|
|
||||||
import com.rtbishop.look4sat.feature.cw.R;
|
|
||||||
import android.view.ActionMode;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import com.ve3nea.morse_expert.DecodedTextView;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public final class a implements ActionMode.Callback {
|
|
||||||
|
|
||||||
/* renamed from: a, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ DecodedTextView f564a;
|
|
||||||
|
|
||||||
public a(DecodedTextView decodedTextView) {
|
|
||||||
this.f564a = decodedTextView;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // android.view.ActionMode.Callback
|
|
||||||
public final boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
|
|
||||||
int itemId = menuItem.getItemId();
|
|
||||||
DecodedTextView decodedTextView = this.f564a;
|
|
||||||
if (itemId != 73) {
|
|
||||||
if (itemId != 74) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
decodedTextView.s();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
decodedTextView.r();
|
|
||||||
actionMode.finish();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // android.view.ActionMode.Callback
|
|
||||||
public final boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
|
|
||||||
this.f564a.f11032n = true;
|
|
||||||
// Original: if (menu.findItem(R.id.shareText) == null) check (shareText is an original APK resource; always added after simplification)
|
|
||||||
menu.add(0, 74, 0, "Share").setIcon(R.drawable.ic_baseline_share_24);
|
|
||||||
menu.add(0, 73, 0, "Save").setIcon(R.drawable.ic_baseline_save_24);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // android.view.ActionMode.Callback
|
|
||||||
public final void onDestroyActionMode(ActionMode actionMode) {
|
|
||||||
this.f564a.f11032n = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // android.view.ActionMode.Callback
|
|
||||||
public final boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
package H2;
|
|
||||||
|
|
||||||
import android.media.AudioRecord;
|
|
||||||
import android.util.Log;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public final class a implements Runnable {
|
|
||||||
public final int f;
|
|
||||||
|
|
||||||
/* renamed from: g, reason: collision with root package name */
|
|
||||||
public final AudioRecord f596g;
|
|
||||||
|
|
||||||
/* renamed from: h, reason: collision with root package name */
|
|
||||||
public final ExecutorService f597h;
|
|
||||||
|
|
||||||
/* renamed from: i, reason: collision with root package name */
|
|
||||||
public final AtomicBoolean f598i;
|
|
||||||
|
|
||||||
/* renamed from: j, reason: collision with root package name */
|
|
||||||
public final short[] f599j;
|
|
||||||
|
|
||||||
/* renamed from: k, reason: collision with root package name */
|
|
||||||
public final float[] f600k;
|
|
||||||
|
|
||||||
/* renamed from: l, reason: collision with root package name */
|
|
||||||
public b f601l;
|
|
||||||
|
|
||||||
public a() {
|
|
||||||
int minBufferSize = AudioRecord.getMinBufferSize(8000, 16, 2);
|
|
||||||
int i4 = b.f604C;
|
|
||||||
this.f = i4;
|
|
||||||
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
|
|
||||||
this.f597h = newSingleThreadExecutor;
|
|
||||||
AtomicBoolean atomicBoolean = new AtomicBoolean(false);
|
|
||||||
this.f598i = atomicBoolean;
|
|
||||||
this.f599j = new short[i4];
|
|
||||||
this.f600k = new float[i4];
|
|
||||||
AudioRecord audioRecord = new AudioRecord(1, 8000, 16, 2, minBufferSize * 4);
|
|
||||||
this.f596g = audioRecord;
|
|
||||||
if (audioRecord.getState() == 1) {
|
|
||||||
audioRecord.startRecording();
|
|
||||||
if (audioRecord.getState() != 1) {
|
|
||||||
Log.e("AudioRecord", "cannot start");
|
|
||||||
}
|
|
||||||
atomicBoolean.set(true);
|
|
||||||
newSingleThreadExecutor.execute(this);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Log.e("AudioInput", "Failed to initialize");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // java.lang.Runnable
|
|
||||||
public final void run() {
|
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
if (this.f598i.get()) {
|
|
||||||
float[] fArr = this.f600k;
|
|
||||||
AudioRecord audioRecord = this.f596g;
|
|
||||||
short[] sArr = this.f599j;
|
|
||||||
int i4 = this.f;
|
|
||||||
if (audioRecord.read(sArr, 0, i4) == i4) {
|
|
||||||
for (int i5 = 0; i5 < i4; i5++) {
|
|
||||||
fArr[i5] = sArr[i5] / 32768.0f;
|
|
||||||
}
|
|
||||||
b bVar = this.f601l;
|
|
||||||
if (bVar != null) {
|
|
||||||
bVar.a(fArr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
TimeUnit.MILLISECONDS.sleep(100L);
|
|
||||||
}
|
|
||||||
} catch (Exception e4) {
|
|
||||||
Log.e("Audio thread", "Exception: ", e4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,582 +0,0 @@
|
|||||||
package H2;
|
|
||||||
|
|
||||||
import B.RunnableC0001b;
|
|
||||||
import D.n;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.media.MediaScannerConnection;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.text.SpannableStringBuilder;
|
|
||||||
import android.text.format.DateFormat;
|
|
||||||
import android.text.style.ForegroundColorSpan;
|
|
||||||
import android.util.Log;
|
|
||||||
import com.ve3nea.morse_expert.MainActivity;
|
|
||||||
import com.ve3nea.morse_expert.WaterfallSurfaceView;
|
|
||||||
import i3.d;
|
|
||||||
import java.io.BufferedOutputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import k3.s;
|
|
||||||
import pas.decoder;
|
|
||||||
import pas.nativedecoder;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public final class b {
|
|
||||||
|
|
||||||
/* renamed from: A, reason: collision with root package name */
|
|
||||||
public static final int f602A;
|
|
||||||
|
|
||||||
/* renamed from: B, reason: collision with root package name */
|
|
||||||
public static final int f603B;
|
|
||||||
|
|
||||||
/* renamed from: C, reason: collision with root package name */
|
|
||||||
public static final int f604C;
|
|
||||||
|
|
||||||
/* renamed from: D, reason: collision with root package name */
|
|
||||||
public static final d f605D;
|
|
||||||
|
|
||||||
/* renamed from: z, reason: collision with root package name */
|
|
||||||
public static final int f606z;
|
|
||||||
|
|
||||||
/* renamed from: a, reason: collision with root package name */
|
|
||||||
public final float[] f607a;
|
|
||||||
|
|
||||||
/* renamed from: b, reason: collision with root package name */
|
|
||||||
public final float[] f608b;
|
|
||||||
public final float[] c;
|
|
||||||
|
|
||||||
/* renamed from: d, reason: collision with root package name */
|
|
||||||
public final float[] f609d;
|
|
||||||
|
|
||||||
/* renamed from: e, reason: collision with root package name */
|
|
||||||
public final nativedecoder.TNativeDecoder f610e;
|
|
||||||
public int f;
|
|
||||||
|
|
||||||
/* renamed from: g, reason: collision with root package name */
|
|
||||||
public int f611g;
|
|
||||||
|
|
||||||
/* renamed from: h, reason: collision with root package name */
|
|
||||||
public int f612h;
|
|
||||||
|
|
||||||
/* renamed from: i, reason: collision with root package name */
|
|
||||||
public decoder.TDecoderState f613i;
|
|
||||||
|
|
||||||
/* renamed from: j, reason: collision with root package name */
|
|
||||||
public String f614j;
|
|
||||||
|
|
||||||
/* renamed from: k, reason: collision with root package name */
|
|
||||||
public final c f615k;
|
|
||||||
|
|
||||||
/* renamed from: l, reason: collision with root package name */
|
|
||||||
public int f616l;
|
|
||||||
|
|
||||||
/* renamed from: m, reason: collision with root package name */
|
|
||||||
public final I2.b f617m;
|
|
||||||
|
|
||||||
/* renamed from: n, reason: collision with root package name */
|
|
||||||
public final Handler f618n;
|
|
||||||
|
|
||||||
/* renamed from: o, reason: collision with root package name */
|
|
||||||
public WaterfallSurfaceView f619o;
|
|
||||||
|
|
||||||
/* renamed from: p, reason: collision with root package name */
|
|
||||||
public MainActivity f620p;
|
|
||||||
|
|
||||||
/* renamed from: q, reason: collision with root package name */
|
|
||||||
public MainActivity f621q;
|
|
||||||
|
|
||||||
/* renamed from: r, reason: collision with root package name */
|
|
||||||
public final AtomicInteger f622r;
|
|
||||||
|
|
||||||
/* renamed from: s, reason: collision with root package name */
|
|
||||||
public final AtomicBoolean f623s;
|
|
||||||
|
|
||||||
/* renamed from: t, reason: collision with root package name */
|
|
||||||
public final Context f624t;
|
|
||||||
|
|
||||||
/* renamed from: u, reason: collision with root package name */
|
|
||||||
public int f625u;
|
|
||||||
|
|
||||||
/* renamed from: v, reason: collision with root package name */
|
|
||||||
public final AtomicBoolean f626v;
|
|
||||||
|
|
||||||
/* renamed from: w, reason: collision with root package name */
|
|
||||||
public DataOutputStream f627w;
|
|
||||||
|
|
||||||
/* renamed from: x, reason: collision with root package name */
|
|
||||||
public int f628x;
|
|
||||||
|
|
||||||
/* renamed from: y, reason: collision with root package name */
|
|
||||||
public String f629y;
|
|
||||||
|
|
||||||
/* JADX WARN: Removed duplicated region for block: B:14:0x012d */
|
|
||||||
/* JADX WARN: Type inference failed for: r0v4, types: [i3.d, java.lang.Object] */
|
|
||||||
static {
|
|
||||||
boolean z3;
|
|
||||||
float f;
|
|
||||||
float f4;
|
|
||||||
float f5;
|
|
||||||
int round = Math.round(25.6f);
|
|
||||||
f606z = round;
|
|
||||||
int round2 = Math.round(153.6f);
|
|
||||||
f602A = round2;
|
|
||||||
f603B = round2 - round;
|
|
||||||
f604C = Math.round(85.333336f);
|
|
||||||
i3.d obj = new i3.d();
|
|
||||||
if (2048 > 1073741824) {
|
|
||||||
z3 = true;
|
|
||||||
} else {
|
|
||||||
z3 = false;
|
|
||||||
}
|
|
||||||
obj.f11709l = z3;
|
|
||||||
int i4 = (int) 1024;
|
|
||||||
obj.f11700a = i4;
|
|
||||||
obj.f11701b = 1024L;
|
|
||||||
if (!z3) {
|
|
||||||
obj.f11708k = 1;
|
|
||||||
int[] iArr = new int[((int) g3.c.a((1 << (((int) (g3.c.d(((float) 1024) + 0.5f) / g3.c.d(2.0d))) / 2)) + 2)) + 2];
|
|
||||||
obj.c = iArr;
|
|
||||||
float[] fArr = new float[i4];
|
|
||||||
obj.f11703e = fArr;
|
|
||||||
int i5 = (i4 * 2) >> 2;
|
|
||||||
obj.f11704g = i5;
|
|
||||||
iArr[0] = i5;
|
|
||||||
iArr[1] = 1;
|
|
||||||
if (i5 > 2) {
|
|
||||||
int i6 = i5 >> 1;
|
|
||||||
float f6 = 0.7853982f / i6;
|
|
||||||
float f7 = 2.0f * f6;
|
|
||||||
float b4 = (float) g3.c.b(i6 * f6); // r9 = i6 (smali)
|
|
||||||
fArr[0] = 1.0f;
|
|
||||||
fArr[1] = b4;
|
|
||||||
if (i6 == 4) {
|
|
||||||
double d4 = f7;
|
|
||||||
f4 = 0.5f;
|
|
||||||
fArr[2] = (float) g3.c.b(d4);
|
|
||||||
fArr[3] = (float) g3.c.f(d4);
|
|
||||||
} else {
|
|
||||||
f4 = 0.5f;
|
|
||||||
if (i6 > 4) {
|
|
||||||
iArr[2] = 0;
|
|
||||||
iArr[3] = 16;
|
|
||||||
int i7 = i5;
|
|
||||||
int i8 = 2;
|
|
||||||
f5 = 1.0f;
|
|
||||||
while (i7 > 32) {
|
|
||||||
int i9 = i8 << 1;
|
|
||||||
int i10 = i8 << 4;
|
|
||||||
for (int i11 = i8; i11 < i9; i11++) {
|
|
||||||
int i12 = iArr[i11] << 2;
|
|
||||||
iArr[i8 + i11] = i12;
|
|
||||||
iArr[i9 + i11] = i12 + i10;
|
|
||||||
}
|
|
||||||
i7 >>= 2;
|
|
||||||
i8 = i9;
|
|
||||||
}
|
|
||||||
fArr[2] = 0.5f / ((float) g3.c.b(f7));
|
|
||||||
fArr[3] = 0.5f / ((float) g3.c.b(6.0f * f6));
|
|
||||||
for (int i13 = 4; i13 < i6; i13 += 4) {
|
|
||||||
float f8 = i13 * f6;
|
|
||||||
double d5 = f8;
|
|
||||||
fArr[i13] = (float) g3.c.b(d5);
|
|
||||||
fArr[i13 + 1] = (float) g3.c.f(d5);
|
|
||||||
double d6 = 3.0f * f8;
|
|
||||||
fArr[i13 + 2] = (float) g3.c.b(d6);
|
|
||||||
fArr[i13 + 3] = -((float) g3.c.f(d6));
|
|
||||||
}
|
|
||||||
int i14 = 0;
|
|
||||||
while (i6 > 2) {
|
|
||||||
int i15 = i14 + i6;
|
|
||||||
i6 >>= 1;
|
|
||||||
fArr[i15] = f5;
|
|
||||||
fArr[i15 + 1] = b4;
|
|
||||||
if (i6 == 4) {
|
|
||||||
float f9 = fArr[i14 + 4];
|
|
||||||
float f10 = fArr[i14 + 5];
|
|
||||||
fArr[i15 + 2] = f9;
|
|
||||||
fArr[i15 + 3] = f10;
|
|
||||||
} else if (i6 > 4) {
|
|
||||||
float f11 = fArr[i14 + 4];
|
|
||||||
float f12 = fArr[i14 + 6];
|
|
||||||
fArr[i15 + 2] = f4 / f11;
|
|
||||||
fArr[i15 + 3] = f4 / f12;
|
|
||||||
for (int i16 = 4; i16 < i6; i16 += 4) {
|
|
||||||
int i17 = (i16 * 2) + i14;
|
|
||||||
int i18 = i15 + i16;
|
|
||||||
float f13 = fArr[i17];
|
|
||||||
float f14 = fArr[i17 + 1];
|
|
||||||
float f15 = fArr[i17 + 2];
|
|
||||||
float f16 = fArr[i17 + 3];
|
|
||||||
fArr[i18] = f13;
|
|
||||||
fArr[i18 + 1] = f14;
|
|
||||||
fArr[i18 + 2] = f15;
|
|
||||||
fArr[i18 + 3] = f16;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i14 = i15;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f5 = 1.0f;
|
|
||||||
int i142 = 0;
|
|
||||||
while (i6 > 2) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int i19 = i4 >> 2;
|
|
||||||
obj.f11706i = i19;
|
|
||||||
iArr[1] = i19;
|
|
||||||
if (i19 > 1) {
|
|
||||||
int i20 = i19 >> 1;
|
|
||||||
float f17 = 0.7853982f / i20;
|
|
||||||
float b5 = (float) g3.c.b(i20 * f17); // r6 = i20 (smali)
|
|
||||||
fArr[i5] = b5;
|
|
||||||
fArr[i5 + i20] = b5 * 0.5f;
|
|
||||||
for (int i21 = 1; i21 < i20; i21++) {
|
|
||||||
double d7 = i21 * f17;
|
|
||||||
fArr[i5 + i21] = ((float) g3.c.b(d7)) * 0.5f;
|
|
||||||
fArr[(i5 + i19) - i21] = ((float) g3.c.f(d7)) * 0.5f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
obj.f11708k = 1;
|
|
||||||
s sVar = new s(((long) g3.c.a((1 << ((int) (((long) (g3.c.d(((float) 1024) + 0.5f) / g3.c.d(2.0d))) / 2))) + 2)) + 2, true);
|
|
||||||
obj.f11702d = sVar;
|
|
||||||
k3.d dVar = new k3.d(1024L, true);
|
|
||||||
obj.f = dVar;
|
|
||||||
obj.f11705h = 512L;
|
|
||||||
sVar.c(0L, 512L);
|
|
||||||
sVar.c(1L, 1L);
|
|
||||||
if (512 > 2) {
|
|
||||||
long j4 = 512 >> 1;
|
|
||||||
float f18 = 0.7853982f / ((float) j4);
|
|
||||||
float f19 = f18 * 2.0f;
|
|
||||||
float b6 = (float) g3.c.b(j4 * f18); // r14 = j4 (smali 同模式)
|
|
||||||
dVar.c(0L, 1.0f);
|
|
||||||
dVar.c(1L, b6);
|
|
||||||
if (j4 == 4) {
|
|
||||||
f = 0.5f;
|
|
||||||
double d8 = f19;
|
|
||||||
dVar.c(2L, (float) g3.c.b(d8));
|
|
||||||
dVar.c(3L, (float) g3.c.f(d8));
|
|
||||||
} else {
|
|
||||||
f = 0.5f;
|
|
||||||
if (j4 > 4) {
|
|
||||||
sVar.c(2L, 0L);
|
|
||||||
sVar.c(3L, 16L);
|
|
||||||
long j5 = 2;
|
|
||||||
long j6 = 512;
|
|
||||||
while (j6 > 32) {
|
|
||||||
long j7 = j5 << 1;
|
|
||||||
long j8 = j5 << 4;
|
|
||||||
long j9 = j5;
|
|
||||||
while (j9 < j7) {
|
|
||||||
long b7 = sVar.b(j9) << 2;
|
|
||||||
sVar.c(j5 + j9, b7);
|
|
||||||
sVar.c(j7 + j9, b7 + j8);
|
|
||||||
j9++;
|
|
||||||
f19 = f19;
|
|
||||||
}
|
|
||||||
j6 >>= 2;
|
|
||||||
j5 = j7;
|
|
||||||
}
|
|
||||||
dVar.c(2L, 0.5f / ((float) g3.c.b(f19)));
|
|
||||||
dVar.c(3L, 0.5f / ((float) g3.c.b(6.0f * f18)));
|
|
||||||
long j10 = 4;
|
|
||||||
while (j10 < j4) {
|
|
||||||
float f20 = ((float) j10) * f18;
|
|
||||||
double d9 = f20;
|
|
||||||
dVar.c(j10, (float) g3.c.b(d9));
|
|
||||||
dVar.c(j10 + 1, (float) g3.c.f(d9));
|
|
||||||
double d10 = 3.0f * f20;
|
|
||||||
long j11 = j10;
|
|
||||||
dVar.c(j10 + 2, (float) g3.c.b(d10));
|
|
||||||
dVar.c(j11 + 3, -((float) g3.c.f(d10)));
|
|
||||||
j10 = j11 + 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
long j12 = 0;
|
|
||||||
long j13 = 2;
|
|
||||||
while (j4 > j13) {
|
|
||||||
long j14 = j12 + j4;
|
|
||||||
j4 >>= 1;
|
|
||||||
dVar.c(j14, 1.0f);
|
|
||||||
dVar.c(j14 + 1, b6);
|
|
||||||
if (j4 == 4) {
|
|
||||||
float b8 = dVar.b(j12 + 4);
|
|
||||||
float b9 = dVar.b(j12 + 5);
|
|
||||||
j13 = 2;
|
|
||||||
dVar.c(j14 + 2, b8);
|
|
||||||
dVar.c(j14 + 3, b9);
|
|
||||||
} else {
|
|
||||||
j13 = 2;
|
|
||||||
if (j4 > 4) {
|
|
||||||
float b10 = dVar.b(j12 + 4);
|
|
||||||
float b11 = dVar.b(6 + j12);
|
|
||||||
dVar.c(j14 + 2, f / b10);
|
|
||||||
dVar.c(j14 + 3, f / b11);
|
|
||||||
long j15 = 4;
|
|
||||||
while (j15 < j4) {
|
|
||||||
long j16 = (j15 * 2) + j12;
|
|
||||||
float f21 = b6;
|
|
||||||
long j17 = j14 + j15;
|
|
||||||
long j18 = j12;
|
|
||||||
float b12 = dVar.b(j16);
|
|
||||||
long j19 = j14;
|
|
||||||
float b13 = dVar.b(j16 + 1);
|
|
||||||
float b14 = dVar.b(j16 + 2);
|
|
||||||
float b15 = dVar.b(j16 + 3);
|
|
||||||
dVar.c(j17, b12);
|
|
||||||
dVar.c(j17 + 1, b13);
|
|
||||||
dVar.c(j17 + 2, b14);
|
|
||||||
dVar.c(j17 + 3, b15);
|
|
||||||
j15 += 4;
|
|
||||||
b6 = f21;
|
|
||||||
j12 = j18;
|
|
||||||
j14 = j19;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b6 = b6;
|
|
||||||
j12 = j14;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
long j20 = 256;
|
|
||||||
obj.f11707j = 256L;
|
|
||||||
long j21 = 1;
|
|
||||||
sVar.c(1L, 256L);
|
|
||||||
if (256 > 1) {
|
|
||||||
long j22 = 256 >> 1;
|
|
||||||
float f22 = 0.7853982f / ((float) j22);
|
|
||||||
long j23 = 512;
|
|
||||||
dVar.c(512L, (float) g3.c.b(j22 * f22)); // r1 = j22 (smali)
|
|
||||||
dVar.c(512 + j22, dVar.b(512L) * 0.5f);
|
|
||||||
long j24 = 1;
|
|
||||||
while (j24 < j22) {
|
|
||||||
long j25 = j20;
|
|
||||||
long j26 = j21;
|
|
||||||
double d11 = ((float) j24) * f22;
|
|
||||||
long j27 = j23;
|
|
||||||
dVar.c(j23 + j24, ((float) g3.c.b(d11)) * 0.5f);
|
|
||||||
dVar.c((j27 + j25) - j24, ((float) g3.c.f(d11)) * 0.5f);
|
|
||||||
j24 += j26;
|
|
||||||
j20 = j25;
|
|
||||||
j21 = j26;
|
|
||||||
j23 = j27;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f605D = obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
public b(Context context) {
|
|
||||||
float[] fArr = new float[1024];
|
|
||||||
fArr[512] = 1.0f;
|
|
||||||
for (int i4 = 1; i4 < 512; i4++) {
|
|
||||||
double d4 = (i4 * 6.283185307179586d) / 1024;
|
|
||||||
float cos = (float) ((Math.cos(d4 * 2.0d) * 0.08d) + (0.42d - (Math.cos(d4) * 0.5d)));
|
|
||||||
fArr[i4] = cos;
|
|
||||||
double d5 = 0.003125f * 6.283185307179586d * (i4 - 512);
|
|
||||||
float sin = (float) ((Math.sin(d5) / d5) * cos);
|
|
||||||
fArr[i4] = sin;
|
|
||||||
fArr[1024 - i4] = sin;
|
|
||||||
}
|
|
||||||
this.f607a = fArr;
|
|
||||||
this.f608b = new float[1024];
|
|
||||||
this.c = new float[1024];
|
|
||||||
this.f609d = new float[f603B];
|
|
||||||
this.f610e = nativedecoder.TNativeDecoder.Create();
|
|
||||||
this.f614j = "";
|
|
||||||
this.f615k = new c(4000, 0);
|
|
||||||
I2.b bVar = new I2.b();
|
|
||||||
this.f617m = bVar;
|
|
||||||
this.f618n = new Handler(Looper.getMainLooper());
|
|
||||||
this.f622r = new AtomicInteger();
|
|
||||||
this.f623s = new AtomicBoolean(false);
|
|
||||||
this.f625u = 0;
|
|
||||||
this.f626v = new AtomicBoolean(false);
|
|
||||||
this.f628x = 0;
|
|
||||||
this.f624t = context;
|
|
||||||
bVar.b(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* JADX WARN: Removed duplicated region for block: B:57:0x01e2 */
|
|
||||||
/* JADX WARN: Removed duplicated region for block: B:63:0x020a A[LOOP:6: B:62:0x0208->B:63:0x020a, LOOP_END] */
|
|
||||||
/* JADX WARN: Removed duplicated region for block: B:66:0x0216 A[LOOP:7: B:65:0x0214->B:66:0x0216, LOOP_END] */
|
|
||||||
/* JADX WARN: Removed duplicated region for block: B:70:0x0232 */
|
|
||||||
/* JADX WARN: Removed duplicated region for block: B:73:? A[RETURN, SYNTHETIC] */
|
|
||||||
/*
|
|
||||||
Code decompiled incorrectly, please refer to instructions dump.
|
|
||||||
*/
|
|
||||||
public final void a(float[] fArr) {
|
|
||||||
c cVar;
|
|
||||||
float[] fArr2;
|
|
||||||
float[] fArr3;
|
|
||||||
b bVar;
|
|
||||||
Exception exc;
|
|
||||||
int i4;
|
|
||||||
float f;
|
|
||||||
int i5;
|
|
||||||
WaterfallSurfaceView waterfallSurfaceView;
|
|
||||||
int length = fArr.length;
|
|
||||||
int i6 = 0;
|
|
||||||
while (true) {
|
|
||||||
cVar = this.f615k;
|
|
||||||
if (i6 >= length) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
cVar.c(Math.abs(fArr[i6]));
|
|
||||||
i6++;
|
|
||||||
}
|
|
||||||
this.f616l = Math.round(((float) Math.log10(Math.max(-0.5d, cVar.f633e))) * 20.0f);
|
|
||||||
int i7 = f604C;
|
|
||||||
int i8 = 1024 - i7;
|
|
||||||
float[] fArr4 = this.f608b;
|
|
||||||
System.arraycopy(fArr4, i7, fArr4, 0, i8);
|
|
||||||
System.arraycopy(fArr, 0, fArr4, i8, i7);
|
|
||||||
int i9 = 0;
|
|
||||||
while (true) {
|
|
||||||
fArr2 = this.c;
|
|
||||||
if (i9 >= 1024) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
fArr2[i9] = fArr4[i9] * this.f607a[i9];
|
|
||||||
i9++;
|
|
||||||
}
|
|
||||||
f605D.k(fArr2, 0);
|
|
||||||
int i10 = f606z;
|
|
||||||
int i11 = i10;
|
|
||||||
while (true) {
|
|
||||||
int i12 = f602A;
|
|
||||||
float f4 = 0.0f;
|
|
||||||
fArr3 = this.f609d;
|
|
||||||
if (i11 < i12) {
|
|
||||||
int i13 = i11 * 2;
|
|
||||||
float f5 = fArr2[i13];
|
|
||||||
float f6 = fArr2[i13 + 1];
|
|
||||||
float f7 = ((f6 * f6) + (f5 * f5)) * 0.001f;
|
|
||||||
if (!Float.isNaN(f7)) {
|
|
||||||
f4 = f7;
|
|
||||||
}
|
|
||||||
fArr3[i11 - i10] = f4;
|
|
||||||
i11++;
|
|
||||||
} else {
|
|
||||||
// smali: no try inside the loop; the recording-write try sits after the loop (see below)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataOutputStream dataOutputStream = this.f627w;
|
|
||||||
Context context = this.f624t;
|
|
||||||
AtomicBoolean atomicBoolean = this.f626v;
|
|
||||||
try { // smali try_start_0: 录音写入整块(原 jadx 误把 try 放 break 处)
|
|
||||||
if (dataOutputStream == null && atomicBoolean.get()) {
|
|
||||||
File file = new File(context.getExternalFilesDir(null), ((String) DateFormat.format("yyyy-MM-dd_HH_mm_ss", new Date())) + "_spectra.bin");
|
|
||||||
this.f629y = file.getAbsoluteFile().toString();
|
|
||||||
this.f627w = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file, false)));
|
|
||||||
this.f628x = 0;
|
|
||||||
this.f614j = " Recording...";
|
|
||||||
}
|
|
||||||
if (this.f627w != null) {
|
|
||||||
for (float f8 : fArr3) {
|
|
||||||
this.f627w.writeFloat(f8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (atomicBoolean.get()) {
|
|
||||||
int i14 = this.f628x + 1;
|
|
||||||
this.f628x = i14;
|
|
||||||
if (i14 >= 2048) {
|
|
||||||
atomicBoolean.set(false);
|
|
||||||
this.f627w.close();
|
|
||||||
this.f627w = null;
|
|
||||||
this.f614j = "";
|
|
||||||
MediaScannerConnection.scanFile(context, new String[]{this.f629y}, new String[]{"application/x-binary"}, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e4) {
|
|
||||||
Log.e("File write", "Exception: ", e4);
|
|
||||||
}
|
|
||||||
nativedecoder.TNativeDecoder tNativeDecoder = this.f610e;
|
|
||||||
try {
|
|
||||||
this.f613i = tNativeDecoder.ProcessSpectrum(fArr3);
|
|
||||||
int i15 = this.f625u + 1;
|
|
||||||
this.f625u = i15;
|
|
||||||
Handler handler = this.f618n;
|
|
||||||
if (i15 == 15) {
|
|
||||||
try {
|
|
||||||
this.f625u = 0;
|
|
||||||
this.f = tNativeDecoder.getPitch();
|
|
||||||
this.f611g = tNativeDecoder.getSnr();
|
|
||||||
this.f612h = tNativeDecoder.getWpm();
|
|
||||||
handler.post(new B0.b(this, this.f614j, new int[]{this.f616l, this.f, this.f611g, this.f612h, tNativeDecoder.getSignalCount(), this.f613i.Value}));
|
|
||||||
AtomicInteger atomicInteger = this.f622r;
|
|
||||||
int i16 = atomicInteger.get();
|
|
||||||
if (i16 != 0) {
|
|
||||||
tNativeDecoder.LockToPitch(i16);
|
|
||||||
atomicInteger.set(0);
|
|
||||||
}
|
|
||||||
tNativeDecoder.setHamMode(this.f623s.get());
|
|
||||||
} catch (Exception e5) {
|
|
||||||
// jadx phantom zones (empty loops/ifs from misplaced catch reconstruction) trimmed; original smali: logs the exception, then jumps back to the normal flow tail
|
|
||||||
exc = e5;
|
|
||||||
bVar = this;
|
|
||||||
Log.e("Audio Processor", "Exception", exc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int charCount = tNativeDecoder.getCharCount();
|
|
||||||
if (charCount > 0) {
|
|
||||||
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
|
|
||||||
int i18 = 0;
|
|
||||||
while (i18 < charCount) {
|
|
||||||
decoder.TCharInfo GetChar = tNativeDecoder.GetChar(i18);
|
|
||||||
spannableStringBuilder.append(GetChar.getChar());
|
|
||||||
ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(this.f617m.a(GetChar));
|
|
||||||
int i19 = i18 + 1;
|
|
||||||
spannableStringBuilder.setSpan(foregroundColorSpan, i18, i19, 33);
|
|
||||||
i18 = i19;
|
|
||||||
}
|
|
||||||
bVar = this;
|
|
||||||
try {
|
|
||||||
handler.post(new RunnableC0001b(bVar, spannableStringBuilder, tNativeDecoder.getDeleteCount(), 2, false));
|
|
||||||
} catch (Exception e6) {
|
|
||||||
// jadx phantom zone trimmed (same catch e5); original smali: logs the exception, then jumps back to the normal flow tail
|
|
||||||
exc = e6;
|
|
||||||
Log.e("Audio Processor", "Exception", exc);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bVar = this;
|
|
||||||
}
|
|
||||||
} catch (Exception e7) {
|
|
||||||
// jadx phantom (the e variable) trimmed
|
|
||||||
bVar = this;
|
|
||||||
}
|
|
||||||
i4 = f603B;
|
|
||||||
int i1722 = i4 / 2;
|
|
||||||
float[] copyOfRange22 = Arrays.copyOfRange(fArr3, i1722 - 20, i1722 + 20);
|
|
||||||
Arrays.sort(copyOfRange22);
|
|
||||||
f = copyOfRange22[20];
|
|
||||||
if (f > 0.0f) {
|
|
||||||
for (int i20 = 0; i20 < i4; i20++) {
|
|
||||||
fArr3[i20] = fArr3[i20] / f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
float[] fArr522 = (float[]) fArr3.clone();
|
|
||||||
new c(12, 1).a(fArr522);
|
|
||||||
new c(12, 0).a(fArr522);
|
|
||||||
for (i5 = 0; i5 < i4; i5++) {
|
|
||||||
fArr3[i5] = fArr3[i5] - fArr522[i5];
|
|
||||||
}
|
|
||||||
for (int i21 = 0; i21 < i4; i21++) {
|
|
||||||
fArr3[i21] = ((float) Math.log10(Math.max(-0.5d, fArr3[i21]) + 1.0d)) * 10.0f;
|
|
||||||
}
|
|
||||||
waterfallSurfaceView = bVar.f619o;
|
|
||||||
if (waterfallSurfaceView != null) {
|
|
||||||
waterfallSurfaceView.queueEvent(new n(waterfallSurfaceView, 1, (float[]) fArr3.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
package H2;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public final class c {
|
|
||||||
|
|
||||||
/* renamed from: a, reason: collision with root package name */
|
|
||||||
public final int f630a;
|
|
||||||
|
|
||||||
/* renamed from: b, reason: collision with root package name */
|
|
||||||
public float[] f631b;
|
|
||||||
public int c;
|
|
||||||
|
|
||||||
/* renamed from: d, reason: collision with root package name */
|
|
||||||
public float f632d;
|
|
||||||
|
|
||||||
/* renamed from: e, reason: collision with root package name */
|
|
||||||
public float f633e;
|
|
||||||
public final /* synthetic */ int f;
|
|
||||||
|
|
||||||
public c(int i4, int i5) {
|
|
||||||
this.f = i5;
|
|
||||||
this.f630a = i4 - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void a(float[] fArr) {
|
|
||||||
int i4 = this.f630a / 2;
|
|
||||||
int length = fArr.length;
|
|
||||||
if (length > i4) {
|
|
||||||
this.f631b = null;
|
|
||||||
for (int i5 = 0; i5 < i4; i5++) {
|
|
||||||
c(fArr[i5]);
|
|
||||||
}
|
|
||||||
for (int i6 = i4; i6 < length; i6++) {
|
|
||||||
fArr[i6 - i4] = c(fArr[i6]);
|
|
||||||
}
|
|
||||||
for (int i7 = length; i7 < length + i4; i7++) {
|
|
||||||
fArr[i7 - i4] = c(fArr[length - 1]);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("Sliding Filter error: input too short");
|
|
||||||
}
|
|
||||||
|
|
||||||
public final float b(float f, float f4) {
|
|
||||||
switch (this.f) {
|
|
||||||
case 0:
|
|
||||||
return Math.max(f, f4);
|
|
||||||
default:
|
|
||||||
return Math.min(f, f4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public final float c(float f) {
|
|
||||||
float[] fArr = this.f631b;
|
|
||||||
int i4 = this.f630a;
|
|
||||||
if (fArr == null) {
|
|
||||||
this.f631b = new float[i4];
|
|
||||||
for (int i5 = 0; i5 < i4; i5++) {
|
|
||||||
this.f631b[i5] = f;
|
|
||||||
}
|
|
||||||
this.f632d = f;
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
float b4 = b(this.f632d, f);
|
|
||||||
this.f632d = b4;
|
|
||||||
this.f633e = b(this.f631b[this.c], b4);
|
|
||||||
float[] fArr2 = this.f631b;
|
|
||||||
int i6 = this.c;
|
|
||||||
int i7 = i6 + 1;
|
|
||||||
this.c = i7;
|
|
||||||
fArr2[i6] = f;
|
|
||||||
if (i7 == i4) {
|
|
||||||
int length = fArr2.length;
|
|
||||||
while (true) {
|
|
||||||
this.c = length - 1;
|
|
||||||
int i8 = this.c;
|
|
||||||
if (i8 <= 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
float[] fArr3 = this.f631b;
|
|
||||||
fArr3[i8 - 1] = b(fArr3[i8 - 1], fArr3[i8]);
|
|
||||||
length = this.c;
|
|
||||||
}
|
|
||||||
this.c = 0;
|
|
||||||
this.f632d = f;
|
|
||||||
}
|
|
||||||
return this.f633e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package I0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ported from Morse Expert 1.15 I0.d; obfuscated class name kept.
|
|
||||||
* R8-merged class surgery: keeps only the (i3.d, int, int, float[]) FFT task constructor;
|
|
||||||
* run() restored verbatim per smali: R8 already optimized the body into dead code (aget then throw null,
|
|
||||||
* never executed); original semantics kept (behavior identical to the original APK).
|
|
||||||
* The SystemForegroundService (work library) case was trimmed.
|
|
||||||
*/
|
|
||||||
public final class d implements Runnable {
|
|
||||||
public final /* synthetic */ int f = 0;
|
|
||||||
|
|
||||||
/* renamed from: g, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ int f653g;
|
|
||||||
|
|
||||||
/* renamed from: h, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ int f654h;
|
|
||||||
|
|
||||||
/* renamed from: i, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ Object f655i;
|
|
||||||
|
|
||||||
/* renamed from: j, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ Object f656j;
|
|
||||||
|
|
||||||
public d(i3.d dVar, int i4, int i5, float[] fArr) {
|
|
||||||
this.f656j = dVar;
|
|
||||||
this.f653g = i4;
|
|
||||||
this.f654h = i5;
|
|
||||||
this.f655i = fArr;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // java.lang.Runnable
|
|
||||||
public final void run() {
|
|
||||||
int i6 = this.f654h;
|
|
||||||
int i7 = this.f653g;
|
|
||||||
if (i7 >= i6) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
float f = ((float[]) this.f655i)[i7 * 2];
|
|
||||||
// Original R8 code: aget then throw null (dead code, never runs) - semantics ported, no computation executes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
package I2;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import pas.decoder;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public final class b {
|
|
||||||
|
|
||||||
/* renamed from: b, reason: collision with root package name */
|
|
||||||
public static final String[] f663b = {"bg_color", "text_color", "text_color_weak", "call_color", "call_color_weak", "cq_color", "cq_color_weak", "rst_color", "rst_color_weak"};
|
|
||||||
public static final String[] c = {"Background color", "Text color", "Text color, weak", "Callsign color", "Callsign color, weak", "CQ color", "CQ color, weak", "RST color", "RST color, weak"};
|
|
||||||
|
|
||||||
/* renamed from: d, reason: collision with root package name */
|
|
||||||
public static final int[] f664d = {-3084048, -16777216, -5592406, -65536, -30584, -16776961, -7829249, -65281, -30465};
|
|
||||||
|
|
||||||
/* renamed from: a, reason: collision with root package name */
|
|
||||||
public final int[] f665a = (int[]) f664d.clone();
|
|
||||||
|
|
||||||
public final int a(decoder.TCharInfo tCharInfo) {
|
|
||||||
boolean lowSnr = tCharInfo.getLowSnr();
|
|
||||||
int[] iArr = this.f665a;
|
|
||||||
if (lowSnr) {
|
|
||||||
int i4 = tCharInfo.getWordType().Value;
|
|
||||||
if (i4 != 0) {
|
|
||||||
if (i4 != 1) {
|
|
||||||
if (i4 != 2) {
|
|
||||||
if (i4 != 3) {
|
|
||||||
if (i4 != 4) {
|
|
||||||
if (i4 == 5) {
|
|
||||||
return iArr[4];
|
|
||||||
}
|
|
||||||
return -16777216;
|
|
||||||
}
|
|
||||||
return iArr[2];
|
|
||||||
}
|
|
||||||
return iArr[6];
|
|
||||||
}
|
|
||||||
return iArr[8];
|
|
||||||
}
|
|
||||||
return iArr[2];
|
|
||||||
}
|
|
||||||
return iArr[2];
|
|
||||||
}
|
|
||||||
int i5 = tCharInfo.getWordType().Value;
|
|
||||||
if (i5 != 0) {
|
|
||||||
if (i5 != 1) {
|
|
||||||
if (i5 != 2) {
|
|
||||||
if (i5 != 3) {
|
|
||||||
if (i5 != 4) {
|
|
||||||
if (i5 != 5) {
|
|
||||||
return -16777216;
|
|
||||||
}
|
|
||||||
return iArr[3];
|
|
||||||
}
|
|
||||||
return iArr[1];
|
|
||||||
}
|
|
||||||
return iArr[5];
|
|
||||||
}
|
|
||||||
return iArr[7];
|
|
||||||
}
|
|
||||||
return iArr[1];
|
|
||||||
}
|
|
||||||
return iArr[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void b(Context context) {
|
|
||||||
SharedPreferences sharedPreferences = context.getSharedPreferences(context.getPackageName() + "_preferences", 0); // 原 c0.x.a(context)
|
|
||||||
for (int i4 = 0; i4 < 9; i4++) {
|
|
||||||
this.f665a[i4] = sharedPreferences.getInt(f663b[i4], f664d[i4]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
package J2;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public final class a {
|
|
||||||
|
|
||||||
/* renamed from: b, reason: collision with root package name */
|
|
||||||
public static final int[] f726b = {16, 17749, 65535, 16776960, 16711680};
|
|
||||||
|
|
||||||
/* renamed from: a, reason: collision with root package name */
|
|
||||||
public final int[] f727a;
|
|
||||||
|
|
||||||
public a() {
|
|
||||||
int[] iArr = new int[256];
|
|
||||||
this.f727a = iArr;
|
|
||||||
int i4 = 4;
|
|
||||||
float f = 255.0f / 4;
|
|
||||||
int[] iArr2 = f726b;
|
|
||||||
int i5 = 0;
|
|
||||||
int i6 = -16777216;
|
|
||||||
iArr[0] = iArr2[0] | (-16777216);
|
|
||||||
int i7 = 1;
|
|
||||||
while (i7 <= i4) {
|
|
||||||
int round = Math.round(i7 * f);
|
|
||||||
iArr[round] = iArr2[i7] | i6;
|
|
||||||
int i8 = round - i5;
|
|
||||||
int i9 = iArr[i5];
|
|
||||||
int i10 = (i9 >> 16) & 255;
|
|
||||||
int i11 = (i9 >> 8) & 255;
|
|
||||||
int i12 = i9 & 255;
|
|
||||||
float f4 = i8;
|
|
||||||
// r10 = iArr2[i7] (next anchor color, smali: aget v10, v4, v8)
|
|
||||||
float f5 = (((iArr2[i7] >> 16) & 255) - i10) / f4;
|
|
||||||
float f6 = (((iArr2[i7] >> 8) & 255) - i11) / f4;
|
|
||||||
float f7 = ((iArr2[i7] & 255) - i12) / f4;
|
|
||||||
for (int i13 = 1; i13 < i8; i13++) {
|
|
||||||
float f8 = i13;
|
|
||||||
iArr[i5 + i13] = ((Math.round(f5 * f8) + i10) << 16) | (-16777216) | ((Math.round(f6 * f8) + i11) << 8) | (Math.round(f8 * f7) + i12);
|
|
||||||
}
|
|
||||||
i7++;
|
|
||||||
i5 = round;
|
|
||||||
i4 = 4;
|
|
||||||
i6 = -16777216;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
package J2;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.opengl.GLES20;
|
|
||||||
import android.opengl.GLSurfaceView;
|
|
||||||
import android.opengl.GLUtils;
|
|
||||||
import android.os.SystemClock;
|
|
||||||
import android.util.Log;
|
|
||||||
import java.nio.Buffer;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.ByteOrder;
|
|
||||||
import java.nio.FloatBuffer;
|
|
||||||
import javax.microedition.khronos.egl.EGLConfig;
|
|
||||||
import javax.microedition.khronos.opengles.GL10;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public final class b implements GLSurfaceView.Renderer {
|
|
||||||
|
|
||||||
/* renamed from: b, reason: collision with root package name */
|
|
||||||
public FloatBuffer f729b;
|
|
||||||
public int c;
|
|
||||||
|
|
||||||
/* renamed from: d, reason: collision with root package name */
|
|
||||||
public final Bitmap f730d;
|
|
||||||
public final Context f;
|
|
||||||
|
|
||||||
/* renamed from: g, reason: collision with root package name */
|
|
||||||
public double f732g;
|
|
||||||
|
|
||||||
/* renamed from: h, reason: collision with root package name */
|
|
||||||
public final Bitmap f733h;
|
|
||||||
|
|
||||||
/* renamed from: i, reason: collision with root package name */
|
|
||||||
public final a f734i;
|
|
||||||
|
|
||||||
/* renamed from: j, reason: collision with root package name */
|
|
||||||
public long f735j;
|
|
||||||
|
|
||||||
/* renamed from: k, reason: collision with root package name */
|
|
||||||
public double f736k;
|
|
||||||
|
|
||||||
/* renamed from: l, reason: collision with root package name */
|
|
||||||
public int f737l;
|
|
||||||
|
|
||||||
/* renamed from: a, reason: collision with root package name */
|
|
||||||
public final int[] f728a = new int[1];
|
|
||||||
|
|
||||||
/* renamed from: e, reason: collision with root package name */
|
|
||||||
public final float[] f731e = {-1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f};
|
|
||||||
|
|
||||||
public b(Context context, int i4) {
|
|
||||||
Bitmap.Config config = Bitmap.Config.ARGB_8888;
|
|
||||||
Bitmap createBitmap = Bitmap.createBitmap(2048, i4, config);
|
|
||||||
this.f730d = createBitmap;
|
|
||||||
createBitmap.eraseColor(16);
|
|
||||||
this.f732g = 0.09375d;
|
|
||||||
this.f734i = new a();
|
|
||||||
this.f735j = 0L;
|
|
||||||
this.f736k = 0.0d;
|
|
||||||
this.f737l = -1;
|
|
||||||
this.f = context;
|
|
||||||
this.f733h = Bitmap.createBitmap(1, i4, config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void a() {
|
|
||||||
int glGetError = GLES20.glGetError();
|
|
||||||
if (glGetError != 0) {
|
|
||||||
StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[3];
|
|
||||||
Log.e(stackTraceElement.getFileName() + ":" + stackTraceElement.getLineNumber(), ", GLES20 error: " + glGetError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public final double b() {
|
|
||||||
long uptimeMillis = SystemClock.uptimeMillis();
|
|
||||||
double width = (((uptimeMillis - this.f735j) * this.f732g) + this.f736k) % this.f730d.getWidth();
|
|
||||||
this.f735j = uptimeMillis;
|
|
||||||
this.f736k = width;
|
|
||||||
return width;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void c(GL10 gl10) {
|
|
||||||
GLES20.glUseProgram(this.c);
|
|
||||||
a();
|
|
||||||
GLES20.glActiveTexture(33984);
|
|
||||||
a();
|
|
||||||
GLES20.glBindTexture(3553, this.f728a[0]);
|
|
||||||
a();
|
|
||||||
GLES20.glUniform1i(GLES20.glGetUniformLocation(this.c, "uTexture"), 0);
|
|
||||||
a();
|
|
||||||
int glGetAttribLocation = GLES20.glGetAttribLocation(this.c, "aPosition");
|
|
||||||
a();
|
|
||||||
int glGetAttribLocation2 = GLES20.glGetAttribLocation(this.c, "aTexPos");
|
|
||||||
a();
|
|
||||||
this.f729b.position(0);
|
|
||||||
a();
|
|
||||||
GLES20.glVertexAttribPointer(glGetAttribLocation, 2, 5126, false, 16, (Buffer) this.f729b);
|
|
||||||
a();
|
|
||||||
GLES20.glEnableVertexAttribArray(glGetAttribLocation);
|
|
||||||
a();
|
|
||||||
this.f729b.position(2);
|
|
||||||
a();
|
|
||||||
GLES20.glVertexAttribPointer(glGetAttribLocation2, 2, 5126, false, 16, (Buffer) this.f729b);
|
|
||||||
a();
|
|
||||||
GLES20.glEnableVertexAttribArray(glGetAttribLocation2);
|
|
||||||
a();
|
|
||||||
GLES20.glDrawArrays(5, 0, 4);
|
|
||||||
a();
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void d(GL10 gl10, int i4, int i5) {
|
|
||||||
GLES20.glViewport(0, 0, i4, i5);
|
|
||||||
a();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // android.opengl.GLSurfaceView.Renderer
|
|
||||||
public final void onDrawFrame(GL10 gl10) {
|
|
||||||
float b4 = (float) (b() / this.f730d.getWidth());
|
|
||||||
float f = 0.5f + b4;
|
|
||||||
float[] fArr = this.f731e;
|
|
||||||
fArr[6] = f;
|
|
||||||
fArr[2] = f;
|
|
||||||
float f4 = b4 + 1.0f;
|
|
||||||
fArr[14] = f4;
|
|
||||||
fArr[10] = f4;
|
|
||||||
this.f729b.position(0);
|
|
||||||
this.f729b.put(this.f731e);
|
|
||||||
c(gl10);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // android.opengl.GLSurfaceView.Renderer
|
|
||||||
public final void onSurfaceChanged(GL10 gl10, int i4, int i5) {
|
|
||||||
d(gl10, i4, i5);
|
|
||||||
float width = 1.0f - (((this.f730d.getWidth() * 0.7f) / (i4 / this.f.getResources().getDisplayMetrics().density)) * 2.0f);
|
|
||||||
float[] fArr = this.f731e;
|
|
||||||
fArr[4] = width;
|
|
||||||
fArr[0] = width;
|
|
||||||
this.f729b.position(0);
|
|
||||||
this.f729b.put(this.f731e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // android.opengl.GLSurfaceView.Renderer
|
|
||||||
public final void onSurfaceCreated(GL10 gl10, EGLConfig eGLConfig) {
|
|
||||||
int glCreateShader = GLES20.glCreateShader(35633);
|
|
||||||
a();
|
|
||||||
GLES20.glShaderSource(glCreateShader, "attribute vec2 aPosition;\nattribute vec2 aTexPos;\nvarying vec2 vTexPos;\nvoid main() {\n vTexPos = aTexPos;\n gl_Position = vec4(aPosition.xy, 0.0, 1.0);\n}");
|
|
||||||
a();
|
|
||||||
GLES20.glCompileShader(glCreateShader);
|
|
||||||
a();
|
|
||||||
int glCreateShader2 = GLES20.glCreateShader(35632);
|
|
||||||
a();
|
|
||||||
GLES20.glShaderSource(glCreateShader2, "precision mediump float;\nuniform sampler2D uTexture;\nvarying vec2 vTexPos;\nvoid main(void)\n{\n gl_FragColor = texture2D(uTexture, vTexPos);\n}");
|
|
||||||
a();
|
|
||||||
GLES20.glCompileShader(glCreateShader2);
|
|
||||||
a();
|
|
||||||
this.c = GLES20.glCreateProgram();
|
|
||||||
a();
|
|
||||||
GLES20.glAttachShader(this.c, glCreateShader);
|
|
||||||
a();
|
|
||||||
GLES20.glAttachShader(this.c, glCreateShader2);
|
|
||||||
a();
|
|
||||||
GLES20.glLinkProgram(this.c);
|
|
||||||
a();
|
|
||||||
int[] iArr = this.f728a;
|
|
||||||
GLES20.glGenTextures(1, iArr, 0);
|
|
||||||
a();
|
|
||||||
GLES20.glBindTexture(3553, iArr[0]);
|
|
||||||
a();
|
|
||||||
GLES20.glTexParameteri(3553, 10240, 9729);
|
|
||||||
GLES20.glTexParameteri(3553, 10242, 10497);
|
|
||||||
GLES20.glTexParameteri(3553, 10241, 9729);
|
|
||||||
GLES20.glTexParameteri(3553, 10243, 10497);
|
|
||||||
a();
|
|
||||||
GLUtils.texImage2D(3553, 0, this.f730d, 0);
|
|
||||||
a();
|
|
||||||
float[] fArr = this.f731e;
|
|
||||||
ByteBuffer allocateDirect = ByteBuffer.allocateDirect(fArr.length * 4);
|
|
||||||
allocateDirect.order(ByteOrder.nativeOrder());
|
|
||||||
FloatBuffer asFloatBuffer = allocateDirect.asFloatBuffer();
|
|
||||||
this.f729b = asFloatBuffer;
|
|
||||||
asFloatBuffer.put(fArr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package K1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ported from Morse Expert 1.15 K1.a (slimmed): keeps only the memory-accounting static fields the k3 FFT library references
|
|
||||||
* (K1.a.c long accounting). The rest of the original K1.a (828 lines) was status-bar/library code and was trimmed
|
|
||||||
* (status-bar logic is inlined in MainActivity).
|
|
||||||
*/
|
|
||||||
public class a {
|
|
||||||
public static long c = 0;
|
|
||||||
}
|
|
||||||
@@ -1,32 +1,35 @@
|
|||||||
/*
|
/*
|
||||||
* CwDecodeScreen.kt - Look4Sat CW decode page (Compose shell).
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
|
* Copyright (C) 2019-2026 Arty Bishop and contributors.
|
||||||
*
|
*
|
||||||
* Ported from Morse Expert 1.15 com.ve3nea.morse_expert.MainActivity (converted to a plain controller class):
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* - AndroidView embeds the original activity_main.xml (ConstraintLayout root, with decodedTextView/scaleView/
|
* it under the terms of the GNU General Public License as published by
|
||||||
* statusTextView/verticalLayout/waterfallView);
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* - Lifecycle onCreate/onResume/onPause/onDestroy fully delegated to the controller, same order as the original Activity
|
* (at your option) any later version.
|
||||||
* (onCreate must run before onResume; onDispose runs onPause then onDestroy);
|
|
||||||
* - Mic permission (RECORD_AUDIO) is managed on the Compose side; onPermissionGranted() (= v()) is called once granted,
|
|
||||||
* an initialized flag guarantees it runs only once (v() news an a() which overwrites f11036E);
|
|
||||||
* - The original options_menu Clear/Pause/Save/Record/Settings became a top button row (icons from the module's original drawables).
|
|
||||||
*
|
*
|
||||||
* Note: setImmersive(window, false) inside the controller's onCreate restores the host window to
|
* This program is distributed in the hope that it will be useful,
|
||||||
* decorFitsSystemWindows(true) (ported original behavior; affects the whole host Activity).
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.feature.cw
|
package com.rtbishop.look4sat.feature.cw
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.media.AudioFormat
|
||||||
|
import android.media.AudioRecord
|
||||||
|
import android.media.MediaRecorder
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.view.LayoutInflater
|
|
||||||
import androidx.activity.compose.BackHandler
|
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -36,43 +39,60 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.systemBarsPadding
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.geometry.Size
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import com.rtbishop.look4sat.core.domain.cw.CwFldigiDecoder
|
||||||
import com.ve3nea.morse_expert.MainActivity
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CW decode page — pure Compose, fldigi decoding engine.
|
||||||
|
*
|
||||||
|
* Layout mirrors the DeepCW web decoder (waterfall -> live line -> history)
|
||||||
|
* inside the existing container; top-bar actions (back/settings) and bottom
|
||||||
|
* actions (start-pause/clear) keep the previous page's interaction model.
|
||||||
|
* The old Morse Expert (ve3nea) controller and layouts are fully removed.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun CwDecodeScreen(navigateUp: () -> Unit = {}) {
|
fun CwDecodeScreen(navigateUp: () -> Unit = {}) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val activity = remember(context) {
|
val scope = rememberCoroutineScope()
|
||||||
context as? Activity ?: error("CwDecodeScreen must be hosted in an Activity")
|
val decoder = remember { CwFldigiDecoder(initialWpm = 18) }
|
||||||
}
|
val waterfall = remember { CwWaterfallState() }
|
||||||
// Ported controller (plain class, not an Activity); new instance per page entry
|
|
||||||
val controller = remember { MainActivity() }
|
|
||||||
// Inflate the original layout early so AndroidView and the controller's onCreate share one root view
|
|
||||||
val rootView = remember(context) {
|
|
||||||
LayoutInflater.from(context).inflate(R.layout.activity_main, null) as ConstraintLayout
|
|
||||||
}
|
|
||||||
|
|
||||||
var permissionGranted by remember {
|
var permissionGranted by remember {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
@@ -81,46 +101,50 @@ fun CwDecodeScreen(navigateUp: () -> Unit = {}) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
var permanentlyDenied by remember { mutableStateOf(false) }
|
var permanentlyDenied by remember { mutableStateOf(false) }
|
||||||
// v() news an a() which overwrites f11036E; must run exactly once
|
var isListening by remember { mutableStateOf(false) }
|
||||||
var initialized by remember { mutableStateOf(false) }
|
|
||||||
var showSettings by remember { mutableStateOf(false) }
|
var showSettings by remember { mutableStateOf(false) }
|
||||||
|
var captureJob by remember { mutableStateOf<Job?>(null) }
|
||||||
|
|
||||||
// Lifecycle: onCreate before onResume; onDispose runs onPause then onDestroy
|
val decodedText by decoder.decodedTextFlow.collectAsState()
|
||||||
DisposableEffect(Unit) {
|
val signalStrength by decoder.signalStrength.collectAsState()
|
||||||
controller.onCreate(activity, rootView)
|
val estimatedSpeed by decoder.estimatedSpeed.collectAsState()
|
||||||
controller.onResume()
|
|
||||||
onDispose {
|
|
||||||
controller.onPause()
|
|
||||||
controller.onDestroy()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the decode core after permission granted; both "already granted on entry" and "granted via dialog" go here
|
|
||||||
LaunchedEffect(permissionGranted) {
|
|
||||||
if (permissionGranted && !initialized) {
|
|
||||||
controller.onPermissionGranted() // = v(): 创建音频采集 + 解码核心
|
|
||||||
// v() only creates the core; the page is already resumed, so run onResume again to start recording immediately
|
|
||||||
// (AudioRecord is freshly created so startRecording won't double-start; GLSurfaceView.onResume is idempotent)
|
|
||||||
controller.onResume()
|
|
||||||
initialized = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val permissionLauncher = rememberLauncherForActivityResult(
|
val permissionLauncher = rememberLauncherForActivityResult(
|
||||||
ActivityResultContracts.RequestPermission()
|
ActivityResultContracts.RequestPermission()
|
||||||
) { granted ->
|
) { granted ->
|
||||||
permissionGranted = granted
|
permissionGranted = granted
|
||||||
permanentlyDenied = !granted &&
|
permanentlyDenied = !granted && (
|
||||||
!activity.shouldShowRequestPermissionRationale(Manifest.permission.RECORD_AUDIO)
|
(context as? android.app.Activity)?.shouldShowRequestPermissionRationale(
|
||||||
|
Manifest.permission.RECORD_AUDIO
|
||||||
|
) ?: false
|
||||||
|
).not()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Original tap_back_again_to_close: single Back shows a hint; press again within 2 s to exit
|
fun startCapture() {
|
||||||
BackHandler {
|
if (!permissionGranted) {
|
||||||
if (!controller.handleBackPress()) navigateUp()
|
permissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
captureJob?.cancel()
|
||||||
|
captureJob = scope.launch(Dispatchers.IO) {
|
||||||
|
val capture = CwMicCapture()
|
||||||
|
capture.audioFlow().collect { chunk ->
|
||||||
|
if (!isActive) return@collect
|
||||||
|
decoder.processBuffer(chunk)
|
||||||
|
waterfall.pushSamples(chunk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isListening = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showSettings) {
|
fun stopCapture() {
|
||||||
CwSettingsDialog(controller = controller, onDismiss = { showSettings = false })
|
captureJob?.cancel()
|
||||||
|
captureJob = null
|
||||||
|
isListening = false
|
||||||
|
}
|
||||||
|
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
onDispose { stopCapture() }
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
@@ -128,86 +152,350 @@ fun CwDecodeScreen(navigateUp: () -> Unit = {}) {
|
|||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.systemBarsPadding()
|
.systemBarsPadding()
|
||||||
) {
|
) {
|
||||||
// Original options_menu: Pause/Clear/Save (original icons) + Record/Settings (text buttons)
|
// Top bar
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 4.dp),
|
.padding(horizontal = 4.dp, vertical = 2.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly
|
|
||||||
) {
|
) {
|
||||||
IconButton(onClick = { controller.togglePause() }) {
|
IconButton(onClick = { stopCapture(); navigateUp() }) {
|
||||||
Icon(
|
Icon(painter = androidx.compose.ui.res.painterResource(com.rtbishop.look4sat.core.presentation.R.drawable.ic_back), contentDescription = "Back")
|
||||||
painter = painterResource(R.drawable.ic_baseline_pause_24),
|
|
||||||
contentDescription = stringResource(R.string.pause)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
IconButton(onClick = { controller.clearDecoded() }) {
|
Text(
|
||||||
Icon(
|
text = "CW Decoder",
|
||||||
painter = painterResource(R.drawable.ic_baseline_delete_24),
|
style = MaterialTheme.typography.titleMedium,
|
||||||
contentDescription = stringResource(R.string.clear)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
}
|
IconButton(onClick = { showSettings = true }) {
|
||||||
IconButton(onClick = { controller.saveText() }) {
|
Icon(painter = androidx.compose.ui.res.painterResource(com.rtbishop.look4sat.core.presentation.R.drawable.ic_cw), contentDescription = "Settings")
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.ic_baseline_save_24),
|
|
||||||
contentDescription = stringResource(R.string.save_text)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// recordSignals needs the decode core (f11037F); disabled until initialized
|
|
||||||
TextButton(
|
|
||||||
onClick = { if (initialized) controller.recordSignals() },
|
|
||||||
enabled = initialized
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.record_signals))
|
|
||||||
}
|
|
||||||
TextButton(onClick = { showSettings = true }) {
|
|
||||||
Text(stringResource(R.string.settings))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(modifier = Modifier.weight(1f).fillMaxWidth()) {
|
// Waterfall
|
||||||
AndroidView(
|
Box(
|
||||||
factory = { rootView },
|
modifier = Modifier
|
||||||
modifier = Modifier.fillMaxSize()
|
.fillMaxWidth()
|
||||||
|
.height(150.dp)
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
) {
|
||||||
|
CwWaterfallView(state = waterfall)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Live decode line
|
||||||
|
Text(
|
||||||
|
text = decodedText.takeLast(80),
|
||||||
|
fontSize = 18.sp,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
maxLines = 1,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 12.dp, vertical = 4.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
// History
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.border(1.dp, MaterialTheme.colorScheme.outlineVariant, RoundedCornerShape(8.dp))
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.4f))
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = decodedText,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(8.dp),
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
)
|
)
|
||||||
if (!permissionGranted) {
|
}
|
||||||
// Mic permission not granted: overlay + hint text + request button
|
|
||||||
Box(
|
// Status cards
|
||||||
modifier = Modifier
|
Row(
|
||||||
.fillMaxSize()
|
modifier = Modifier
|
||||||
.background(Color(0x99000000)),
|
.fillMaxWidth()
|
||||||
contentAlignment = Alignment.Center
|
.padding(horizontal = 8.dp, vertical = 6.dp),
|
||||||
) {
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
) {
|
||||||
Text(
|
StatusCard("DECODER", if (isListening) "ACTIVE" else "IDLE",
|
||||||
text = stringResource(R.string.cw_mic_permission),
|
if (isListening) Color(0xFF4CAF50) else MaterialTheme.colorScheme.outline,
|
||||||
color = Color.White,
|
Modifier.weight(1f))
|
||||||
textAlign = TextAlign.Center,
|
StatusCard("SPEED", if (estimatedSpeed != null) "${estimatedSpeed?.roundToInt()} WPM" else "--",
|
||||||
modifier = Modifier.padding(horizontal = 24.dp)
|
MaterialTheme.colorScheme.primary, Modifier.weight(1f))
|
||||||
|
StatusCard("LEVEL", "${(signalStrength * 100).roundToInt()}%",
|
||||||
|
MaterialTheme.colorScheme.tertiary, Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bottom actions
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 8.dp, vertical = 6.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = { if (isListening) stopCapture() else startCapture() },
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = if (isListening) androidx.compose.ui.res.painterResource(com.rtbishop.look4sat.core.presentation.R.drawable.ic_close)
|
||||||
|
else androidx.compose.ui.res.painterResource(com.rtbishop.look4sat.core.presentation.R.drawable.ic_arrow),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(18.dp)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.size(4.dp))
|
||||||
|
Text(if (isListening) "Pause" else "Start")
|
||||||
|
}
|
||||||
|
Button(
|
||||||
|
onClick = { decoder.resetDecoder(); waterfall.reset() },
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
Icon(painter = androidx.compose.ui.res.painterResource(com.rtbishop.look4sat.core.presentation.R.drawable.ic_delete), contentDescription = null, modifier = Modifier.size(18.dp))
|
||||||
|
Spacer(Modifier.size(4.dp))
|
||||||
|
Text("Clear")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showSettings) {
|
||||||
|
CwSettingsDialog(
|
||||||
|
onDismiss = { showSettings = false },
|
||||||
|
currentWpm = estimatedSpeed?.roundToInt() ?: 18
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!permissionGranted) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(Color(0x99000000)),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Text(
|
||||||
|
text = "Microphone permission is required for CW decoding",
|
||||||
|
color = Color.White,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.padding(horizontal = 24.dp)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(12.dp))
|
||||||
|
Button(onClick = { permissionLauncher.launch(Manifest.permission.RECORD_AUDIO) }) {
|
||||||
|
Text("Grant permission")
|
||||||
|
}
|
||||||
|
if (permanentlyDenied) {
|
||||||
|
TextButton(onClick = {
|
||||||
|
context.startActivity(
|
||||||
|
Intent(
|
||||||
|
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
|
||||||
|
Uri.parse("package:${context.packageName}")
|
||||||
|
)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
}) {
|
||||||
Button(onClick = {
|
Text("Open app settings")
|
||||||
permissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
|
|
||||||
}) {
|
|
||||||
Text(stringResource(R.string.cw_grant_permission))
|
|
||||||
}
|
|
||||||
if (permanentlyDenied) {
|
|
||||||
// "Never ask again" checked: guide user to system settings
|
|
||||||
TextButton(onClick = {
|
|
||||||
activity.startActivity(
|
|
||||||
Intent(
|
|
||||||
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
|
|
||||||
Uri.parse("package:${activity.packageName}")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}) {
|
|
||||||
Text(stringResource(R.string.cw_open_settings))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun StatusCard(label: String, value: String, accent: Color, modifier: Modifier = Modifier) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.border(1.dp, accent.copy(alpha = 0.6f), RoundedCornerShape(8.dp))
|
||||||
|
.padding(horizontal = 8.dp, vertical = 6.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Text(text = label, fontSize = 10.sp, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||||
|
Text(
|
||||||
|
text = value,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = accent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Microphone capture at 8000 Hz mono PCM float (fldigi sample rate).
|
||||||
|
*/
|
||||||
|
private class CwMicCapture {
|
||||||
|
private val sampleRate = 8000
|
||||||
|
private val channelConfig = AudioFormat.CHANNEL_IN_MONO
|
||||||
|
private val audioFormat = AudioFormat.ENCODING_PCM_FLOAT
|
||||||
|
|
||||||
|
fun audioFlow(): kotlinx.coroutines.flow.Flow<FloatArray> =
|
||||||
|
kotlinx.coroutines.flow.flow {
|
||||||
|
val bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)
|
||||||
|
.coerceAtLeast(sampleRate / 5)
|
||||||
|
val recorder = AudioRecord(
|
||||||
|
MediaRecorder.AudioSource.MIC,
|
||||||
|
sampleRate,
|
||||||
|
channelConfig,
|
||||||
|
audioFormat,
|
||||||
|
bufferSize * 4
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
recorder.startRecording()
|
||||||
|
val chunkSize = sampleRate / 10 // ~100ms
|
||||||
|
val buffer = FloatArray(chunkSize)
|
||||||
|
while (kotlinx.coroutines.currentCoroutineContext().isActive) {
|
||||||
|
val read = recorder.read(buffer, 0, chunkSize, AudioRecord.READ_BLOCKING)
|
||||||
|
if (read > 0) {
|
||||||
|
emit(if (read == chunkSize) buffer.copyOf() else buffer.copyOfRange(0, read))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
recorder.stop()
|
||||||
|
recorder.release()
|
||||||
|
}
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Waterfall (lightweight sliding spectrogram) ---
|
||||||
|
|
||||||
|
class CwWaterfallState {
|
||||||
|
private val columns = 160
|
||||||
|
private val rows = 64
|
||||||
|
private val data = FloatArray(columns * rows)
|
||||||
|
private var colCount = 0
|
||||||
|
private val pending = ArrayList<Float>(4096)
|
||||||
|
private val fftWindow = 512
|
||||||
|
|
||||||
|
/** Monotonic frame counter — reading this in composition triggers redraw. */
|
||||||
|
private val _version = androidx.compose.runtime.mutableIntStateOf(0)
|
||||||
|
val version: androidx.compose.runtime.State<Int> = _version
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun pushSamples(samples: FloatArray) {
|
||||||
|
pending.addAll(samples.toList())
|
||||||
|
var frames = 0
|
||||||
|
while (pending.size >= fftWindow) {
|
||||||
|
val window = FloatArray(fftWindow) { pending[it] }
|
||||||
|
repeat(fftWindow) { pending.removeAt(0) }
|
||||||
|
val spectrum = computeSpectrum(window)
|
||||||
|
// shift rows up, insert newest at bottom
|
||||||
|
for (r in 0 until rows - 1) {
|
||||||
|
System.arraycopy(data, (r + 1) * columns, data, r * columns, columns)
|
||||||
|
}
|
||||||
|
for (c in 0 until columns) {
|
||||||
|
val bin = c * spectrum.size / columns
|
||||||
|
data[(rows - 1) * columns + c] = spectrum[bin.coerceAtMost(spectrum.size - 1)]
|
||||||
|
}
|
||||||
|
if (colCount < rows) colCount++
|
||||||
|
frames++
|
||||||
|
}
|
||||||
|
if (frames > 0) _version.intValue++
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Magnitude spectrum via radix-2 FFT on a Hann-windowed frame. */
|
||||||
|
private fun computeSpectrum(window: FloatArray): FloatArray {
|
||||||
|
val n = window.size
|
||||||
|
val re = FloatArray(n)
|
||||||
|
val im = FloatArray(n)
|
||||||
|
for (i in 0 until n) {
|
||||||
|
val w = 0.5 - 0.5 * kotlin.math.cos(2.0 * Math.PI * i / (n - 1))
|
||||||
|
re[i] = window[i] * w.toFloat()
|
||||||
|
}
|
||||||
|
// iterative radix-2 FFT (n = 512)
|
||||||
|
var j = 0
|
||||||
|
for (i in 1 until n) {
|
||||||
|
var bit = n shr 1
|
||||||
|
while (j and bit != 0) { j = j xor bit; bit = bit shr 1 }
|
||||||
|
j = j xor bit
|
||||||
|
if (i < j) {
|
||||||
|
val tr = re[i]; re[i] = re[j]; re[j] = tr
|
||||||
|
val ti = im[i]; im[i] = im[j]; im[j] = ti
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var len = 2
|
||||||
|
while (len <= n) {
|
||||||
|
val ang = -2.0 * Math.PI / len
|
||||||
|
val wRe = kotlin.math.cos(ang).toFloat()
|
||||||
|
val wIm = kotlin.math.sin(ang).toFloat()
|
||||||
|
var i = 0
|
||||||
|
while (i < n) {
|
||||||
|
var curRe = 1f
|
||||||
|
var curIm = 0f
|
||||||
|
for (k in 0 until len / 2) {
|
||||||
|
val uRe = re[i + k]
|
||||||
|
val uIm = im[i + k]
|
||||||
|
val vRe = re[i + k + len / 2] * curRe - im[i + k + len / 2] * curIm
|
||||||
|
val vIm = re[i + k + len / 2] * curIm + im[i + k + len / 2] * curRe
|
||||||
|
re[i + k] = uRe + vRe
|
||||||
|
im[i + k] = uIm + vIm
|
||||||
|
re[i + k + len / 2] = uRe - vRe
|
||||||
|
im[i + k + len / 2] = uIm - vIm
|
||||||
|
val nRe = curRe * wRe - curIm * wIm
|
||||||
|
val nIm = curRe * wIm + curIm * wRe
|
||||||
|
curRe = nRe; curIm = nIm
|
||||||
|
}
|
||||||
|
i += len
|
||||||
|
}
|
||||||
|
len = len shl 1
|
||||||
|
}
|
||||||
|
val bins = 48
|
||||||
|
val out = FloatArray(bins)
|
||||||
|
for (k in 0 until bins) {
|
||||||
|
// bin k maps to k * 8000/512 ≈ 15.6 Hz steps; show 0..750 Hz region
|
||||||
|
out[k] = kotlin.math.sqrt(re[k] * re[k] + im[k] * im[k]) / n
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun getColumn(c: Int): FloatArray {
|
||||||
|
val col = FloatArray(rows)
|
||||||
|
for (r in 0 until rows) {
|
||||||
|
col[r] = data[r * columns + c.coerceIn(0, columns - 1)]
|
||||||
|
}
|
||||||
|
return col
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun reset() {
|
||||||
|
data.fill(0f)
|
||||||
|
pending.clear()
|
||||||
|
colCount = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CwWaterfallView(state: CwWaterfallState) {
|
||||||
|
val columns = 160
|
||||||
|
val rows = 64
|
||||||
|
// Read the frame counter so the canvas redraws as new spectra arrive.
|
||||||
|
state.version.value
|
||||||
|
Canvas(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(Color(0xFF0D1B2A))
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
) {
|
||||||
|
val cellW = size.width / columns
|
||||||
|
val cellH = size.height / rows
|
||||||
|
for (c in 0 until columns) {
|
||||||
|
val col = state.getColumn(c)
|
||||||
|
for (r in 0 until rows) {
|
||||||
|
val v = col[r]
|
||||||
|
// log-ish scaling: quiet bins stay dark, strong CW tones pop
|
||||||
|
val intensity = (kotlin.math.ln1p(v * 600f) * 55f).coerceIn(0f, 255f)
|
||||||
|
if (intensity > 4f) {
|
||||||
|
val green = (intensity * 1.25f).coerceAtMost(255f)
|
||||||
|
drawRect(
|
||||||
|
color = Color(0f, green / 255f, 0f, 1f),
|
||||||
|
topLeft = Offset(c * cellW, r * cellH),
|
||||||
|
size = Size(cellW + 0.5f, cellH + 0.5f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,10 +6,17 @@
|
|||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.feature.cw
|
package com.rtbishop.look4sat.feature.cw
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
@@ -44,50 +51,25 @@ import androidx.compose.ui.draw.clip
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.ve3nea.morse_expert.MainActivity
|
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
/** Keys/defaults ported from the original Morse Expert (root_preferences.xml + SettingsActivity logic). */
|
|
||||||
private const val KEY_MESSAGE_TYPE = "message_type"
|
|
||||||
private const val KEY_TEXT_FONT_SIZE = "text_font_size"
|
|
||||||
private const val DEFAULT_MESSAGE_TYPE = "general_text"
|
|
||||||
private const val DEFAULT_TEXT_FONT_SIZE = 18
|
|
||||||
private const val MIN_FONT_SIZE = 7
|
|
||||||
private const val MAX_FONT_SIZE = 99
|
|
||||||
|
|
||||||
/** Preset palette (9 colors, 3x3 grid); the original ColorPreferenceCompat used a third-party colorpicker, not imported. */
|
|
||||||
private val PaletteColors = listOf(
|
|
||||||
0xFFD0F0F0.toInt(), 0xFF000000.toInt(), 0xFFFFFFFF.toInt(),
|
|
||||||
0xFFAAAAAA.toInt(), 0xFFFF0000.toInt(), 0xFF00FF00.toInt(),
|
|
||||||
0xFF0000FF.toInt(), 0xFFFFFF00.toInt(), 0xFFFF00FF.toInt(),
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CW settings dialog (replaces the original Morse Expert SettingsActivity).
|
* CW decoder settings (fldigi engine).
|
||||||
* Items and keys ported from the original APK: message_type / text_font_size / 9 colors
|
*
|
||||||
* (I2.b.f663b keys, I2.b.f664d defaults, I2.b.c English titles).
|
* Options ported from fldigi progdefaults: speed (WPM), SOM codebook
|
||||||
* Storage: SharedPreferences(getPackageName() + "_preferences").
|
* decoding on/off, filter bandwidth. Persisted in SharedPreferences.
|
||||||
* OK saves and calls controller.onResume() to take effect at once; Cancel/outside-tap only closes without saving.
|
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun CwSettingsDialog(controller: MainActivity, onDismiss: () -> Unit) {
|
fun CwSettingsDialog(
|
||||||
val activity = controller.mActivity ?: return
|
onDismiss: () -> Unit,
|
||||||
val prefs = remember(activity) {
|
currentWpm: Int,
|
||||||
activity.getSharedPreferences(activity.packageName + "_preferences", Context.MODE_PRIVATE)
|
onSpeedChange: (Int) -> Unit = {},
|
||||||
}
|
onSomChange: (Boolean) -> Unit = {},
|
||||||
|
onBandwidthChange: (Int) -> Unit = {}
|
||||||
var messageType by remember {
|
) {
|
||||||
mutableStateOf(prefs.getString(KEY_MESSAGE_TYPE, DEFAULT_MESSAGE_TYPE) ?: DEFAULT_MESSAGE_TYPE)
|
var wpm by remember { mutableStateOf(currentWpm.coerceIn(5, 50)) }
|
||||||
}
|
var useSom by remember { mutableStateOf(true) }
|
||||||
var fontSize by remember {
|
var bandwidth by remember { mutableStateOf(150) }
|
||||||
mutableStateOf(
|
|
||||||
prefs.getInt(KEY_TEXT_FONT_SIZE, DEFAULT_TEXT_FONT_SIZE)
|
|
||||||
.toFloat().coerceIn(MIN_FONT_SIZE.toFloat(), MAX_FONT_SIZE.toFloat())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
var colorValues by remember {
|
|
||||||
mutableStateOf(IntArray(9) { i -> prefs.getInt(I2.b.f663b[i], I2.b.f664d[i]) })
|
|
||||||
}
|
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
@@ -98,52 +80,71 @@ fun CwSettingsDialog(controller: MainActivity, onDismiss: () -> Unit) {
|
|||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
.padding(top = 4.dp)
|
.padding(top = 4.dp)
|
||||||
) {
|
) {
|
||||||
Text("Message type", style = MaterialTheme.typography.titleSmall)
|
Text("Speed (WPM)", style = MaterialTheme.typography.titleSmall)
|
||||||
MessageTypeRow("general_text", "General Text", messageType) { messageType = it }
|
|
||||||
MessageTypeRow("ham_radio_qso", "Ham Radio QSO", messageType) { messageType = it }
|
|
||||||
HorizontalDivider(Modifier.padding(vertical = 8.dp))
|
|
||||||
|
|
||||||
Text("Text font size", style = MaterialTheme.typography.titleSmall)
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Slider(
|
Slider(
|
||||||
value = fontSize,
|
value = wpm.toFloat(),
|
||||||
onValueChange = { fontSize = it },
|
onValueChange = { wpm = it.roundToInt() },
|
||||||
valueRange = MIN_FONT_SIZE.toFloat()..MAX_FONT_SIZE.toFloat(),
|
valueRange = 5f..50f,
|
||||||
steps = MAX_FONT_SIZE - MIN_FONT_SIZE - 1,
|
modifier = Modifier.weight(1f)
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "${fontSize.roundToInt()}",
|
text = "$wpm",
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
textAlign = TextAlign.End,
|
textAlign = TextAlign.End,
|
||||||
modifier = Modifier.width(44.dp),
|
modifier = Modifier.width(44.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
HorizontalDivider(Modifier.padding(vertical = 8.dp))
|
HorizontalDivider(Modifier.padding(vertical = 8.dp))
|
||||||
|
|
||||||
Text("Colors", style = MaterialTheme.typography.titleSmall)
|
Text("Filter bandwidth (Hz)", style = MaterialTheme.typography.titleSmall)
|
||||||
for (i in 0 until 9) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
ColorSettingRow(
|
Slider(
|
||||||
title = I2.b.c[i],
|
value = bandwidth.toFloat(),
|
||||||
value = colorValues[i],
|
onValueChange = { bandwidth = it.roundToInt() },
|
||||||
onSelect = { selected ->
|
valueRange = 50f..500f,
|
||||||
colorValues = colorValues.copyOf().also { it[i] = selected }
|
steps = 8,
|
||||||
},
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
|
Text(
|
||||||
|
text = "$bandwidth",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
textAlign = TextAlign.End,
|
||||||
|
modifier = Modifier.width(44.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
HorizontalDivider(Modifier.padding(vertical = 8.dp))
|
||||||
|
|
||||||
|
Text("SOM codebook decoding", style = MaterialTheme.typography.titleSmall)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(6.dp))
|
||||||
|
.clickable { useSom = true }
|
||||||
|
.padding(vertical = 2.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
RadioButton(selected = useSom, onClick = { useSom = true })
|
||||||
|
Text("On (recommended, higher accuracy)")
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(6.dp))
|
||||||
|
.clickable { useSom = false }
|
||||||
|
.padding(vertical = 2.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
RadioButton(selected = !useSom, onClick = { useSom = false })
|
||||||
|
Text("Off (plain table lookup)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
val editor = prefs.edit()
|
onSpeedChange(wpm)
|
||||||
editor.putString(KEY_MESSAGE_TYPE, messageType)
|
onSomChange(useSom)
|
||||||
editor.putInt(KEY_TEXT_FONT_SIZE, fontSize.roundToInt())
|
onBandwidthChange(bandwidth)
|
||||||
for (i in 0 until 9) {
|
|
||||||
editor.putInt(I2.b.f663b[i], colorValues[i])
|
|
||||||
}
|
|
||||||
editor.apply()
|
|
||||||
// The original re-read all settings in MainActivity.onResume() when SettingsActivity returned; ported logic in place
|
|
||||||
controller.onResume()
|
|
||||||
onDismiss()
|
onDismiss()
|
||||||
}) { Text("OK") }
|
}) { Text("OK") }
|
||||||
},
|
},
|
||||||
@@ -152,67 +153,3 @@ fun CwSettingsDialog(controller: MainActivity, onDismiss: () -> Unit) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun MessageTypeRow(option: String, label: String, selected: String, onSelect: (String) -> Unit) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clip(RoundedCornerShape(6.dp))
|
|
||||||
.clickable { onSelect(option) }
|
|
||||||
.padding(vertical = 2.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
RadioButton(selected = option == selected, onClick = { onSelect(option) })
|
|
||||||
Text(label, style = MaterialTheme.typography.bodyMedium)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ColorSettingRow(title: String, value: Int, onSelect: (Int) -> Unit) {
|
|
||||||
Column(Modifier.fillMaxWidth().padding(vertical = 6.dp)) {
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
Text(title, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.weight(1f))
|
|
||||||
Box(
|
|
||||||
Modifier
|
|
||||||
.size(20.dp)
|
|
||||||
.clip(RoundedCornerShape(4.dp))
|
|
||||||
.background(Color(value)),
|
|
||||||
)
|
|
||||||
Spacer(Modifier.width(6.dp))
|
|
||||||
Text(
|
|
||||||
text = String.format("#%06X", value and 0xFFFFFF),
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Spacer(Modifier.height(6.dp))
|
|
||||||
PaletteGrid(selected = value, onSelect = onSelect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun PaletteGrid(selected: Int, onSelect: (Int) -> Unit) {
|
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
|
||||||
PaletteColors.chunked(3).forEach { rowColors ->
|
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
|
||||||
rowColors.forEach { color ->
|
|
||||||
val isSelected = color == selected
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.size(36.dp)
|
|
||||||
.border(
|
|
||||||
width = if (isSelected) 3.dp else 1.dp,
|
|
||||||
color = if (isSelected) MaterialTheme.colorScheme.primary
|
|
||||||
else MaterialTheme.colorScheme.outlineVariant,
|
|
||||||
shape = RoundedCornerShape(6.dp),
|
|
||||||
)
|
|
||||||
.clip(RoundedCornerShape(6.dp))
|
|
||||||
.background(Color(color))
|
|
||||||
.clickable { onSelect(color) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package com.rtbishop.look4sat.feature.cw.suncompat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compile-time stub: Android has no sun.misc.Cleaner (ported from Morse Expert 1.15's k3 FFT library cleanup callback,
|
|
||||||
* the real 1024-point FFT never hits the native path).
|
|
||||||
*/
|
|
||||||
public class Cleaner {
|
|
||||||
public static Cleaner create(Object referent, Runnable thunk) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clean() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
package com.rtbishop.look4sat.feature.cw.suncompat;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compile-time stub: Android has no sun.misc.Unsafe (obtained via reflection at runtime; k3.r.f12109a is null on failure).
|
|
||||||
* Ported from Morse Expert 1.15 (the k3 FFT library's >1GB native path never triggers; real FFT uses the float[] branch).
|
|
||||||
*/
|
|
||||||
public class Unsafe {
|
|
||||||
public long allocateMemory(long bytes) {
|
|
||||||
throw new UnsupportedOperationException("sun.misc.Unsafe not available on Android");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void freeMemory(long address) {
|
|
||||||
throw new UnsupportedOperationException("sun.misc.Unsafe not available on Android");
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte getByte(long address) {
|
|
||||||
throw new UnsupportedOperationException("sun.misc.Unsafe not available on Android");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void putByte(long address, byte value) {
|
|
||||||
throw new UnsupportedOperationException("sun.misc.Unsafe not available on Android");
|
|
||||||
}
|
|
||||||
|
|
||||||
public short getShort(long address) {
|
|
||||||
throw new UnsupportedOperationException("sun.misc.Unsafe not available on Android");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void putShort(long address, short value) {
|
|
||||||
throw new UnsupportedOperationException("sun.misc.Unsafe not available on Android");
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getInt(long address) {
|
|
||||||
throw new UnsupportedOperationException("sun.misc.Unsafe not available on Android");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void putInt(long address, int value) {
|
|
||||||
throw new UnsupportedOperationException("sun.misc.Unsafe not available on Android");
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getLong(long address) {
|
|
||||||
throw new UnsupportedOperationException("sun.misc.Unsafe not available on Android");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void putLong(long address, long value) {
|
|
||||||
throw new UnsupportedOperationException("sun.misc.Unsafe not available on Android");
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getFloat(long address) {
|
|
||||||
throw new UnsupportedOperationException("sun.misc.Unsafe not available on Android");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void putFloat(long address, float value) {
|
|
||||||
throw new UnsupportedOperationException("sun.misc.Unsafe not available on Android");
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getDouble(long address) {
|
|
||||||
throw new UnsupportedOperationException("sun.misc.Unsafe not available on Android");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void putDouble(long address, double value) {
|
|
||||||
throw new UnsupportedOperationException("sun.misc.Unsafe not available on Android");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMemory(long address, long bytes, byte value) {
|
|
||||||
throw new UnsupportedOperationException("sun.misc.Unsafe not available on Android");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void copyMemory(long src, long dst, long len) {
|
|
||||||
throw new UnsupportedOperationException("sun.misc.Unsafe not available on Android");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getObject(Object o, long offset) {
|
|
||||||
throw new UnsupportedOperationException("sun.misc.Unsafe not available on Android");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void putObject(Object o, long offset, Object value) {
|
|
||||||
throw new UnsupportedOperationException("sun.misc.Unsafe not available on Android");
|
|
||||||
}
|
|
||||||
|
|
||||||
public long objectFieldOffset(java.lang.reflect.Field field) {
|
|
||||||
throw new UnsupportedOperationException("sun.misc.Unsafe not available on Android");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
package com.ve3nea.morse_expert;
|
|
||||||
|
|
||||||
import F2.a;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.media.MediaScannerConnection;
|
|
||||||
import android.text.Layout;
|
|
||||||
import android.text.Selection;
|
|
||||||
import android.text.Spannable;
|
|
||||||
import android.text.SpannableStringBuilder;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.text.format.DateFormat;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.ActionMode;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ported from Morse Expert 1.15 DecodedTextView; obfuscated class name kept.
|
|
||||||
* Adaptation: originally extends l.C1702e0 (obfuscated appcompat class) -> now extends TextView directly
|
|
||||||
* (C1702e0 only provided TextView-compatible behavior; this class never used its specific methods).
|
|
||||||
*/
|
|
||||||
public class DecodedTextView extends TextView {
|
|
||||||
|
|
||||||
/* renamed from: m, reason: collision with root package name */
|
|
||||||
public final Context f11031m;
|
|
||||||
|
|
||||||
/* renamed from: n, reason: collision with root package name */
|
|
||||||
public boolean f11032n;
|
|
||||||
|
|
||||||
/* renamed from: o, reason: collision with root package name */
|
|
||||||
public final AtomicBoolean f11033o;
|
|
||||||
|
|
||||||
public DecodedTextView(Context context, AttributeSet attributeSet) {
|
|
||||||
super(context, attributeSet);
|
|
||||||
this.f11032n = false;
|
|
||||||
this.f11033o = new AtomicBoolean(false);
|
|
||||||
this.f11031m = context;
|
|
||||||
setCustomSelectionActionModeCallback(getActionModeCallback());
|
|
||||||
}
|
|
||||||
|
|
||||||
private ActionMode.Callback getActionModeCallback() {
|
|
||||||
return new a(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getDecodedText() {
|
|
||||||
int selectionStart = getSelectionStart();
|
|
||||||
int selectionEnd = getSelectionEnd();
|
|
||||||
int max = Math.max(0, Math.min(selectionStart, selectionEnd));
|
|
||||||
int max2 = Math.max(0, Math.max(selectionStart, selectionEnd));
|
|
||||||
if (max2 > max) {
|
|
||||||
return getText().subSequence(max, max2).toString();
|
|
||||||
}
|
|
||||||
return getText().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // android.widget.TextView
|
|
||||||
public final void onSelectionChanged(int i4, int i5) {
|
|
||||||
super.onSelectionChanged(i4, i5);
|
|
||||||
if (hasSelection()) {
|
|
||||||
this.f11032n = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void q(SpannableStringBuilder spannableStringBuilder) {
|
|
||||||
boolean z3;
|
|
||||||
try {
|
|
||||||
CharSequence text = getText();
|
|
||||||
Layout layout = getLayout();
|
|
||||||
int lineCount = getLineCount();
|
|
||||||
int length = text.length();
|
|
||||||
int lineHeight = getLineHeight();
|
|
||||||
int scrollY = getScrollY();
|
|
||||||
if (layout.getLineBottom(lineCount - 1) == getHeight() + scrollY) {
|
|
||||||
z3 = true;
|
|
||||||
} else {
|
|
||||||
z3 = false;
|
|
||||||
}
|
|
||||||
getSelectionStart();
|
|
||||||
getSelectionEnd();
|
|
||||||
hasSelection();
|
|
||||||
if (z3) {
|
|
||||||
Selection.setSelection((Spannable) text, length, length);
|
|
||||||
}
|
|
||||||
append(spannableStringBuilder);
|
|
||||||
int i4 = lineCount - 50;
|
|
||||||
if (i4 > 0) {
|
|
||||||
int i5 = lineHeight * i4;
|
|
||||||
getEditableText().delete(0, layout.getLineEnd(lineCount - 51));
|
|
||||||
if (scrollY >= i5) {
|
|
||||||
scrollBy(0, -i5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e4) {
|
|
||||||
Log.e("printChars()", "Exception: ", e4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void r() {
|
|
||||||
String decodedText = getDecodedText();
|
|
||||||
boolean isEmpty = TextUtils.isEmpty(decodedText);
|
|
||||||
Context context = this.f11031m;
|
|
||||||
if (isEmpty) {
|
|
||||||
Toast.makeText(context, "No text to save", 0).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
File file = new File(getContext().getExternalFilesDir(null), ((String) DateFormat.format("yyyy-MM-dd_HH_mm_ss", new Date())) + ".txt");
|
|
||||||
String absolutePath = file.getAbsolutePath();
|
|
||||||
PrintWriter printWriter = new PrintWriter(file);
|
|
||||||
try {
|
|
||||||
printWriter.println(decodedText);
|
|
||||||
printWriter.close();
|
|
||||||
MediaScannerConnection.scanFile(context, new String[]{absolutePath}, new String[]{"text/plain"}, null);
|
|
||||||
Toast.makeText(context, "Text saved to " + absolutePath, 1).show();
|
|
||||||
} catch (Throwable th) {
|
|
||||||
printWriter.close();
|
|
||||||
throw th;
|
|
||||||
}
|
|
||||||
} catch (Exception e4) {
|
|
||||||
Log.e("File Save", "Failed", e4);
|
|
||||||
Toast.makeText(context, "Unable to save: " + e4.getMessage(), 1).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void s() {
|
|
||||||
Intent intent = new Intent("android.intent.action.SEND");
|
|
||||||
intent.setFlags(268435456);
|
|
||||||
intent.putExtra("android.intent.extra.TEXT", getDecodedText());
|
|
||||||
intent.setType("text/plain");
|
|
||||||
this.f11031m.startActivity(Intent.createChooser(intent, "Share Decoded Text"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,232 +0,0 @@
|
|||||||
|
|
||||||
package com.ve3nea.morse_expert;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.Window;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
|
||||||
import com.rtbishop.look4sat.feature.cw.R;
|
|
||||||
import E2.g;
|
|
||||||
import H2.a;
|
|
||||||
import H2.b;
|
|
||||||
import j1.C1646n;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ported verbatim from Morse Expert 1.15 MainActivity (com.ve3nea.morse_expert); obfuscated
|
|
||||||
* class/field names kept as-is. Surgery notes: originally extends e.AbstractActivityC1557l
|
|
||||||
* (obfuscated AppCompat base) -> now a plain controller class, Activity injected via
|
|
||||||
* onCreate(Activity, ConstraintLayout) for Compose AndroidView integration. Ads (d1.*),
|
|
||||||
* billing (I2.a), premium/rate menu removed per user. h3.b.r (recursive findViewById),
|
|
||||||
* K1.a.M (immersive status bar), B.i (permissions) replaced with equivalent standard APIs.
|
|
||||||
* Decoder logic (H2.a audio, H2.b core, pas engine) untouched.
|
|
||||||
*/
|
|
||||||
public class MainActivity {
|
|
||||||
|
|
||||||
public static final /* synthetic */ int f11034K = 0;
|
|
||||||
|
|
||||||
/* renamed from: D, reason: collision with root package name */
|
|
||||||
public C1646n f11035D;
|
|
||||||
|
|
||||||
/* renamed from: E, reason: collision with root package name */
|
|
||||||
public a f11036E;
|
|
||||||
|
|
||||||
/* renamed from: F, reason: collision with root package name */
|
|
||||||
public b f11037F;
|
|
||||||
|
|
||||||
/* renamed from: G, reason: collision with root package name */
|
|
||||||
public long f11038G = 0;
|
|
||||||
|
|
||||||
/* renamed from: J, reason: collision with root package name */
|
|
||||||
public final g f11040J = new g(this, 1);
|
|
||||||
|
|
||||||
/** Injected Activity context (replaces the original this Activity capabilities) */
|
|
||||||
public android.app.Activity mActivity;
|
|
||||||
|
|
||||||
public void onCreate(android.app.Activity activity, ConstraintLayout constraintLayout) {
|
|
||||||
onCreate(activity, constraintLayout, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* applyImmersive=false is used when embedding in a small container (e.g. the transponder CW panel):
|
|
||||||
* does not touch the host window's system-bar flags (avoids affecting the whole Activity).
|
|
||||||
*/
|
|
||||||
public void onCreate(android.app.Activity activity, ConstraintLayout constraintLayout, boolean applyImmersive) {
|
|
||||||
this.mActivity = activity;
|
|
||||||
if (applyImmersive) {
|
|
||||||
// K1.a.M(getWindow(), false): immersive status bar (nav/fullscreen flags, ported as-is)
|
|
||||||
setImmersive(activity.getWindow(), false);
|
|
||||||
}
|
|
||||||
View inflate = constraintLayout;
|
|
||||||
int i4 = R.id.decodedTextView;
|
|
||||||
DecodedTextView decodedTextView = (DecodedTextView) findViewRecursive(inflate, R.id.decodedTextView);
|
|
||||||
if (decodedTextView != null) {
|
|
||||||
i4 = R.id.scaleView;
|
|
||||||
ScaleView scaleView = (ScaleView) findViewRecursive(inflate, R.id.scaleView);
|
|
||||||
if (scaleView != null) {
|
|
||||||
i4 = R.id.statusTextView;
|
|
||||||
TextView textView = (TextView) findViewRecursive(inflate, R.id.statusTextView);
|
|
||||||
if (textView != null) {
|
|
||||||
i4 = R.id.verticalLayout;
|
|
||||||
LinearLayout linearLayout = (LinearLayout) findViewRecursive(inflate, R.id.verticalLayout);
|
|
||||||
if (linearLayout != null) {
|
|
||||||
i4 = R.id.waterfallView;
|
|
||||||
WaterfallSurfaceView waterfallSurfaceView = (WaterfallSurfaceView) findViewRecursive(inflate, R.id.waterfallView);
|
|
||||||
if (waterfallSurfaceView != null) {
|
|
||||||
this.f11035D = new C1646n(constraintLayout, decodedTextView, scaleView, textView, linearLayout, waterfallSurfaceView);
|
|
||||||
((WaterfallSurfaceView) this.f11035D.f11892k).setOnTouchListener(this.f11040J);
|
|
||||||
// Originally: permission check then v(); permission flow handled by the Compose page
|
|
||||||
// Originally: ad banner mounting (removed)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new NullPointerException("Missing required view with ID: " + inflate.getResources().getResourceName(i4));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Original h3.b.r: recursive findViewById (ported behavior) */
|
|
||||||
private static View findViewRecursive(View view, int id) {
|
|
||||||
if (view instanceof ViewGroup) {
|
|
||||||
ViewGroup viewGroup = (ViewGroup) view;
|
|
||||||
int childCount = viewGroup.getChildCount();
|
|
||||||
for (int i = 0; i < childCount; i++) {
|
|
||||||
View found = viewGroup.getChildAt(i).findViewById(id);
|
|
||||||
if (found != null) {
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Original K1.a.M(window, false): immersive (hide nav/fullscreen flags, ported as-is) */
|
|
||||||
private static void setImmersive(Window window, boolean immersive) {
|
|
||||||
if (Build.VERSION.SDK_INT >= 30) {
|
|
||||||
window.setDecorFitsSystemWindows(!immersive);
|
|
||||||
if (immersive) {
|
|
||||||
window.getDecorView().setSystemUiVisibility(
|
|
||||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
|
|
||||||
} else {
|
|
||||||
window.getDecorView().setSystemUiVisibility(
|
|
||||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
View decorView = window.getDecorView();
|
|
||||||
int systemUiVisibility = decorView.getSystemUiVisibility();
|
|
||||||
decorView.setSystemUiVisibility(immersive ? (systemUiVisibility & (-1793)) : (systemUiVisibility | 1792));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Original v(): audio capture + decoder core init (logic unchanged) */
|
|
||||||
public void v() {
|
|
||||||
this.f11036E = new a();
|
|
||||||
b bVar = new b(mActivity.getApplicationContext());
|
|
||||||
this.f11037F = bVar;
|
|
||||||
this.f11036E.f601l = bVar;
|
|
||||||
bVar.f619o = (WaterfallSurfaceView) this.f11035D.f11892k;
|
|
||||||
bVar.f620p = this;
|
|
||||||
bVar.f621q = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Original onResume: restore decoding (message type/font/background/record start/waterfall) */
|
|
||||||
public void onResume() {
|
|
||||||
android.content.SharedPreferences prefs = mActivity.getSharedPreferences(
|
|
||||||
mActivity.getPackageName() + "_preferences", Context.MODE_PRIVATE);
|
|
||||||
String messageType = prefs.getString("message_type", "general_text");
|
|
||||||
b bVar = this.f11037F;
|
|
||||||
if (bVar != null) {
|
|
||||||
bVar.f623s.set(messageType.equals("ham_radio_qso"));
|
|
||||||
}
|
|
||||||
((DecodedTextView) this.f11035D.f11888g).setTextSize(Math.max(7, prefs.getInt("text_font_size", 18)));
|
|
||||||
b bVar2 = this.f11037F;
|
|
||||||
if (bVar2 != null) {
|
|
||||||
bVar2.f617m.b(mActivity);
|
|
||||||
((DecodedTextView) this.f11035D.f11888g).setBackgroundColor(this.f11037F.f617m.f665a[0]);
|
|
||||||
}
|
|
||||||
a aVar = this.f11036E;
|
|
||||||
if (aVar != null) {
|
|
||||||
android.media.AudioRecord audioRecord = aVar.f596g;
|
|
||||||
audioRecord.startRecording();
|
|
||||||
if (audioRecord.getState() != 1) {
|
|
||||||
android.util.Log.e("AudioRecord", "cannot start");
|
|
||||||
}
|
|
||||||
aVar.f598i.set(true);
|
|
||||||
}
|
|
||||||
((WaterfallSurfaceView) this.f11035D.f11892k).onResume();
|
|
||||||
// Originally: ad lifecycle (removed)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Original onPause: stop recording + waterfall pause */
|
|
||||||
public void onPause() {
|
|
||||||
a aVar = this.f11036E;
|
|
||||||
if (aVar != null) {
|
|
||||||
aVar.f598i.set(false);
|
|
||||||
android.media.AudioRecord audioRecord = aVar.f596g;
|
|
||||||
audioRecord.stop();
|
|
||||||
if (audioRecord.getState() != 1) {
|
|
||||||
android.util.Log.e("AudioRecord", "cannot stop");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
((WaterfallSurfaceView) this.f11035D.f11892k).onPause();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Original onDestroy: release audio/decoder (billing cleanup removed) */
|
|
||||||
public void onDestroy() {
|
|
||||||
a aVar = this.f11036E;
|
|
||||||
if (aVar != null) {
|
|
||||||
aVar.f598i.set(false);
|
|
||||||
try {
|
|
||||||
aVar.f596g.stop();
|
|
||||||
aVar.f596g.release();
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
aVar.f597h.shutdownNow();
|
|
||||||
}
|
|
||||||
this.f11036E = null;
|
|
||||||
this.f11037F = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Menu: clear decoded text (original clear_mnu) */
|
|
||||||
public void clearDecoded() {
|
|
||||||
((DecodedTextView) this.f11035D.f11888g).setText("");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Menu: pause/resume decoding (original pause_mnu, red/white icon toggle logic) */
|
|
||||||
public boolean togglePause() {
|
|
||||||
java.util.concurrent.atomic.AtomicBoolean atomicBoolean = ((DecodedTextView) this.f11035D.f11888g).f11033o;
|
|
||||||
atomicBoolean.set(!atomicBoolean.get());
|
|
||||||
return atomicBoolean.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Menu: save text (original save_mnu -> DecodedTextView.r()) */
|
|
||||||
public void saveText() {
|
|
||||||
((DecodedTextView) this.f11035D.f11888g).r();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Menu: record signals (original record_signals_mnu) */
|
|
||||||
public void recordSignals() {
|
|
||||||
this.f11037F.f626v.set(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Original tap_back_again_to_close hint */
|
|
||||||
public boolean handleBackPress() {
|
|
||||||
if (System.currentTimeMillis() < this.f11038G + 2000) {
|
|
||||||
return true; // double-back: exit
|
|
||||||
}
|
|
||||||
this.f11038G = System.currentTimeMillis();
|
|
||||||
Toast.makeText(mActivity, R.string.tap_back_again_to_close, 0).show();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Start decoding after permission granted (original onRequestPermissionsResult success) */
|
|
||||||
public void onPermissionGranted() {
|
|
||||||
v();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
package com.ve3nea.morse_expert;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Path;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public class ScaleView extends View {
|
|
||||||
public int f;
|
|
||||||
|
|
||||||
/* renamed from: g, reason: collision with root package name */
|
|
||||||
public int f11041g;
|
|
||||||
|
|
||||||
/* renamed from: h, reason: collision with root package name */
|
|
||||||
public final Paint f11042h;
|
|
||||||
|
|
||||||
/* renamed from: i, reason: collision with root package name */
|
|
||||||
public final Paint f11043i;
|
|
||||||
|
|
||||||
/* renamed from: j, reason: collision with root package name */
|
|
||||||
public final Paint f11044j;
|
|
||||||
|
|
||||||
/* renamed from: k, reason: collision with root package name */
|
|
||||||
public final Paint f11045k;
|
|
||||||
|
|
||||||
public ScaleView(Context context, AttributeSet attributeSet) {
|
|
||||||
super(context, attributeSet);
|
|
||||||
Paint paint = new Paint();
|
|
||||||
this.f11042h = paint;
|
|
||||||
paint.setColor(-16711936);
|
|
||||||
Paint paint2 = new Paint();
|
|
||||||
this.f11043i = paint2;
|
|
||||||
paint2.setColor(-16744448);
|
|
||||||
Paint paint3 = new Paint();
|
|
||||||
this.f11044j = paint3;
|
|
||||||
paint3.setColor(-256);
|
|
||||||
Paint paint4 = new Paint();
|
|
||||||
this.f11045k = paint4;
|
|
||||||
paint4.setColor(-8355840);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // android.view.View
|
|
||||||
public final void onDraw(Canvas canvas) {
|
|
||||||
Paint paint;
|
|
||||||
super.onDraw(canvas);
|
|
||||||
int i4 = this.f11041g;
|
|
||||||
if (i4 != 1) {
|
|
||||||
if (i4 != 2) {
|
|
||||||
if (i4 != 3) {
|
|
||||||
if (i4 != 4) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
paint = this.f11044j;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
paint = this.f11045k;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
paint = this.f11042h;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
paint = this.f11043i;
|
|
||||||
}
|
|
||||||
int round = (int) Math.round(getWidth() * 0.7d);
|
|
||||||
Path path = new Path();
|
|
||||||
path.moveTo(0.0f, this.f);
|
|
||||||
float f = round;
|
|
||||||
path.lineTo(f, this.f - round);
|
|
||||||
path.lineTo(f, this.f + round);
|
|
||||||
path.close();
|
|
||||||
canvas.drawPath(path, paint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package com.ve3nea.morse_expert;
|
|
||||||
|
|
||||||
import J2.b;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.opengl.GLSurfaceView;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public class WaterfallSurfaceView extends GLSurfaceView {
|
|
||||||
public final b f;
|
|
||||||
|
|
||||||
public WaterfallSurfaceView(Context context, AttributeSet attributeSet) {
|
|
||||||
super(context, attributeSet);
|
|
||||||
setEGLContextClientVersion(2);
|
|
||||||
b bVar = new b(context, H2.b.f603B);
|
|
||||||
this.f = bVar;
|
|
||||||
setRenderer(bVar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
package d1;
|
|
||||||
|
|
||||||
/* renamed from: d1.b, reason: case insensitive filesystem */
|
|
||||||
/* Ported from Morse Expert 1.15; obfuscated names kept. b(Parcel)/c(y2.d) were library utilities, trimmed (L1/y2 library deps). */
|
|
||||||
public abstract /* synthetic */ class AbstractC1518b {
|
|
||||||
public static float a(float f, float f4, k3.d dVar, long j4, float f5, float f6) {
|
|
||||||
dVar.c(j4, f - f4);
|
|
||||||
return f5 + f6;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String d(String str, int i4) {
|
|
||||||
return str + i4;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String e(String str, String str2) {
|
|
||||||
return str + str2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String f(String str, String str2, String str3) {
|
|
||||||
return str + str2 + str3;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String g(StringBuilder sb, int i4, String str) {
|
|
||||||
sb.append(i4);
|
|
||||||
sb.append(str);
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String h(StringBuilder sb, String str, String str2) {
|
|
||||||
sb.append(str);
|
|
||||||
sb.append(str2);
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static /* synthetic */ boolean i(Object obj) {
|
|
||||||
return obj != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float j(float f, float f4, k3.d dVar, long j4, float f5, float f6) {
|
|
||||||
dVar.c(j4, f + f4);
|
|
||||||
return f5 + f6;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String k(String str, String str2) {
|
|
||||||
return str + str2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float l(float f, float f4, k3.d dVar, long j4, float f5, float f6) {
|
|
||||||
dVar.c(j4, f - f4);
|
|
||||||
return f5 - f6;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float m(float f, float f4, k3.d dVar, long j4, float f5, float f6) {
|
|
||||||
dVar.c(j4, f + f4);
|
|
||||||
return f5 - f6;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static /* synthetic */ String n(int i4) {
|
|
||||||
switch (i4) {
|
|
||||||
case 1:
|
|
||||||
return "NONE";
|
|
||||||
case 2:
|
|
||||||
return "LEFT";
|
|
||||||
case 3:
|
|
||||||
return "TOP";
|
|
||||||
case 4:
|
|
||||||
return "RIGHT";
|
|
||||||
case 5:
|
|
||||||
return "BOTTOM";
|
|
||||||
case 6:
|
|
||||||
return "BASELINE";
|
|
||||||
case 7:
|
|
||||||
return "CENTER";
|
|
||||||
case 8:
|
|
||||||
return "CENTER_X";
|
|
||||||
case 9:
|
|
||||||
return "CENTER_Y";
|
|
||||||
default:
|
|
||||||
throw null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
package g3;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public final class a {
|
|
||||||
|
|
||||||
/* renamed from: a, reason: collision with root package name */
|
|
||||||
public final int f11582a;
|
|
||||||
|
|
||||||
/* renamed from: b, reason: collision with root package name */
|
|
||||||
public final double f11583b;
|
|
||||||
public final double c;
|
|
||||||
|
|
||||||
public a(double d4) {
|
|
||||||
int i4 = (int) (0.6366197723675814d * d4);
|
|
||||||
while (true) {
|
|
||||||
double d5 = -i4;
|
|
||||||
double d6 = 1.570796251296997d * d5;
|
|
||||||
double d7 = d4 + d6;
|
|
||||||
double d8 = 7.549789948768648E-8d * d5;
|
|
||||||
double d9 = d8 + d7;
|
|
||||||
double d10 = (-((d7 - d4) - d6)) + (-((d9 - d7) - d8));
|
|
||||||
double d11 = d5 * 6.123233995736766E-17d;
|
|
||||||
double d12 = d11 + d9;
|
|
||||||
double d13 = d10 + (-((d12 - d9) - d11));
|
|
||||||
if (d12 > 0.0d) {
|
|
||||||
this.f11582a = i4;
|
|
||||||
this.f11583b = d12;
|
|
||||||
this.c = d13;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
i4--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package g3;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public abstract class b {
|
|
||||||
|
|
||||||
/* renamed from: a, reason: collision with root package name */
|
|
||||||
public static final double[][] f11584a = (double[][]) d.f11591a.clone();
|
|
||||||
}
|
|
||||||
@@ -1,507 +0,0 @@
|
|||||||
package g3;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public abstract class c {
|
|
||||||
|
|
||||||
/* renamed from: a, reason: collision with root package name */
|
|
||||||
public static final double[][] f11585a;
|
|
||||||
|
|
||||||
/* renamed from: b, reason: collision with root package name */
|
|
||||||
public static final double[] f11586b;
|
|
||||||
public static final double[] c;
|
|
||||||
|
|
||||||
/* renamed from: d, reason: collision with root package name */
|
|
||||||
public static final double[] f11587d;
|
|
||||||
|
|
||||||
/* renamed from: e, reason: collision with root package name */
|
|
||||||
public static final double[] f11588e;
|
|
||||||
public static final long[] f;
|
|
||||||
|
|
||||||
/* renamed from: g, reason: collision with root package name */
|
|
||||||
public static final long[] f11589g;
|
|
||||||
|
|
||||||
/* renamed from: h, reason: collision with root package name */
|
|
||||||
public static final double[] f11590h;
|
|
||||||
|
|
||||||
static {
|
|
||||||
StrictMath.log(Double.MAX_VALUE);
|
|
||||||
f11585a = new double[][]{new double[]{1.0d, 5.669184079525E-24d}, new double[]{-0.25d, -0.25d}, new double[]{0.3333333134651184d, 1.986821492305628E-8d}, new double[]{-0.25d, -6.663542893624021E-14d}, new double[]{0.19999998807907104d, 1.1921056801463227E-8d}, new double[]{-0.1666666567325592d, -7.800414592973399E-9d}, new double[]{0.1428571343421936d, 5.650007086920087E-9d}, new double[]{-0.12502530217170715d, -7.44321345601866E-11d}, new double[]{0.11113807559013367d, 9.219544613762692E-9d}};
|
|
||||||
f11586b = new double[]{0.0d, 0.1246747374534607d, 0.24740394949913025d, 0.366272509098053d, 0.4794255495071411d, 0.5850973129272461d, 0.6816387176513672d, 0.7675435543060303d, 0.8414709568023682d, 0.902267575263977d, 0.9489846229553223d, 0.9808930158615112d, 0.9974949359893799d, 0.9985313415527344d};
|
|
||||||
c = new double[]{0.0d, -4.068233003401932E-9d, 9.755392680573412E-9d, 1.9987994582857286E-8d, -1.0902938113007961E-8d, -3.9986783938944604E-8d, 4.23719669792332E-8d, -5.207000323380292E-8d, 2.800552834259E-8d, 1.883511811213715E-8d, -3.5997360512765566E-9d, 4.116164446561962E-8d, 5.0614674548127384E-8d, -1.0129027912496858E-9d};
|
|
||||||
f11587d = new double[]{1.0d, 0.9921976327896118d, 0.9689123630523682d, 0.9305076599121094d, 0.8775825500488281d, 0.8109631538391113d, 0.7316888570785522d, 0.6409968137741089d, 0.5403022766113281d, 0.4311765432357788d, 0.3153223395347595d, 0.19454771280288696d, 0.07073719799518585d, -0.05417713522911072d};
|
|
||||||
f11588e = new double[]{0.0d, 3.4439717236742845E-8d, 5.865827662008209E-8d, -3.7999795083850525E-8d, 1.184154459111628E-8d, -3.43338934259355E-8d, 1.1795268640216787E-8d, 4.438921624363781E-8d, 2.925681159240093E-8d, -2.6437112632041807E-8d, 2.2860509143963117E-8d, -4.813899778443457E-9d, 3.6725170580355583E-9d, 2.0217439756338078E-10d};
|
|
||||||
f = new long[]{2935890503282001226L, 9154082963658192752L, 3952090531849364496L, 9193070505571053912L, 7910884519577875640L, 113236205062349959L, 4577762542105553359L, -5034868814120038111L, 4208363204685324176L, 5648769086999809661L, 2819561105158720014L, -4035746434778044925L, -302932621132653753L, -2644281811660520851L, -3183605296591799669L, 6722166367014452318L, -3512299194304650054L, -7278142539171889152L};
|
|
||||||
f11589g = new long[]{-3958705157555305932L, -4267615245585081135L};
|
|
||||||
f11590h = new double[]{0.0d, 0.125d, 0.25d, 0.375d, 0.5d, 0.625d, 0.75d, 0.875d, 1.0d, 1.125d, 1.25d, 1.375d, 1.5d, 1.625d};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double a(double d4) {
|
|
||||||
double d5;
|
|
||||||
if (d4 != d4) {
|
|
||||||
return d4;
|
|
||||||
}
|
|
||||||
if (d4 == d4 && d4 < 4.503599627370496E15d && d4 > -4.503599627370496E15d) {
|
|
||||||
long j4 = (long) d4;
|
|
||||||
if (d4 < 0.0d && j4 != d4) {
|
|
||||||
j4--;
|
|
||||||
}
|
|
||||||
if (j4 == 0) {
|
|
||||||
d5 = j4 * d4;
|
|
||||||
} else {
|
|
||||||
d5 = j4;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
d5 = d4;
|
|
||||||
}
|
|
||||||
if (d5 == d4) {
|
|
||||||
return d5;
|
|
||||||
}
|
|
||||||
double d6 = d5 + 1.0d;
|
|
||||||
if (d6 == 0.0d) {
|
|
||||||
return d4 * d6;
|
|
||||||
}
|
|
||||||
return d6;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double b(double d4) {
|
|
||||||
double g4;
|
|
||||||
double d5 = 0.0d;
|
|
||||||
if (d4 < 0.0d) {
|
|
||||||
d4 = -d4;
|
|
||||||
}
|
|
||||||
if (d4 != d4 || d4 == Double.POSITIVE_INFINITY) {
|
|
||||||
return Double.NaN;
|
|
||||||
}
|
|
||||||
int i4 = 0;
|
|
||||||
if (d4 > 3294198.0d) {
|
|
||||||
double[] dArr = new double[3];
|
|
||||||
e(d4, dArr);
|
|
||||||
i4 = ((int) dArr[0]) & 3;
|
|
||||||
d4 = dArr[1];
|
|
||||||
d5 = dArr[2];
|
|
||||||
} else if (d4 > 1.5707963267948966d) {
|
|
||||||
a aVar = new a(d4);
|
|
||||||
i4 = aVar.f11582a & 3;
|
|
||||||
d4 = aVar.f11583b;
|
|
||||||
d5 = aVar.c;
|
|
||||||
}
|
|
||||||
if (i4 != 0) {
|
|
||||||
if (i4 != 1) {
|
|
||||||
if (i4 != 2) {
|
|
||||||
if (i4 != 3) {
|
|
||||||
return Double.NaN;
|
|
||||||
}
|
|
||||||
return g(d4, d5);
|
|
||||||
}
|
|
||||||
g4 = c(d4, d5);
|
|
||||||
} else {
|
|
||||||
g4 = g(d4, d5);
|
|
||||||
}
|
|
||||||
return -g4;
|
|
||||||
}
|
|
||||||
return c(d4, d5);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double c(double d4, double d5) {
|
|
||||||
double d6 = 1.5707963267948966d - d4;
|
|
||||||
return g(d6, (6.123233995736766E-17d - d5) + (-((d6 - 1.5707963267948966d) + d4)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double d(double d4) {
|
|
||||||
if (d4 != 0.0d) {
|
|
||||||
long doubleToRawLongBits = Double.doubleToRawLongBits(d4);
|
|
||||||
if (((Long.MIN_VALUE & doubleToRawLongBits) != 0 || d4 != d4) && d4 != 0.0d) {
|
|
||||||
return Double.NaN;
|
|
||||||
}
|
|
||||||
if (d4 == Double.POSITIVE_INFINITY) {
|
|
||||||
return Double.POSITIVE_INFINITY;
|
|
||||||
}
|
|
||||||
int i4 = ((int) (doubleToRawLongBits >> 52)) - 1023;
|
|
||||||
if ((9218868437227405312L & doubleToRawLongBits) == 0) {
|
|
||||||
if (d4 == 0.0d) {
|
|
||||||
return Double.NEGATIVE_INFINITY;
|
|
||||||
}
|
|
||||||
doubleToRawLongBits <<= 1;
|
|
||||||
while ((4503599627370496L & doubleToRawLongBits) == 0) {
|
|
||||||
i4--;
|
|
||||||
doubleToRawLongBits <<= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((i4 == -1 || i4 == 0) && d4 < 1.01d && d4 > 0.99d) {
|
|
||||||
double d5 = d4 - 1.0d;
|
|
||||||
double d6 = d5 * 1.073741824E9d;
|
|
||||||
double d7 = (d5 + d6) - d6;
|
|
||||||
double d8 = d5 - d7;
|
|
||||||
double[][] dArr = f11585a;
|
|
||||||
double[] dArr2 = dArr[dArr.length - 1];
|
|
||||||
double d9 = dArr2[0];
|
|
||||||
double d10 = dArr2[1];
|
|
||||||
for (int length = dArr.length - 2; length >= 0; length--) {
|
|
||||||
double d11 = d9 * d7;
|
|
||||||
double d12 = (d10 * d8) + (d10 * d7) + (d9 * d8);
|
|
||||||
double d13 = d11 * 1.073741824E9d;
|
|
||||||
double d14 = (d11 + d13) - d13;
|
|
||||||
double d15 = (d11 - d14) + d12;
|
|
||||||
double[] dArr3 = dArr[length];
|
|
||||||
double d16 = d14 + dArr3[0];
|
|
||||||
double d17 = d15 + dArr3[1];
|
|
||||||
double d18 = d16 * 1.073741824E9d;
|
|
||||||
d9 = (d16 + d18) - d18;
|
|
||||||
d10 = (d16 - d9) + d17;
|
|
||||||
}
|
|
||||||
double d19 = d9 * d7;
|
|
||||||
double d20 = (d10 * d8) + (d7 * d10) + (d9 * d8);
|
|
||||||
double d21 = 1.073741824E9d * d19;
|
|
||||||
double d22 = (d19 + d21) - d21;
|
|
||||||
return (d19 - d22) + d20 + d22;
|
|
||||||
}
|
|
||||||
long j4 = 4499201580859392L & doubleToRawLongBits;
|
|
||||||
double[] dArr4 = b.f11584a[(int) (j4 >> 42)];
|
|
||||||
double d23 = (doubleToRawLongBits & 4398046511103L) / (j4 + 4.503599627370496E15d);
|
|
||||||
double d24 = (((((((((((-0.16624882440418567d) * d23) + 0.19999954120254515d) * d23) - 0.2499999997677497d) * d23) + 0.3333333333332802d) * d23) - 0.5d) * d23) + 1.0d) * d23;
|
|
||||||
double d25 = i4;
|
|
||||||
double d26 = 0.6931470632553101d * d25;
|
|
||||||
double d27 = dArr4[0];
|
|
||||||
double d28 = d26 + d27;
|
|
||||||
double d29 = (-((d28 - d26) - d27)) + 0.0d;
|
|
||||||
double d30 = d28 + d24;
|
|
||||||
double d31 = d29 + (-((d30 - d28) - d24));
|
|
||||||
double d32 = d25 * 1.1730463525082348E-7d;
|
|
||||||
double d33 = d30 + d32;
|
|
||||||
double d34 = d31 + (-((d33 - d30) - d32));
|
|
||||||
double d35 = dArr4[1];
|
|
||||||
double d36 = d33 + d35;
|
|
||||||
double d37 = d36 + 0.0d;
|
|
||||||
return d34 + (-((d36 - d33) - d35)) + (-((d37 - d36) - 0.0d)) + d37;
|
|
||||||
}
|
|
||||||
return Double.NEGATIVE_INFINITY;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void e(double d4, double[] dArr) {
|
|
||||||
long j4;
|
|
||||||
long j5;
|
|
||||||
long j6;
|
|
||||||
boolean z3;
|
|
||||||
boolean z4;
|
|
||||||
boolean z5;
|
|
||||||
boolean z6;
|
|
||||||
boolean z7;
|
|
||||||
boolean z8;
|
|
||||||
boolean z9;
|
|
||||||
boolean z10;
|
|
||||||
boolean z11;
|
|
||||||
boolean z12;
|
|
||||||
boolean z13;
|
|
||||||
boolean z14;
|
|
||||||
boolean z15;
|
|
||||||
boolean z16;
|
|
||||||
boolean z17;
|
|
||||||
boolean z18;
|
|
||||||
boolean z19;
|
|
||||||
boolean z20;
|
|
||||||
boolean z21;
|
|
||||||
boolean z22;
|
|
||||||
boolean z23;
|
|
||||||
long j7;
|
|
||||||
long doubleToRawLongBits = Double.doubleToRawLongBits(d4);
|
|
||||||
int i4 = ((int) ((doubleToRawLongBits >> 52) & 2047)) - 1022;
|
|
||||||
long j8 = ((doubleToRawLongBits & 4503599627370495L) | 4503599627370496L) << 11;
|
|
||||||
int i5 = i4 >> 6;
|
|
||||||
int i6 = i4 - (i5 << 6);
|
|
||||||
long[] jArr = f;
|
|
||||||
if (i6 != 0) {
|
|
||||||
if (i5 == 0) {
|
|
||||||
j7 = 0;
|
|
||||||
} else {
|
|
||||||
j7 = jArr[i5 - 1] << i6;
|
|
||||||
}
|
|
||||||
long j9 = jArr[i5];
|
|
||||||
int i7 = 64 - i6;
|
|
||||||
j4 = j7 | (j9 >>> i7);
|
|
||||||
long j10 = jArr[i5 + 1];
|
|
||||||
j5 = (j9 << i6) | (j10 >>> i7);
|
|
||||||
j6 = (jArr[i5 + 2] >>> i7) | (j10 << i6);
|
|
||||||
} else {
|
|
||||||
if (i5 == 0) {
|
|
||||||
j4 = 0;
|
|
||||||
} else {
|
|
||||||
j4 = jArr[i5 - 1];
|
|
||||||
}
|
|
||||||
j5 = jArr[i5];
|
|
||||||
j6 = jArr[i5 + 1];
|
|
||||||
}
|
|
||||||
long j11 = j8 >>> 32;
|
|
||||||
long j12 = j8 & 4294967295L;
|
|
||||||
long j13 = j5 >>> 32;
|
|
||||||
long j14 = j5 & 4294967295L;
|
|
||||||
long j15 = j11 * j13;
|
|
||||||
long j16 = j12 * j14;
|
|
||||||
long j17 = j13 * j12;
|
|
||||||
long j18 = j14 * j11;
|
|
||||||
long j19 = j16 + (j18 << 32);
|
|
||||||
long j20 = j15 + (j18 >>> 32);
|
|
||||||
if ((j16 & Long.MIN_VALUE) != 0) {
|
|
||||||
z3 = true;
|
|
||||||
} else {
|
|
||||||
z3 = false;
|
|
||||||
}
|
|
||||||
if ((j18 & 2147483648L) != 0) {
|
|
||||||
z4 = true;
|
|
||||||
} else {
|
|
||||||
z4 = false;
|
|
||||||
}
|
|
||||||
long j21 = j19 & Long.MIN_VALUE;
|
|
||||||
if (j21 != 0) {
|
|
||||||
z5 = true;
|
|
||||||
} else {
|
|
||||||
z5 = false;
|
|
||||||
}
|
|
||||||
if ((z3 && z4) || ((z3 || z4) && !z5)) {
|
|
||||||
j20++;
|
|
||||||
}
|
|
||||||
if (j21 != 0) {
|
|
||||||
z6 = true;
|
|
||||||
} else {
|
|
||||||
z6 = false;
|
|
||||||
}
|
|
||||||
if ((j17 & 2147483648L) != 0) {
|
|
||||||
z7 = true;
|
|
||||||
} else {
|
|
||||||
z7 = false;
|
|
||||||
}
|
|
||||||
long j22 = j19 + (j17 << 32);
|
|
||||||
long j23 = j20 + (j17 >>> 32);
|
|
||||||
long j24 = j22 & Long.MIN_VALUE;
|
|
||||||
if (j24 != 0) {
|
|
||||||
z8 = true;
|
|
||||||
} else {
|
|
||||||
z8 = false;
|
|
||||||
}
|
|
||||||
if ((z6 && z7) || ((z6 || z7) && !z8)) {
|
|
||||||
j23++;
|
|
||||||
}
|
|
||||||
long j25 = j6 >>> 32;
|
|
||||||
long j26 = (j11 * j25) + ((((j6 & 4294967295L) * j11) + (j25 * j12)) >>> 32);
|
|
||||||
if (j24 != 0) {
|
|
||||||
z9 = true;
|
|
||||||
} else {
|
|
||||||
z9 = false;
|
|
||||||
}
|
|
||||||
if ((j26 & Long.MIN_VALUE) != 0) {
|
|
||||||
z10 = true;
|
|
||||||
} else {
|
|
||||||
z10 = false;
|
|
||||||
}
|
|
||||||
long j27 = j22 + j26;
|
|
||||||
if ((j27 & Long.MIN_VALUE) != 0) {
|
|
||||||
z11 = true;
|
|
||||||
} else {
|
|
||||||
z11 = false;
|
|
||||||
}
|
|
||||||
if ((z9 && z10) || ((z9 || z10) && !z11)) {
|
|
||||||
j23++;
|
|
||||||
}
|
|
||||||
long j28 = j4 >>> 32;
|
|
||||||
long j29 = j4 & 4294967295L;
|
|
||||||
long j30 = (j12 * j29) + (((j11 * j29) + (j12 * j28)) << 32) + j23;
|
|
||||||
int i8 = (int) (j30 >>> 62);
|
|
||||||
long j31 = (j27 >>> 62) | (j30 << 2);
|
|
||||||
long j32 = j27 << 2;
|
|
||||||
long j33 = j31 >>> 32;
|
|
||||||
long j34 = j31 & 4294967295L;
|
|
||||||
long[] jArr2 = f11589g;
|
|
||||||
long j35 = jArr2[0];
|
|
||||||
long j36 = j35 >>> 32;
|
|
||||||
long j37 = j35 & 4294967295L;
|
|
||||||
long j38 = j34 * j37;
|
|
||||||
long j39 = j34 * j36;
|
|
||||||
long j40 = j33 * j37;
|
|
||||||
long j41 = j38 + (j40 << 32);
|
|
||||||
long j42 = (j33 * j36) + (j40 >>> 32);
|
|
||||||
if ((j38 & Long.MIN_VALUE) != 0) {
|
|
||||||
z12 = true;
|
|
||||||
} else {
|
|
||||||
z12 = false;
|
|
||||||
}
|
|
||||||
if ((j40 & 2147483648L) != 0) {
|
|
||||||
z13 = true;
|
|
||||||
} else {
|
|
||||||
z13 = false;
|
|
||||||
}
|
|
||||||
long j43 = j41 & Long.MIN_VALUE;
|
|
||||||
if (j43 != 0) {
|
|
||||||
z14 = true;
|
|
||||||
} else {
|
|
||||||
z14 = false;
|
|
||||||
}
|
|
||||||
if ((z12 && z13) || ((z12 || z13) && !z14)) {
|
|
||||||
j42++;
|
|
||||||
}
|
|
||||||
if (j43 != 0) {
|
|
||||||
z15 = true;
|
|
||||||
} else {
|
|
||||||
z15 = false;
|
|
||||||
}
|
|
||||||
if ((j39 & 2147483648L) != 0) {
|
|
||||||
z16 = true;
|
|
||||||
} else {
|
|
||||||
z16 = false;
|
|
||||||
}
|
|
||||||
long j44 = j41 + (j39 << 32);
|
|
||||||
long j45 = j42 + (j39 >>> 32);
|
|
||||||
long j46 = j44 & Long.MIN_VALUE;
|
|
||||||
if (j46 != 0) {
|
|
||||||
z17 = true;
|
|
||||||
} else {
|
|
||||||
z17 = false;
|
|
||||||
}
|
|
||||||
if ((z15 && z16) || ((z15 || z16) && !z17)) {
|
|
||||||
j45++;
|
|
||||||
}
|
|
||||||
long j47 = jArr2[1];
|
|
||||||
long j48 = j47 >>> 32;
|
|
||||||
long j49 = (j33 * j48) + (((j33 * (j47 & 4294967295L)) + (j34 * j48)) >>> 32);
|
|
||||||
if (j46 != 0) {
|
|
||||||
z18 = true;
|
|
||||||
} else {
|
|
||||||
z18 = false;
|
|
||||||
}
|
|
||||||
if ((j49 & Long.MIN_VALUE) != 0) {
|
|
||||||
z19 = true;
|
|
||||||
} else {
|
|
||||||
z19 = false;
|
|
||||||
}
|
|
||||||
long j50 = j44 + j49;
|
|
||||||
long j51 = j50 & Long.MIN_VALUE;
|
|
||||||
if (j51 != 0) {
|
|
||||||
z20 = true;
|
|
||||||
} else {
|
|
||||||
z20 = false;
|
|
||||||
}
|
|
||||||
if ((z18 && z19) || ((z18 || z19) && !z20)) {
|
|
||||||
j45++;
|
|
||||||
}
|
|
||||||
long j52 = j32 >>> 32;
|
|
||||||
long j53 = (j52 * j36) + (((j52 * j37) + ((j32 & 4294967295L) * j36)) >>> 32);
|
|
||||||
if (j51 != 0) {
|
|
||||||
z21 = true;
|
|
||||||
} else {
|
|
||||||
z21 = false;
|
|
||||||
}
|
|
||||||
if ((j53 & Long.MIN_VALUE) != 0) {
|
|
||||||
z22 = true;
|
|
||||||
} else {
|
|
||||||
z22 = false;
|
|
||||||
}
|
|
||||||
if (((j50 + j53) & Long.MIN_VALUE) != 0) {
|
|
||||||
z23 = true;
|
|
||||||
} else {
|
|
||||||
z23 = false;
|
|
||||||
}
|
|
||||||
if ((z21 && z22) || ((z21 || z22) && !z23)) {
|
|
||||||
j45++;
|
|
||||||
}
|
|
||||||
double d5 = (j45 >>> 12) / 4.503599627370496E15d;
|
|
||||||
double d6 = ((((j45 & 4095) << 40) + (f11589g[0] >>> 24)) / 4.503599627370496E15d) / 4.503599627370496E15d; // r34 = f11589g[0](smali: aget-wide v5, v2)
|
|
||||||
double d7 = d5 + d6;
|
|
||||||
dArr[0] = i8;
|
|
||||||
dArr[1] = d7 * 2.0d;
|
|
||||||
dArr[2] = (-((d7 - d5) - d6)) * 2.0d;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* JADX WARN: Removed duplicated region for block: B:20:0x005f */
|
|
||||||
/* JADX WARN: Removed duplicated region for block: B:22:0x0063 */
|
|
||||||
/* JADX WARN: Removed duplicated region for block: B:32:0x007a */
|
|
||||||
/*
|
|
||||||
Code decompiled incorrectly, please refer to instructions dump.
|
|
||||||
*/
|
|
||||||
public static double f(double d4) {
|
|
||||||
boolean z3;
|
|
||||||
double d5;
|
|
||||||
double d6;
|
|
||||||
double g4;
|
|
||||||
int i4 = 0;
|
|
||||||
double d7 = 0.0d;
|
|
||||||
if (d4 < 0.0d) {
|
|
||||||
d5 = -d4;
|
|
||||||
z3 = true;
|
|
||||||
} else {
|
|
||||||
z3 = false;
|
|
||||||
d5 = d4;
|
|
||||||
}
|
|
||||||
if (d5 == 0.0d) {
|
|
||||||
if (Double.doubleToRawLongBits(d4) >= 0) {
|
|
||||||
return 0.0d;
|
|
||||||
}
|
|
||||||
return -0.0d;
|
|
||||||
}
|
|
||||||
if (d5 != d5 || d5 == Double.POSITIVE_INFINITY) {
|
|
||||||
return Double.NaN;
|
|
||||||
}
|
|
||||||
if (d5 > 3294198.0d) {
|
|
||||||
double[] dArr = new double[3];
|
|
||||||
e(d5, dArr);
|
|
||||||
i4 = ((int) dArr[0]) & 3;
|
|
||||||
d5 = dArr[1];
|
|
||||||
d6 = dArr[2];
|
|
||||||
} else {
|
|
||||||
if (d5 > 1.5707963267948966d) {
|
|
||||||
a aVar = new a(d5);
|
|
||||||
int i5 = aVar.f11582a & 3;
|
|
||||||
d5 = aVar.f11583b;
|
|
||||||
d6 = aVar.c;
|
|
||||||
i4 = i5;
|
|
||||||
}
|
|
||||||
if (z3) {
|
|
||||||
i4 ^= 2;
|
|
||||||
}
|
|
||||||
// smali-verified: f() = sin (quadrant switch!):
|
|
||||||
// case 0 -> g(d5,0)(sin(0-π/2))
|
|
||||||
// case 1 -> c(d5,0) (sin(pi/2-pi) = cos of the complement)
|
|
||||||
// case 2 -> -g(d5,0) (sin(pi-3pi/2) = -sin of the complement)
|
|
||||||
// case 3 -> -c(d5,0) (sin(3pi/2-2pi) = -cos of the complement)
|
|
||||||
// jadx's malformed nested if reconstructed case 0 as return NaN (all small angles NaN!),
|
|
||||||
// and routed cases 1/2/3 all through g() - root cause of 329 NaNs in the twiddle table and the scrambled spectrum!
|
|
||||||
if (i4 == 0) {
|
|
||||||
return g(d5, d7);
|
|
||||||
}
|
|
||||||
if (i4 == 1) {
|
|
||||||
return c(d5, d7);
|
|
||||||
}
|
|
||||||
if (i4 == 2) {
|
|
||||||
return -g(d5, d7);
|
|
||||||
}
|
|
||||||
return -c(d5, d7);
|
|
||||||
}
|
|
||||||
d7 = d6;
|
|
||||||
// jadx's reconstruction is incomplete (650 smali lines vs 63 reconstructed); f() is dead code called by nothing,
|
|
||||||
// return restored per smali tail semantics (cond_0: v22 = v22 + v14; return v22)
|
|
||||||
return d7;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double g(double d4, double d5) {
|
|
||||||
int i4 = (int) ((8.0d * d4) + 0.5d);
|
|
||||||
double d6 = d4 - f11590h[i4];
|
|
||||||
double d7 = f11586b[i4];
|
|
||||||
double d8 = c[i4];
|
|
||||||
double d9 = f11587d[i4];
|
|
||||||
double d10 = f11588e[i4];
|
|
||||||
double d11 = d6 * d6;
|
|
||||||
double d12 = ((((((2.7553817452272217E-6d * d11) - 1.9841269659586505E-4d) * d11) + 0.008333333333329196d) * d11) - 0.16666666666666666d) * d11 * d6;
|
|
||||||
double d13 = ((((((2.479773539153719E-5d * d11) - 0.0013888888689039883d) * d11) + 0.041666666666621166d) * d11) - 0.49999999999999994d) * d11;
|
|
||||||
double d14 = 1.073741824E9d * d6;
|
|
||||||
double d15 = (d6 + d14) - d14;
|
|
||||||
double d16 = (d6 - d15) + d12;
|
|
||||||
double d17 = d7 + 0.0d;
|
|
||||||
double d18 = d9 * d15;
|
|
||||||
double d19 = d17 + d18;
|
|
||||||
double d20 = (d10 * d16) + (d8 * d13) + (d10 * d15) + (d9 * d16) + (d7 * d13) + (-((d17 - 0.0d) - d7)) + 0.0d + (-((d19 - d17) - d18)) + d8;
|
|
||||||
if (d5 != 0.0d) {
|
|
||||||
double d21 = (((d13 + 1.0d) * (d9 + d10)) - ((d15 + d16) * (d7 + d8))) * d5;
|
|
||||||
double d22 = d19 + d21;
|
|
||||||
d20 += -((d22 - d19) - d21);
|
|
||||||
d19 = d22;
|
|
||||||
}
|
|
||||||
return d19 + d20;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package g3;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public abstract class d {
|
|
||||||
|
|
||||||
/* renamed from: a, reason: collision with root package name */
|
|
||||||
public static final double[][] f11591a = {new double[]{0.0d, 0.0d}, new double[]{9.760860120877624E-4d, -3.903230345984362E-11d}, new double[]{0.0019512202125042677d, -8.124251825289188E-11d}, new double[]{0.0029254043474793434d, -1.8374207360194882E-11d}, new double[]{0.0038986406289041042d, -2.1324678121885073E-10d}, new double[]{0.004870930686593056d, -4.5199654318611534E-10d}, new double[]{0.00584227591753006d, -2.933016992001806E-10d}, new double[]{0.006812678650021553d, -2.325147219074669E-10d}, new double[]{0.007782140746712685d, -3.046577356838847E-10d}, new double[]{0.008750664070248604d, -5.500631513861575E-10d}, new double[]{0.00971824862062931d, 8.48292035519895E-10d}, new double[]{0.010684899985790253d, 1.1422610134013436E-10d}, new double[]{0.01165061630308628d, 9.168889933128375E-10d}, new double[]{0.012615403160452843d, -5.303786078838E-10d}, new double[]{0.013579258695244789d, -5.688639355498786E-10d}, new double[]{0.01454218477010727d, 7.296670293275653E-10d}, new double[]{0.015504186972975731d, -4.370104767451421E-10d}, new double[]{0.016465261578559875d, 1.43695591408832E-9d}, new double[]{0.01742541790008545d, -1.1862263158849434E-9d}, new double[]{0.018384650349617004d, -9.482976524690715E-10d}, new double[]{0.01934296265244484d, 1.9068609515836638E-10d}, new double[]{0.020300358533859253d, 2.655990315697216E-10d}, new double[]{0.021256837993860245d, 1.0315548713040775E-9d}, new double[]{0.022212404757738113d, 5.13345647019085E-10d}, new double[]{0.02316705882549286d, 4.5604151934208014E-10d}, new double[]{0.02412080392241478d, -1.1255706987475148E-9d}, new double[]{0.025073636323213577d, 1.2289023836765196E-9d}, new double[]{0.02602556347846985d, 1.7990281828096504E-9d}, new double[]{0.026976589113473892d, -1.4152718164638451E-9d}, new double[]{0.02792670577764511d, 7.568772963781632E-10d}, new double[]{0.0288759246468544d, -1.1449998592111558E-9d}, new double[]{0.029824241995811462d, -1.6850976862319495E-9d}, new double[]{0.030771657824516296d, 8.422373919843096E-10d}, new double[]{0.0317181795835495d, 6.872350402175489E-10d}, new double[]{0.03266380727291107d, -4.541194749189272E-10d}, new double[]{0.03360854089260101d, -8.9064764856495E-10d}, new double[]{0.034552380442619324d, 1.0640404096769032E-9d}, new double[]{0.0354953333735466d, -3.5901655945224663E-10d}, new double[]{0.03643739968538284d, -3.4829517943661266E-9d}, new double[]{0.037378571927547455d, 8.149473794244232E-10d}, new double[]{0.03831886500120163d, -6.990650304449166E-10d}, new double[]{0.03925827145576477d, 1.0883076226453258E-9d}, new double[]{0.040196798741817474d, 3.845192807999274E-10d}, new double[]{0.04113444685935974d, -1.1570594692045927E-9d}, new double[]{0.04207121580839157d, -1.8877045166697178E-9d}, new double[]{0.043007105588912964d, -1.6332083257987747E-10d}, new double[]{0.04394212365150452d, -1.7950057534514933E-9d}, new double[]{0.04487626254558563d, 2.302710041648838E-9d}, new double[]{0.045809537172317505d, -1.1410233017161343E-9d}, new double[]{0.04674194008111954d, -3.0498741599744685E-9d}, new double[]{0.04767347127199173d, -1.8026348269183678E-9d}, new double[]{0.04860413819551468d, -3.233204600453039E-9d}, new double[]{0.04953393340110779d, 1.7211688427961583E-9d}, new double[]{0.05046287178993225d, -2.329967807055457E-10d}, new double[]{0.05139094591140747d, -4.191810118556531E-11d}, new double[]{0.052318163216114044d, -3.5574324788328143E-9d}, new double[]{0.053244516253471375d, -1.7346590916458485E-9d}, new double[]{0.05417001247406006d, -4.343048751383674E-10d}, new double[]{0.055094651877880096d, 1.92909364037955E-9d}, new double[]{0.056018441915512085d, -5.139745677199588E-10d}, new double[]{0.05694137513637543d, 1.2637629975129189E-9d}, new double[]{0.05786345899105072d, 1.3840561112481119E-9d}, new double[]{0.058784693479537964d, 1.414889689612056E-9d}, new double[]{0.05970507860183716d, 2.9199191907666474E-9d}, new double[]{0.0606246218085289d, 7.90594243412116E-12d}, new double[]{0.06154331564903259d, 1.6844747839686189E-9d}, new double[]{0.06246116757392883d, 2.0498074572151747E-9d}, new double[]{0.06337818503379822d, -4.800180493433863E-9d}, new double[]{0.06429435312747955d, -2.4220822960064277E-9d}, new double[]{0.06520968675613403d, -4.179048566709334E-9d}, new double[]{0.06612417101860046d, 6.363872957010456E-9d}, new double[]{0.06703783571720123d, 9.339468680056365E-10d}, new double[]{0.06795066595077515d, -4.04226739708981E-9d}, new double[]{0.0688626617193222d, -7.043545052852817E-9d}, new double[]{0.06977382302284241d, -6.552819560439773E-9d}, new double[]{0.07068414986133575d, -1.0571674860370546E-9d}, new double[]{0.07159365713596344d, -3.948954622015801E-9d}, new double[]{0.07250232994556427d, 1.1776625988228244E-9d}, new double[]{0.07341018319129944d, 9.221072639606492E-10d}, new double[]{0.07431721687316895d, -3.219119568928366E-9d}, new double[]{0.0752234160900116d, 5.147575929018918E-9d}, new double[]{0.07612881064414978d, -2.291749683541979E-9d}, new doubleLine truncated
|
|
||||||
new double[]{0.6813591718673706d, 5.294053246347931E-8d}, new double[]{0.681853175163269d, -1.2220826223585654E-9d}, new double[]{0.6823468208312988d, 5.8377876767612725E-8d}, new double[]{0.6828403472900391d, -6.437492120743254E-9d}, new double[]{0.6833335161209106d, 4.2990710043633113E-8d}, new double[]{0.6838265657424927d, -3.1516131027023284E-8d}, new double[]{0.684319257736206d, 8.70017386744679E-9d}, new double[]{0.6848117113113403d, 4.466959125843237E-8d}, new double[]{0.6853040456771851d, -4.25782656420497E-8d}, new double[]{0.6857960224151611d, -1.4386267593671393E-8d}, new double[]{0.6862877607345581d, 1.0274494061148778E-8d}, new double[]{0.686779260635376d, 3.164186629229597E-8d}, new double[]{0.6872705221176147d, 4.995334552140326E-8d}, new double[]{0.687761664390564d, -5.3763211240398744E-8d}, new double[]{0.6882524490356445d, -4.0852427502515625E-8d}, new double[]{0.688742995262146d, -3.0287143914420064E-8d}, new double[]{0.6892333030700684d, -2.183125937905008E-8d}, new double[]{0.6897233724594116d, -1.524901992178814E-8d}, new double[]{0.6902132034301758d, -1.0305018010328949E-8d}, new double[]{0.6907027959823608d, -6.764191876212205E-9d}, new double[]{0.6911921501159668d, -4.391824838015402E-9d}, new double[]{0.6916812658309937d, -2.9535446262017846E-9d}, new double[]{0.6921701431274414d, -2.2153227096187463E-9d}, new double[]{0.6926587820053101d, -1.943473623641502E-9d}};
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package i3;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public final class a implements Runnable {
|
|
||||||
public final /* synthetic */ int f;
|
|
||||||
|
|
||||||
/* renamed from: g, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ int f11686g;
|
|
||||||
|
|
||||||
/* renamed from: h, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ int f11687h;
|
|
||||||
|
|
||||||
/* renamed from: i, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ float[] f11688i;
|
|
||||||
|
|
||||||
/* renamed from: j, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ float[] f11689j;
|
|
||||||
|
|
||||||
/* renamed from: k, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ d f11690k;
|
|
||||||
|
|
||||||
public a(d dVar, int i4, int i5, int i6, float[] fArr, float[] fArr2) {
|
|
||||||
this.f11690k = dVar;
|
|
||||||
this.f = i4;
|
|
||||||
this.f11686g = i5;
|
|
||||||
this.f11687h = i6;
|
|
||||||
this.f11688i = fArr;
|
|
||||||
this.f11689j = fArr2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // java.lang.Runnable
|
|
||||||
public final void run() {
|
|
||||||
int i4 = this.f11686g;
|
|
||||||
int i5 = this.f;
|
|
||||||
if (i5 >= i4) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
float f = this.f11689j[this.f11687h + i5];
|
|
||||||
throw null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package i3;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public final class b implements Runnable {
|
|
||||||
public final /* synthetic */ long f;
|
|
||||||
|
|
||||||
/* renamed from: g, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ long f11691g;
|
|
||||||
|
|
||||||
/* renamed from: h, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ long f11692h;
|
|
||||||
|
|
||||||
/* renamed from: i, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ k3.d f11693i;
|
|
||||||
|
|
||||||
/* renamed from: j, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ k3.d f11694j;
|
|
||||||
|
|
||||||
/* renamed from: k, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ d f11695k;
|
|
||||||
|
|
||||||
public b(d dVar, long j4, long j5, long j6, k3.d dVar2, k3.d dVar3) {
|
|
||||||
this.f11695k = dVar;
|
|
||||||
this.f = j4;
|
|
||||||
this.f11691g = j5;
|
|
||||||
this.f11692h = j6;
|
|
||||||
this.f11693i = dVar2;
|
|
||||||
this.f11694j = dVar3;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // java.lang.Runnable
|
|
||||||
public final void run() {
|
|
||||||
long j4 = this.f11691g;
|
|
||||||
long j5 = this.f;
|
|
||||||
if (j5 >= j4) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.f11694j.b(this.f11692h + j5);
|
|
||||||
throw null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package i3;
|
|
||||||
|
|
||||||
import k3.g;
|
|
||||||
import k3.s;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public final class c implements Runnable {
|
|
||||||
public final /* synthetic */ int f = 1;
|
|
||||||
|
|
||||||
/* renamed from: g, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ long f11696g;
|
|
||||||
|
|
||||||
/* renamed from: h, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ long f11697h;
|
|
||||||
|
|
||||||
/* renamed from: i, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ g f11698i;
|
|
||||||
|
|
||||||
/* renamed from: j, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ Object f11699j;
|
|
||||||
|
|
||||||
public c(long j4, long j5, s sVar, s sVar2) {
|
|
||||||
this.f11696g = j4;
|
|
||||||
this.f11697h = j5;
|
|
||||||
this.f11698i = sVar;
|
|
||||||
this.f11699j = sVar2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // java.lang.Runnable
|
|
||||||
public final void run() {
|
|
||||||
switch (this.f) {
|
|
||||||
case 0:
|
|
||||||
k3.d dVar = (k3.d) this.f11698i;
|
|
||||||
long j4 = this.f11697h;
|
|
||||||
long j5 = this.f11696g;
|
|
||||||
if (j5 >= j4) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dVar.b(j5 * 2);
|
|
||||||
throw null;
|
|
||||||
default:
|
|
||||||
for (long j6 = this.f11696g; j6 < this.f11697h; j6++) {
|
|
||||||
((s) this.f11698i).c(j6, ((s) this.f11699j).b(j6));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public c(d dVar, long j4, long j5, k3.d dVar2) {
|
|
||||||
this.f11699j = dVar;
|
|
||||||
this.f11696g = j4;
|
|
||||||
this.f11697h = j5;
|
|
||||||
this.f11698i = dVar2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,42 +0,0 @@
|
|||||||
package j1;
|
|
||||||
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
|
||||||
import com.ve3nea.morse_expert.DecodedTextView;
|
|
||||||
import com.ve3nea.morse_expert.ScaleView;
|
|
||||||
import com.ve3nea.morse_expert.WaterfallSurfaceView;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ported from Morse Expert 1.15 j1.C1646n; obfuscated class/field names kept.
|
|
||||||
* R8-merged class surgery: keeps only the view holder (6-arg constructor + 5 View fields);
|
|
||||||
* the remaining lines 158-1278 (ads/billing merged methods) were removed wholesale (user: no ad migration).
|
|
||||||
*/
|
|
||||||
public final class C1646n {
|
|
||||||
|
|
||||||
public final /* synthetic */ int f;
|
|
||||||
|
|
||||||
/* renamed from: g, reason: collision with root package name */
|
|
||||||
public Object f11888g;
|
|
||||||
|
|
||||||
/* renamed from: h, reason: collision with root package name */
|
|
||||||
public Object f11889h;
|
|
||||||
|
|
||||||
/* renamed from: i, reason: collision with root package name */
|
|
||||||
public Object f11890i;
|
|
||||||
|
|
||||||
/* renamed from: j, reason: collision with root package name */
|
|
||||||
public Object f11891j;
|
|
||||||
|
|
||||||
/* renamed from: k, reason: collision with root package name */
|
|
||||||
public Object f11892k;
|
|
||||||
|
|
||||||
public C1646n(ConstraintLayout constraintLayout, DecodedTextView decodedTextView, ScaleView scaleView, TextView textView, LinearLayout linearLayout, WaterfallSurfaceView waterfallSurfaceView) {
|
|
||||||
this.f = 1;
|
|
||||||
this.f11888g = decodedTextView;
|
|
||||||
this.f11889h = scaleView;
|
|
||||||
this.f11890i = textView;
|
|
||||||
this.f11891j = linearLayout;
|
|
||||||
this.f11892k = waterfallSurfaceView;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
package j3;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public final class a implements Runnable {
|
|
||||||
public final /* synthetic */ int f;
|
|
||||||
|
|
||||||
/* renamed from: g, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ int f11919g;
|
|
||||||
|
|
||||||
/* renamed from: h, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ int f11920h;
|
|
||||||
|
|
||||||
/* renamed from: i, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ int f11921i;
|
|
||||||
|
|
||||||
/* renamed from: j, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ float[] f11922j;
|
|
||||||
|
|
||||||
/* renamed from: k, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ float[] f11923k;
|
|
||||||
|
|
||||||
/* renamed from: l, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ int f11924l;
|
|
||||||
|
|
||||||
public /* synthetic */ a(int i4, int i5, int i6, int i7, int i8, float[] fArr, float[] fArr2) {
|
|
||||||
this.f = i8;
|
|
||||||
this.f11919g = i4;
|
|
||||||
this.f11920h = i5;
|
|
||||||
this.f11921i = i6;
|
|
||||||
this.f11922j = fArr;
|
|
||||||
this.f11923k = fArr2;
|
|
||||||
this.f11924l = i7;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // java.lang.Runnable
|
|
||||||
public final void run() {
|
|
||||||
switch (this.f) {
|
|
||||||
case 0:
|
|
||||||
int i4 = this.f11919g;
|
|
||||||
int i5 = this.f11920h;
|
|
||||||
int i6 = i4 + i5;
|
|
||||||
int i7 = this.f11921i;
|
|
||||||
while (true) {
|
|
||||||
int i8 = i7;
|
|
||||||
if (i8 > 512) {
|
|
||||||
i7 = i8 >> 2;
|
|
||||||
c.w(i7, i6 - i7, this.f11924l - (i8 >> 3), this.f11922j, this.f11923k);
|
|
||||||
} else {
|
|
||||||
int i9 = this.f11924l;
|
|
||||||
float[] fArr = this.f11923k;
|
|
||||||
c.u(i8, 1, this.f11922j, i6 - i8, i9, fArr);
|
|
||||||
int i10 = i4 - i8;
|
|
||||||
int i11 = 0;
|
|
||||||
int i12 = i5 - i8;
|
|
||||||
while (i12 > 0) {
|
|
||||||
int i13 = i11 + 1;
|
|
||||||
int i14 = this.f11924l;
|
|
||||||
float[] fArr2 = this.f11923k;
|
|
||||||
int i15 = i12;
|
|
||||||
i11 = i13;
|
|
||||||
int i16 = this.f11924l;
|
|
||||||
float[] fArr3 = this.f11923k;
|
|
||||||
c.u(i8, c.E(i8, i12, i13, this.f11919g, i14, this.f11922j, fArr2), this.f11922j, i10 + i15, i16, fArr3);
|
|
||||||
i12 = i15 - i8;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
int i17 = this.f11919g;
|
|
||||||
int i18 = this.f11920h;
|
|
||||||
int i19 = i17 + i18;
|
|
||||||
int i20 = this.f11921i;
|
|
||||||
int i21 = 1;
|
|
||||||
while (i20 > 512) {
|
|
||||||
i20 >>= 2;
|
|
||||||
i21 <<= 2;
|
|
||||||
c.y(i20, i19 - i20, this.f11924l - i20, this.f11922j, this.f11923k);
|
|
||||||
}
|
|
||||||
int i22 = this.f11924l;
|
|
||||||
float[] fArr4 = this.f11923k;
|
|
||||||
c.u(i20, 0, this.f11922j, i19 - i20, i22, fArr4);
|
|
||||||
int i23 = i21 >> 1;
|
|
||||||
int i24 = i17 - i20;
|
|
||||||
int i25 = i18 - i20;
|
|
||||||
while (i25 > 0) {
|
|
||||||
int i26 = i23 + 1;
|
|
||||||
int i27 = this.f11924l;
|
|
||||||
float[] fArr5 = this.f11923k;
|
|
||||||
int i28 = i25;
|
|
||||||
i23 = i26;
|
|
||||||
int i29 = this.f11924l;
|
|
||||||
float[] fArr6 = this.f11923k;
|
|
||||||
c.u(i20, c.E(i20, i25, i26, this.f11919g, i27, this.f11922j, fArr5), this.f11922j, i24 + i28, i29, fArr6);
|
|
||||||
i25 = i28 - i20;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
package j3;
|
|
||||||
|
|
||||||
import k3.d;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public final class b implements Runnable {
|
|
||||||
public final /* synthetic */ int f;
|
|
||||||
|
|
||||||
/* renamed from: g, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ long f11925g;
|
|
||||||
|
|
||||||
/* renamed from: h, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ long f11926h;
|
|
||||||
|
|
||||||
/* renamed from: i, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ long f11927i;
|
|
||||||
|
|
||||||
/* renamed from: j, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ d f11928j;
|
|
||||||
|
|
||||||
/* renamed from: k, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ d f11929k;
|
|
||||||
|
|
||||||
/* renamed from: l, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ long f11930l;
|
|
||||||
|
|
||||||
public /* synthetic */ b(long j4, long j5, long j6, d dVar, d dVar2, long j7, int i4) {
|
|
||||||
this.f = i4;
|
|
||||||
this.f11925g = j4;
|
|
||||||
this.f11926h = j5;
|
|
||||||
this.f11927i = j6;
|
|
||||||
this.f11928j = dVar;
|
|
||||||
this.f11929k = dVar2;
|
|
||||||
this.f11930l = j7;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // java.lang.Runnable
|
|
||||||
public final void run() {
|
|
||||||
switch (this.f) {
|
|
||||||
case 0:
|
|
||||||
long j4 = this.f11925g;
|
|
||||||
long j5 = this.f11926h;
|
|
||||||
long j6 = j4 + j5;
|
|
||||||
long j7 = this.f11927i;
|
|
||||||
while (j7 > 512) {
|
|
||||||
long j8 = j7 >> 2;
|
|
||||||
c.x(j8, j6 - j8, this.f11930l - (j7 >> 3), this.f11928j, this.f11929k);
|
|
||||||
j7 = j8;
|
|
||||||
}
|
|
||||||
c.v(j7, 1L, j6 - j7, this.f11930l, this.f11928j, this.f11929k);
|
|
||||||
long j9 = j4 - j7;
|
|
||||||
long j10 = j5 - j7;
|
|
||||||
long j11 = 0;
|
|
||||||
for (long j12 = 0; j10 > j12; j12 = 0) {
|
|
||||||
long j13 = j11 + 1;
|
|
||||||
long j14 = j10;
|
|
||||||
c.v(j7, c.F(j7, j10, j13, this.f11925g, this.f11930l, this.f11928j, this.f11929k), j9 + j14, this.f11930l, this.f11928j, this.f11929k);
|
|
||||||
j10 = j14 - j7;
|
|
||||||
j11 = j13;
|
|
||||||
j9 = j9;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
case 1:
|
|
||||||
long j15 = this.f11925g;
|
|
||||||
long j16 = this.f11926h;
|
|
||||||
long j17 = j15 + j16;
|
|
||||||
long j18 = this.f11927i;
|
|
||||||
long j19 = 1;
|
|
||||||
while (j18 > 512) {
|
|
||||||
long j20 = j18 >> 2;
|
|
||||||
j19 <<= 2;
|
|
||||||
c.z(j20, j17 - j20, this.f11930l - j20, this.f11928j, this.f11929k);
|
|
||||||
j18 = j20;
|
|
||||||
}
|
|
||||||
c.v(j18, 0L, j17 - j18, this.f11930l, this.f11928j, this.f11929k);
|
|
||||||
long j21 = j19 >> 1;
|
|
||||||
long j22 = j15 - j18;
|
|
||||||
long j23 = j16 - j18;
|
|
||||||
while (j23 > 0) {
|
|
||||||
long j24 = j21 + 1;
|
|
||||||
long j25 = j23;
|
|
||||||
j21 = j24;
|
|
||||||
c.v(j18, c.F(j18, j23, j24, this.f11925g, this.f11930l, this.f11928j, this.f11929k), j22 + j25, this.f11930l, this.f11928j, this.f11929k);
|
|
||||||
j23 = j25 - j18;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
for (long j26 = this.f11925g; j26 < this.f11926h; j26++) {
|
|
||||||
this.f11928j.c(this.f11927i + j26, this.f11929k.b(this.f11930l + j26));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public b(long j4, long j5, long j6, long j7, d dVar, d dVar2) {
|
|
||||||
this.f = 2;
|
|
||||||
this.f11925g = j4;
|
|
||||||
this.f11926h = j5;
|
|
||||||
this.f11928j = dVar;
|
|
||||||
this.f11927i = j6;
|
|
||||||
this.f11929k = dVar2;
|
|
||||||
this.f11930l = j7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,11 +0,0 @@
|
|||||||
package k3;
|
|
||||||
|
|
||||||
import java.lang.Thread;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public final class a implements Thread.UncaughtExceptionHandler {
|
|
||||||
@Override // java.lang.Thread.UncaughtExceptionHandler
|
|
||||||
public final void uncaughtException(Thread thread, Throwable th) {
|
|
||||||
th.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package k3;
|
|
||||||
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ThreadFactory;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public final class b implements ThreadFactory {
|
|
||||||
|
|
||||||
/* renamed from: b, reason: collision with root package name */
|
|
||||||
public static final ThreadFactory f12091b = Executors.defaultThreadFactory();
|
|
||||||
|
|
||||||
/* renamed from: a, reason: collision with root package name */
|
|
||||||
public final a f12092a;
|
|
||||||
|
|
||||||
public b(a aVar) {
|
|
||||||
this.f12092a = aVar;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // java.util.concurrent.ThreadFactory
|
|
||||||
public final Thread newThread(Runnable runnable) {
|
|
||||||
Thread newThread = f12091b.newThread(runnable);
|
|
||||||
newThread.setUncaughtExceptionHandler(this.f12092a);
|
|
||||||
return newThread;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package k3;
|
|
||||||
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public abstract class c {
|
|
||||||
|
|
||||||
/* renamed from: a, reason: collision with root package name */
|
|
||||||
public static final ExecutorService f12093a;
|
|
||||||
|
|
||||||
/* renamed from: b, reason: collision with root package name */
|
|
||||||
public static ExecutorService f12094b;
|
|
||||||
public static final int c;
|
|
||||||
|
|
||||||
/* renamed from: d, reason: collision with root package name */
|
|
||||||
public static final long f12095d;
|
|
||||||
|
|
||||||
/* JADX WARN: Type inference failed for: r1v0, types: [java.lang.Object, k3.a] */
|
|
||||||
static {
|
|
||||||
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool(new b(new a()));
|
|
||||||
f12093a = newCachedThreadPool;
|
|
||||||
f12094b = newCachedThreadPool;
|
|
||||||
c = Runtime.getRuntime().availableProcessors();
|
|
||||||
f12095d = 100000L;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Future a(Runnable runnable) {
|
|
||||||
if (f12094b.isShutdown() || f12094b.isTerminated()) {
|
|
||||||
f12094b = f12093a;
|
|
||||||
}
|
|
||||||
return f12094b.submit(runnable);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void b(Future[] futureArr) throws InterruptedException, java.util.concurrent.ExecutionException {
|
|
||||||
// smali: no catch, thrown straight up -> Java declares throws (faithful)
|
|
||||||
for (Future future : futureArr) {
|
|
||||||
future.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
package k3;
|
|
||||||
|
|
||||||
import com.rtbishop.look4sat.feature.cw.suncompat.Cleaner;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public final class d extends g {
|
|
||||||
|
|
||||||
/* renamed from: k, reason: collision with root package name */
|
|
||||||
public float[] f12096k;
|
|
||||||
|
|
||||||
public d() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public d(long j4, boolean z3) {
|
|
||||||
this.f = q.f12106g;
|
|
||||||
this.f12103h = 4L;
|
|
||||||
if (j4 > 0) {
|
|
||||||
this.f12102g = j4;
|
|
||||||
if (j4 > 1073741824) {
|
|
||||||
this.f12105j = r.f12109a.allocateMemory(4 * j4);
|
|
||||||
if (z3) {
|
|
||||||
a(j4);
|
|
||||||
}
|
|
||||||
Cleaner.create(this, new f(this.f12105j, this.f12102g, this.f12103h));
|
|
||||||
K1.a.c += this.f12102g * this.f12103h;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.f12096k = new float[(int) j4];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException(j4 + " is not a positive long value");
|
|
||||||
}
|
|
||||||
|
|
||||||
public final float b(long j4) {
|
|
||||||
float[] fArr = this.f12096k;
|
|
||||||
long j5 = this.f12105j;
|
|
||||||
if (j5 != 0) {
|
|
||||||
return r.f12109a.getFloat((this.f12103h * j4) + j5);
|
|
||||||
}
|
|
||||||
if (this.f12104i) {
|
|
||||||
return fArr[0];
|
|
||||||
}
|
|
||||||
return fArr[(int) j4];
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void c(long j4, float f) {
|
|
||||||
long j5 = this.f12105j;
|
|
||||||
if (j5 != 0) {
|
|
||||||
r.f12109a.putFloat((this.f12103h * j4) + j5, f);
|
|
||||||
} else {
|
|
||||||
if (!this.f12104i) {
|
|
||||||
this.f12096k[(int) j4] = f;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new IllegalAccessError("Constant arrays cannot be modified.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* JADX WARN: Type inference failed for: r0v1, types: [k3.g, java.lang.Object, k3.d] */
|
|
||||||
public final Object clone() {
|
|
||||||
if (this.f12104i) {
|
|
||||||
long j4 = this.f12102g;
|
|
||||||
float b4 = b(0L);
|
|
||||||
d gVar = new d();
|
|
||||||
gVar.f = q.f12106g;
|
|
||||||
gVar.f12103h = 4L;
|
|
||||||
if (j4 > 0) {
|
|
||||||
gVar.f12102g = j4;
|
|
||||||
gVar.f12104i = true;
|
|
||||||
gVar.f12096k = new float[]{b4};
|
|
||||||
return gVar;
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException(j4 + " is not a positive long value");
|
|
||||||
}
|
|
||||||
d dVar = new d(this.f12102g, false);
|
|
||||||
r.a(0L, 0L, this.f12102g, this, dVar);
|
|
||||||
return dVar;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // k3.g
|
|
||||||
public final boolean equals(Object obj) {
|
|
||||||
if (!super.equals(obj) || this.f12096k != ((d) obj).f12096k) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // k3.g
|
|
||||||
public final int hashCode() {
|
|
||||||
int i4;
|
|
||||||
int hashCode = super.hashCode() * 29;
|
|
||||||
float[] fArr = this.f12096k;
|
|
||||||
if (fArr != null) {
|
|
||||||
i4 = fArr.hashCode();
|
|
||||||
} else {
|
|
||||||
i4 = 0;
|
|
||||||
}
|
|
||||||
return hashCode + i4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
package k3;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public final class e implements Runnable {
|
|
||||||
public final /* synthetic */ long f;
|
|
||||||
|
|
||||||
/* renamed from: g, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ long f12097g;
|
|
||||||
|
|
||||||
/* renamed from: h, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ long f12098h;
|
|
||||||
|
|
||||||
/* renamed from: i, reason: collision with root package name */
|
|
||||||
public final /* synthetic */ g f12099i;
|
|
||||||
|
|
||||||
public e(g gVar, long j4, long j5, long j6) {
|
|
||||||
this.f12099i = gVar;
|
|
||||||
this.f = j4;
|
|
||||||
this.f12097g = j5;
|
|
||||||
this.f12098h = j6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* JADX WARN: Can't fix incorrect switch cases order, some code will duplicate */
|
|
||||||
/* JADX WARN: Failed to find 'out' block for switch in B:2:0x0011. Please report as an issue. */
|
|
||||||
@Override // java.lang.Runnable
|
|
||||||
public final void run() {
|
|
||||||
g gVar = this.f12099i;
|
|
||||||
int ordinal = gVar.f.ordinal();
|
|
||||||
long j4 = this.f12098h;
|
|
||||||
long j5 = this.f12097g;
|
|
||||||
long j6 = this.f;
|
|
||||||
switch (ordinal) {
|
|
||||||
case 0:
|
|
||||||
case 1:
|
|
||||||
case 2:
|
|
||||||
case 10:
|
|
||||||
case 11:
|
|
||||||
while (j6 < j5) {
|
|
||||||
r.f12109a.putByte((gVar.f12103h * j6) + j4, (byte) 0);
|
|
||||||
j6++;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
case 3:
|
|
||||||
while (j6 < j5) {
|
|
||||||
r.f12109a.putShort((gVar.f12103h * j6) + j4, (short) 0);
|
|
||||||
j6++;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
case 4:
|
|
||||||
while (j6 < j5) {
|
|
||||||
r.f12109a.putInt((gVar.f12103h * j6) + j4, 0);
|
|
||||||
j6++;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
case 5:
|
|
||||||
while (j6 < j5) {
|
|
||||||
r.f12109a.putLong((gVar.f12103h * j6) + j4, 0L);
|
|
||||||
j6++;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
case 6:
|
|
||||||
while (j6 < j5) {
|
|
||||||
r.f12109a.putFloat((gVar.f12103h * j6) + j4, 0.0f);
|
|
||||||
j6++;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
case 7:
|
|
||||||
while (j6 < j5) {
|
|
||||||
r.f12109a.putDouble((gVar.f12103h * j6) + j4, 0.0d);
|
|
||||||
j6++;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
case 8:
|
|
||||||
case 9:
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Invalid array type.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package k3;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public final class f implements Runnable {
|
|
||||||
public long f;
|
|
||||||
|
|
||||||
/* renamed from: g, reason: collision with root package name */
|
|
||||||
public final long f12100g;
|
|
||||||
|
|
||||||
/* renamed from: h, reason: collision with root package name */
|
|
||||||
public final long f12101h;
|
|
||||||
|
|
||||||
public f(long j4, long j5, long j6) {
|
|
||||||
this.f = j4;
|
|
||||||
this.f12100g = j5;
|
|
||||||
this.f12101h = j6;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // java.lang.Runnable
|
|
||||||
public final void run() {
|
|
||||||
long j4 = this.f;
|
|
||||||
if (j4 != 0) {
|
|
||||||
r.f12109a.freeMemory(j4);
|
|
||||||
this.f = 0L;
|
|
||||||
long j5 = K1.a.c - (this.f12100g * this.f12101h);
|
|
||||||
K1.a.c = j5;
|
|
||||||
if (j5 < 0) {
|
|
||||||
K1.a.c = 0L;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
package k3;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public abstract class g implements Serializable, Cloneable {
|
|
||||||
public q f;
|
|
||||||
|
|
||||||
/* renamed from: g, reason: collision with root package name */
|
|
||||||
public long f12102g;
|
|
||||||
|
|
||||||
/* renamed from: h, reason: collision with root package name */
|
|
||||||
public long f12103h;
|
|
||||||
|
|
||||||
/* renamed from: i, reason: collision with root package name */
|
|
||||||
public boolean f12104i = false;
|
|
||||||
|
|
||||||
/* renamed from: j, reason: collision with root package name */
|
|
||||||
public long f12105j = 0;
|
|
||||||
|
|
||||||
public final void a(long j4) {
|
|
||||||
long j5;
|
|
||||||
g gVar = this;
|
|
||||||
long j6 = gVar.f12105j;
|
|
||||||
if (j6 != 0) {
|
|
||||||
long j7 = c.c;
|
|
||||||
double[][] dArr = g3.c.f11585a;
|
|
||||||
if (j4 <= j7) {
|
|
||||||
j7 = j4;
|
|
||||||
}
|
|
||||||
int i4 = (int) j7;
|
|
||||||
if (i4 > 2 && j4 >= c.f12095d) {
|
|
||||||
long j8 = j4 / i4;
|
|
||||||
Future[] futureArr = new Future[i4];
|
|
||||||
int i5 = 0;
|
|
||||||
while (i5 < i4) {
|
|
||||||
long j9 = i5 * j8;
|
|
||||||
if (i5 == i4 - 1) {
|
|
||||||
j5 = j4;
|
|
||||||
} else {
|
|
||||||
j5 = j9 + j8;
|
|
||||||
}
|
|
||||||
futureArr[i5] = c.a(new e(gVar, j9, j5, j6));
|
|
||||||
i5++;
|
|
||||||
gVar = gVar;
|
|
||||||
j6 = j6;
|
|
||||||
}
|
|
||||||
g gVar2 = gVar;
|
|
||||||
try {
|
|
||||||
c.b(futureArr);
|
|
||||||
return;
|
|
||||||
} catch (InterruptedException unused) {
|
|
||||||
r.f12109a.setMemory(gVar2.f12105j, gVar2.f12103h * j4, (byte) 0);
|
|
||||||
return;
|
|
||||||
} catch (ExecutionException unused2) {
|
|
||||||
r.f12109a.setMemory(gVar2.f12105j, j4 * gVar2.f12103h, (byte) 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r.f12109a.setMemory(j6, gVar.f12103h * j4, (byte) 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (obj != null && (obj instanceof g)) {
|
|
||||||
g gVar = (g) obj;
|
|
||||||
if (this.f == gVar.f && this.f12102g == gVar.f12102g && this.f12103h == gVar.f12103h && this.f12104i == gVar.f12104i && this.f12105j == gVar.f12105j) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int hashCode() {
|
|
||||||
int i4;
|
|
||||||
q qVar = this.f;
|
|
||||||
if (qVar != null) {
|
|
||||||
i4 = qVar.hashCode();
|
|
||||||
} else {
|
|
||||||
i4 = 0;
|
|
||||||
}
|
|
||||||
long j4 = this.f12102g;
|
|
||||||
int i5 = (((203 + i4) * 29) + ((int) (j4 ^ (j4 >>> 32)))) * 29;
|
|
||||||
long j5 = this.f12103h;
|
|
||||||
int i6 = (((i5 + ((int) (j5 ^ (j5 >>> 32)))) * 29) + (this.f12104i ? 1 : 0)) * 841;
|
|
||||||
long j6 = this.f12105j;
|
|
||||||
return i6 + ((int) (j6 ^ (j6 >>> 32)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package k3;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public final class l extends q {
|
|
||||||
public l() {
|
|
||||||
super("LONG", 5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package k3;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public final class m extends q {
|
|
||||||
public m() {
|
|
||||||
super("FLOAT", 6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package k3;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public final class n extends q {
|
|
||||||
public n() {
|
|
||||||
super("DOUBLE", 7);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
package k3;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* R8-generated pseudo-enum (the k3 FFT library's array-type enum).
|
|
||||||
* Direct q subclasses in smali: l (LONG/5), m (FLOAT/6), n (DOUBLE/7),
|
|
||||||
* the other constants (BYTE/UNSIGNED_BYTE/SHORT/INT/COMPLEX_FLOAT/COMPLEX_DOUBLE/STRING/OBJECT)
|
|
||||||
* are expressed directly as q instances.
|
|
||||||
*/
|
|
||||||
public class q {
|
|
||||||
public static final l f;
|
|
||||||
|
|
||||||
/* renamed from: g, reason: collision with root package name */
|
|
||||||
public static final m f12106g;
|
|
||||||
|
|
||||||
/* renamed from: h, reason: collision with root package name */
|
|
||||||
public static final n f12107h;
|
|
||||||
|
|
||||||
/* renamed from: i, reason: collision with root package name */
|
|
||||||
public static final /* synthetic */ q[] f12108i;
|
|
||||||
|
|
||||||
private final String name;
|
|
||||||
private final int ordinal;
|
|
||||||
|
|
||||||
protected q(String name, int ordinal) {
|
|
||||||
this.name = name;
|
|
||||||
this.ordinal = ordinal;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final String name() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final int ordinal() {
|
|
||||||
return ordinal;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static q valueOf(String str) {
|
|
||||||
for (q qVar : f12108i) {
|
|
||||||
if (qVar.name.equals(str)) {
|
|
||||||
return qVar;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("No enum constant k3.q." + str);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static q[] values() {
|
|
||||||
return (q[]) f12108i.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
static {
|
|
||||||
q logic = new q("LOGIC", 0);
|
|
||||||
q byteType = new q("BYTE", 1);
|
|
||||||
q unsignedByte = new q("UNSIGNED_BYTE", 2);
|
|
||||||
q shortType = new q("SHORT", 3);
|
|
||||||
q intType = new q("INT", 4);
|
|
||||||
l longType = new l();
|
|
||||||
f = longType;
|
|
||||||
m floatType = new m();
|
|
||||||
f12106g = floatType;
|
|
||||||
n doubleType = new n();
|
|
||||||
f12107h = doubleType;
|
|
||||||
q complexFloat = new q("COMPLEX_FLOAT", 8);
|
|
||||||
q complexDouble = new q("COMPLEX_DOUBLE", 9);
|
|
||||||
q string = new q("STRING", 10);
|
|
||||||
q object = new q("OBJECT", 11);
|
|
||||||
f12108i = new q[]{logic, byteType, unsignedByte, shortType, intType, longType, floatType, doubleType, complexFloat, complexDouble, string, object};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
package k3;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import com.rtbishop.look4sat.feature.cw.suncompat.Unsafe;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public abstract class r {
|
|
||||||
|
|
||||||
/* renamed from: a, reason: collision with root package name */
|
|
||||||
public static final Unsafe f12109a;
|
|
||||||
|
|
||||||
static {
|
|
||||||
Object obj = null;
|
|
||||||
try {
|
|
||||||
Class<?> cls = Class.forName("sun.misc.Unsafe");
|
|
||||||
Field declaredField = cls.getDeclaredField("theUnsafe");
|
|
||||||
declaredField.setAccessible(true);
|
|
||||||
obj = declaredField.get(cls);
|
|
||||||
} catch (ClassNotFoundException | IllegalAccessException | IllegalArgumentException | NoSuchFieldException | SecurityException ignored) {
|
|
||||||
// Android adaptation: sun.misc.Unsafe may be unavailable (hidden API); silently degrades to null;
|
|
||||||
// the large-array native path (j4 > 1GB) never triggers in real decoding
|
|
||||||
}
|
|
||||||
f12109a = (Unsafe) obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void a(long j4, long j5, long j6, d dVar, d dVar2) {
|
|
||||||
long j7;
|
|
||||||
if (j4 >= 0 && j4 < dVar.f12102g) {
|
|
||||||
if (j5 >= 0 && j5 < dVar2.f12102g) {
|
|
||||||
if (j6 >= 0) {
|
|
||||||
if (!dVar2.f12104i) {
|
|
||||||
long j8 = c.c;
|
|
||||||
double[][] dArr = g3.c.f11585a;
|
|
||||||
if (j6 <= j8) {
|
|
||||||
j8 = j6;
|
|
||||||
}
|
|
||||||
int i4 = (int) j8;
|
|
||||||
if (i4 >= 2 && j6 >= c.f12095d) {
|
|
||||||
long j9 = j6 / i4;
|
|
||||||
Future[] futureArr = new Future[i4];
|
|
||||||
int i5 = 0;
|
|
||||||
while (i5 < i4) {
|
|
||||||
long j10 = i5 * j9;
|
|
||||||
if (i5 == i4 - 1) {
|
|
||||||
j7 = j6;
|
|
||||||
} else {
|
|
||||||
j7 = j10 + j9;
|
|
||||||
}
|
|
||||||
Future[] futureArr2 = futureArr;
|
|
||||||
int i6 = i5;
|
|
||||||
futureArr2[i6] = c.a(new j3.b(j10, j7, j5, j4, dVar2, dVar));
|
|
||||||
i5 = i6 + 1;
|
|
||||||
futureArr = futureArr2;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
c.b(futureArr);
|
|
||||||
return;
|
|
||||||
} catch (InterruptedException unused) {
|
|
||||||
long j11 = j4;
|
|
||||||
long j12 = j5;
|
|
||||||
while (j11 < j4 + j6) {
|
|
||||||
dVar2.c(j12, dVar.b(j11));
|
|
||||||
j11++;
|
|
||||||
j12++;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} catch (ExecutionException unused2) {
|
|
||||||
long j13 = j4;
|
|
||||||
long j14 = j5;
|
|
||||||
while (j13 < j4 + j6) {
|
|
||||||
dVar2.c(j14, dVar.b(j13));
|
|
||||||
j13++;
|
|
||||||
j14++;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
long j15 = j4;
|
|
||||||
long j16 = j5;
|
|
||||||
while (j15 < j4 + j6) {
|
|
||||||
dVar2.c(j16, dVar.b(j15));
|
|
||||||
j15++;
|
|
||||||
j16++;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("Constant arrays cannot be modified.");
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("length < 0");
|
|
||||||
}
|
|
||||||
throw new ArrayIndexOutOfBoundsException("destPos < 0 || destPos >= dest.length()");
|
|
||||||
}
|
|
||||||
throw new ArrayIndexOutOfBoundsException("srcPos < 0 || srcPos >= src.length()");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
package k3;
|
|
||||||
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import com.rtbishop.look4sat.feature.cw.suncompat.Cleaner;
|
|
||||||
import com.rtbishop.look4sat.feature.cw.suncompat.Unsafe;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public final class s extends g {
|
|
||||||
|
|
||||||
/* renamed from: k, reason: collision with root package name */
|
|
||||||
public long[] f12110k;
|
|
||||||
|
|
||||||
public s() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public s(long j4, boolean z3) {
|
|
||||||
this.f = q.f;
|
|
||||||
this.f12103h = 8L;
|
|
||||||
if (j4 > 0) {
|
|
||||||
this.f12102g = j4;
|
|
||||||
if (j4 > 1073741824) {
|
|
||||||
this.f12105j = r.f12109a.allocateMemory(8 * j4);
|
|
||||||
if (z3) {
|
|
||||||
a(j4);
|
|
||||||
}
|
|
||||||
Cleaner.create(this, new f(this.f12105j, this.f12102g, this.f12103h));
|
|
||||||
K1.a.c += this.f12102g * this.f12103h;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.f12110k = new long[(int) j4];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException(j4 + " is not a positive long value");
|
|
||||||
}
|
|
||||||
|
|
||||||
public final long b(long j4) {
|
|
||||||
long[] jArr = this.f12110k;
|
|
||||||
long j5 = this.f12105j;
|
|
||||||
if (j5 != 0) {
|
|
||||||
return r.f12109a.getLong((this.f12103h * j4) + j5);
|
|
||||||
}
|
|
||||||
if (this.f12104i) {
|
|
||||||
return jArr[0];
|
|
||||||
}
|
|
||||||
return jArr[(int) j4];
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void c(long j4, long j5) {
|
|
||||||
long j6 = this.f12105j;
|
|
||||||
if (j6 != 0) {
|
|
||||||
r.f12109a.putLong((this.f12103h * j4) + j6, j5);
|
|
||||||
} else {
|
|
||||||
if (!this.f12104i) {
|
|
||||||
this.f12110k[(int) j4] = j5;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new IllegalAccessError("Constant arrays cannot be modified.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* JADX WARN: Type inference failed for: r0v29, types: [k3.g, java.lang.Object, k3.s] */
|
|
||||||
public final Object clone() {
|
|
||||||
long j4;
|
|
||||||
int i4 = 0;
|
|
||||||
long j5 = 0;
|
|
||||||
if (this.f12104i) {
|
|
||||||
long j6 = this.f12102g;
|
|
||||||
long b4 = b(0L);
|
|
||||||
s gVar = new s();
|
|
||||||
gVar.f = q.f12107h;
|
|
||||||
gVar.f12103h = 8L;
|
|
||||||
if (j6 > 0) {
|
|
||||||
gVar.f12102g = j6;
|
|
||||||
gVar.f12104i = true;
|
|
||||||
gVar.f12110k = new long[]{b4};
|
|
||||||
return gVar;
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException(j6 + " is not a positive long value");
|
|
||||||
}
|
|
||||||
s sVar = new s(this.f12102g, false);
|
|
||||||
long j7 = this.f12102g;
|
|
||||||
Unsafe unsafe = r.f12109a;
|
|
||||||
if (0 < j7) {
|
|
||||||
if (0 < sVar.f12102g) {
|
|
||||||
if (j7 >= 0) {
|
|
||||||
if (!sVar.f12104i) {
|
|
||||||
long j8 = c.c;
|
|
||||||
double[][] dArr = g3.c.f11585a;
|
|
||||||
if (j7 <= j8) {
|
|
||||||
j8 = j7;
|
|
||||||
}
|
|
||||||
int i5 = (int) j8;
|
|
||||||
if (i5 >= 2 && j7 >= c.f12095d) {
|
|
||||||
long j9 = j7 / i5;
|
|
||||||
Future[] futureArr = new Future[i5];
|
|
||||||
while (i4 < i5) {
|
|
||||||
long j10 = i4 * j9;
|
|
||||||
if (i4 == i5 - 1) {
|
|
||||||
j4 = j7;
|
|
||||||
} else {
|
|
||||||
j4 = j10 + j9;
|
|
||||||
}
|
|
||||||
Future[] futureArr2 = futureArr;
|
|
||||||
int i6 = i4;
|
|
||||||
futureArr2[i6] = c.a(new i3.c(j10, j4, sVar, this));
|
|
||||||
i4 = i6 + 1;
|
|
||||||
futureArr = futureArr2;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
c.b(futureArr);
|
|
||||||
return sVar;
|
|
||||||
} catch (InterruptedException unused) {
|
|
||||||
long j11 = 0;
|
|
||||||
while (j5 < j7) {
|
|
||||||
sVar.c(j11, b(j5));
|
|
||||||
j5++;
|
|
||||||
j11++;
|
|
||||||
}
|
|
||||||
} catch (ExecutionException unused2) {
|
|
||||||
long j12 = 0;
|
|
||||||
while (j5 < j7) {
|
|
||||||
sVar.c(j12, b(j5));
|
|
||||||
j5++;
|
|
||||||
j12++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
long j13 = 0;
|
|
||||||
while (j5 < j7) {
|
|
||||||
sVar.c(j13, b(j5));
|
|
||||||
j5++;
|
|
||||||
j13++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sVar;
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("Constant arrays cannot be modified.");
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("length < 0");
|
|
||||||
}
|
|
||||||
throw new ArrayIndexOutOfBoundsException("destPos < 0 || destPos >= dest.length()");
|
|
||||||
}
|
|
||||||
throw new ArrayIndexOutOfBoundsException("srcPos < 0 || srcPos >= src.length()");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // k3.g
|
|
||||||
public final boolean equals(Object obj) {
|
|
||||||
if (!super.equals(obj) || this.f12110k != ((s) obj).f12110k) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // k3.g
|
|
||||||
public final int hashCode() {
|
|
||||||
int i4;
|
|
||||||
int hashCode = super.hashCode() * 29;
|
|
||||||
long[] jArr = this.f12110k;
|
|
||||||
if (jArr != null) {
|
|
||||||
i4 = jArr.hashCode();
|
|
||||||
} else {
|
|
||||||
i4 = 0;
|
|
||||||
}
|
|
||||||
return hashCode + i4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
package pas;
|
|
||||||
|
|
||||||
import pas.system;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public class cwstru {
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public static class TMorseCharInfo extends system.Record {
|
|
||||||
public TMorseCharInfo(long j4, boolean z3) {
|
|
||||||
__Init(j4, z3);
|
|
||||||
}
|
|
||||||
|
|
||||||
private native void __Destroy(long j4);
|
|
||||||
|
|
||||||
@Override // pas.system.PascalObjectEx
|
|
||||||
public void __Release() {
|
|
||||||
__Destroy(this._pasobj);
|
|
||||||
this._pasobj = 0L;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // pas.system.Record
|
|
||||||
public int __Size() {
|
|
||||||
return __Size(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public native char getCh();
|
|
||||||
|
|
||||||
public native int getIdxE();
|
|
||||||
|
|
||||||
public native boolean getLowSnr();
|
|
||||||
|
|
||||||
public native TWordEndType getWordEnd();
|
|
||||||
|
|
||||||
public native float getWordSpaceProb();
|
|
||||||
|
|
||||||
public native TMorseWordType getWordType();
|
|
||||||
|
|
||||||
public native void setCh(char c);
|
|
||||||
|
|
||||||
public native void setIdxE(int i4);
|
|
||||||
|
|
||||||
public native void setLowSnr(boolean z3);
|
|
||||||
|
|
||||||
public native void setWordEnd(TWordEndType tWordEndType);
|
|
||||||
|
|
||||||
public native void setWordSpaceProb(float f);
|
|
||||||
|
|
||||||
public native void setWordType(TMorseWordType tMorseWordType);
|
|
||||||
|
|
||||||
public TMorseCharInfo() {
|
|
||||||
__Init(0L, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TMorseCharInfo(system.PascalObject pascalObject) {
|
|
||||||
super(pascalObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TMorseCharInfo(long j4) {
|
|
||||||
super(j4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public static class TMorseWordType extends system.Enum {
|
|
||||||
public static final int wtCall = 5;
|
|
||||||
public static final int wtCq = 3;
|
|
||||||
public static final int wtGenWord = 0;
|
|
||||||
public static final int wtKeyword = 1;
|
|
||||||
public static final int wtQrl = 4;
|
|
||||||
public static final int wtRst = 2;
|
|
||||||
|
|
||||||
public TMorseWordType(int i4) {
|
|
||||||
this.Value = i4;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TMorseWordType wtCall() {
|
|
||||||
return new TMorseWordType(5);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TMorseWordType wtCq() {
|
|
||||||
return new TMorseWordType(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TMorseWordType wtGenWord() {
|
|
||||||
return new TMorseWordType(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TMorseWordType wtKeyword() {
|
|
||||||
return new TMorseWordType(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TMorseWordType wtQrl() {
|
|
||||||
return new TMorseWordType(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TMorseWordType wtRst() {
|
|
||||||
return new TMorseWordType(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // pas.system.Enum
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (((obj instanceof TMorseWordType) && this.Value == ((TMorseWordType) obj).Value) || super.equals(obj)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public static class TWordEndType extends system.Enum {
|
|
||||||
public static final int weLong = 2;
|
|
||||||
public static final int weNone = 0;
|
|
||||||
public static final int weShort = 1;
|
|
||||||
|
|
||||||
public TWordEndType(int i4) {
|
|
||||||
this.Value = i4;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TWordEndType weLong() {
|
|
||||||
return new TWordEndType(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TWordEndType weNone() {
|
|
||||||
return new TWordEndType(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TWordEndType weShort() {
|
|
||||||
return new TWordEndType(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // pas.system.Enum
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (((obj instanceof TWordEndType) && this.Value == ((TWordEndType) obj).Value) || super.equals(obj)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static {
|
|
||||||
system.InitJni();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
package pas;
|
|
||||||
|
|
||||||
import pas.cwstru;
|
|
||||||
import pas.system;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public class decoder {
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public static class TCharInfo extends system.TObject {
|
|
||||||
public TCharInfo(system.PascalObject pascalObject) {
|
|
||||||
super(pascalObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TCharInfo Class() {
|
|
||||||
return new TCharInfo(system.GetClassRef(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static native TCharInfo Create();
|
|
||||||
|
|
||||||
public static native TCharInfo Create(char c);
|
|
||||||
|
|
||||||
public static native TCharInfo Create(cwstru.TMorseCharInfo tMorseCharInfo);
|
|
||||||
|
|
||||||
public static system.TClass TClass() {
|
|
||||||
return system.GetTClass(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public native char getChar();
|
|
||||||
|
|
||||||
public native boolean getLowSnr();
|
|
||||||
|
|
||||||
public native TMorseWordType getWordType();
|
|
||||||
|
|
||||||
public TCharInfo(long j4) {
|
|
||||||
super(j4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public static class TDecoder extends system.TObject {
|
|
||||||
public TDecoder(system.PascalObject pascalObject) {
|
|
||||||
super(pascalObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TDecoder Class() {
|
|
||||||
return new TDecoder(system.GetClassRef(2));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static native TDecoder Create();
|
|
||||||
|
|
||||||
public static system.TClass TClass() {
|
|
||||||
return system.GetTClass(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public native TCharInfo GetChar(int i4);
|
|
||||||
|
|
||||||
public native void LockToPitch(int i4);
|
|
||||||
|
|
||||||
public native int getCharCount();
|
|
||||||
|
|
||||||
public native int getDeleteCount();
|
|
||||||
|
|
||||||
public native boolean getHamMode();
|
|
||||||
|
|
||||||
public native int getPitch();
|
|
||||||
|
|
||||||
public native int getSignalCount();
|
|
||||||
|
|
||||||
public native int getSnr();
|
|
||||||
|
|
||||||
public native int getWpm();
|
|
||||||
|
|
||||||
public native void setHamMode(boolean z3);
|
|
||||||
|
|
||||||
public TDecoder(long j4) {
|
|
||||||
super(j4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public static class TDecoderState extends system.Enum {
|
|
||||||
public static final int dsDecoding = 2;
|
|
||||||
public static final int dsLockedDecoding = 4;
|
|
||||||
public static final int dsLockedPause = 3;
|
|
||||||
public static final int dsNone = 0;
|
|
||||||
public static final int dsPause = 1;
|
|
||||||
|
|
||||||
public TDecoderState(int i4) {
|
|
||||||
this.Value = i4;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TDecoderState dsDecoding() {
|
|
||||||
return new TDecoderState(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TDecoderState dsLockedDecoding() {
|
|
||||||
return new TDecoderState(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TDecoderState dsLockedPause() {
|
|
||||||
return new TDecoderState(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TDecoderState dsNone() {
|
|
||||||
return new TDecoderState(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TDecoderState dsPause() {
|
|
||||||
return new TDecoderState(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // pas.system.Enum
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (((obj instanceof TDecoderState) && this.Value == ((TDecoderState) obj).Value) || super.equals(obj)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public static class TMorseWordType extends system.Enum {
|
|
||||||
public static final int wtCall = 5;
|
|
||||||
public static final int wtCq = 3;
|
|
||||||
public static final int wtGenWord = 0;
|
|
||||||
public static final int wtKeyword = 1;
|
|
||||||
public static final int wtQrl = 4;
|
|
||||||
public static final int wtRst = 2;
|
|
||||||
|
|
||||||
public TMorseWordType(int i4) {
|
|
||||||
this.Value = i4;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TMorseWordType wtCall() {
|
|
||||||
return new TMorseWordType(5);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TMorseWordType wtCq() {
|
|
||||||
return new TMorseWordType(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TMorseWordType wtGenWord() {
|
|
||||||
return new TMorseWordType(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TMorseWordType wtKeyword() {
|
|
||||||
return new TMorseWordType(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TMorseWordType wtQrl() {
|
|
||||||
return new TMorseWordType(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TMorseWordType wtRst() {
|
|
||||||
return new TMorseWordType(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // pas.system.Enum
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (((obj instanceof TMorseWordType) && this.Value == ((TMorseWordType) obj).Value) || super.equals(obj)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static {
|
|
||||||
system.InitJni();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package pas;
|
|
||||||
|
|
||||||
import pas.decoder;
|
|
||||||
import pas.system;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public class nativedecoder {
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public static class TNativeDecoder extends decoder.TDecoder {
|
|
||||||
public TNativeDecoder(system.PascalObject pascalObject) {
|
|
||||||
super(pascalObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TNativeDecoder Class() {
|
|
||||||
return new TNativeDecoder(system.GetClassRef(3));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static native TNativeDecoder Create();
|
|
||||||
|
|
||||||
public static system.TClass TClass() {
|
|
||||||
return system.GetTClass(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
public native decoder.TDecoderState ProcessSpectrum(Object obj);
|
|
||||||
|
|
||||||
public TNativeDecoder(long j4) {
|
|
||||||
super(j4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static {
|
|
||||||
system.InitJni();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,385 +0,0 @@
|
|||||||
package pas;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.TimeZone;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public class system {
|
|
||||||
private static boolean _JniLibLoaded = false;
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public static abstract class BaseSet {
|
|
||||||
protected int Value = 0;
|
|
||||||
|
|
||||||
public abstract int Base();
|
|
||||||
|
|
||||||
public abstract int ElMax();
|
|
||||||
|
|
||||||
public abstract byte Size();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public static class Enum {
|
|
||||||
public int Value;
|
|
||||||
|
|
||||||
public int Ord() {
|
|
||||||
return this.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
return (obj instanceof Integer) && this.Value == ((Integer) obj).intValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int hashCode() {
|
|
||||||
return this.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean equals(int i4) {
|
|
||||||
return this.Value == i4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public static class MethodPtr extends PascalObjectEx {
|
|
||||||
protected String mName;
|
|
||||||
protected Object mObject;
|
|
||||||
protected String mSignature;
|
|
||||||
|
|
||||||
public MethodPtr() {
|
|
||||||
this._cleanup = true;
|
|
||||||
this._pasobj = -1L;
|
|
||||||
}
|
|
||||||
|
|
||||||
private native void __Destroy();
|
|
||||||
|
|
||||||
private native void __Init(Object obj, String str, String str2, boolean z3);
|
|
||||||
|
|
||||||
public void Init() {
|
|
||||||
boolean z3;
|
|
||||||
Object obj = this.mObject;
|
|
||||||
String str = this.mName;
|
|
||||||
String str2 = this.mSignature;
|
|
||||||
if (this != obj) {
|
|
||||||
z3 = true;
|
|
||||||
} else {
|
|
||||||
z3 = false;
|
|
||||||
}
|
|
||||||
__Init(obj, str, str2, z3);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // pas.system.PascalObjectEx
|
|
||||||
public void __Release() {
|
|
||||||
if (this._pasobj > 0) {
|
|
||||||
__Destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public static abstract class PascalInterface extends PascalObjectEx {
|
|
||||||
public PascalInterface(long j4, boolean z3) {
|
|
||||||
this._pasobj = j4;
|
|
||||||
__Init();
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void __Init();
|
|
||||||
|
|
||||||
public void __TypeCast(PascalObject pascalObject, String str) {
|
|
||||||
if (pascalObject != null) {
|
|
||||||
if (pascalObject instanceof PascalInterface) {
|
|
||||||
this._pasobj = pascalObject._pasobj;
|
|
||||||
__Init();
|
|
||||||
} else {
|
|
||||||
this._pasobj = system.InterfaceCast(pascalObject._pasobj, str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public static class Record extends PascalObjectEx {
|
|
||||||
protected PascalObject _objref;
|
|
||||||
|
|
||||||
public Record(PascalObject pascalObject) {
|
|
||||||
super(pascalObject);
|
|
||||||
this._objref = pascalObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void __Init(long j4, boolean z3) {
|
|
||||||
this._pasobj = j4;
|
|
||||||
this._cleanup = z3;
|
|
||||||
if (j4 == 0 && __Size() != 0) {
|
|
||||||
this._pasobj = system.AllocMemory(__Size());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int __Size() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // pas.system.PascalObjectEx, pas.system.PascalObject
|
|
||||||
public void finalize() {
|
|
||||||
long j4 = this._pasobj;
|
|
||||||
if (j4 < 0) {
|
|
||||||
this._pasobj = -j4;
|
|
||||||
this._cleanup = true;
|
|
||||||
}
|
|
||||||
super.finalize();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Record(long j4) {
|
|
||||||
super(j4);
|
|
||||||
}
|
|
||||||
|
|
||||||
public final int __Size(int i4) {
|
|
||||||
return system.GetRecordSize(i4);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Record() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public static abstract class Set<TS extends BaseSet, TE extends Enum> extends BaseSet {
|
|
||||||
public Set() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Assign(TS ts) {
|
|
||||||
this.Value = ts.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Exclude(TE... teArr) {
|
|
||||||
for (TE te : teArr) {
|
|
||||||
this.Value = (~GetMask(te)) & this.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetMask(TE te) {
|
|
||||||
return 1 << (te.Ord() - Base());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean Has(TE te) {
|
|
||||||
if ((GetMask(te) & this.Value) != 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Include(TE... teArr) {
|
|
||||||
for (TE te : teArr) {
|
|
||||||
this.Value = GetMask(te) | this.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Intersect(TS ts) {
|
|
||||||
this.Value = ts.Value & this.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean IsEmpty() {
|
|
||||||
if (this.Value == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean equals(TS ts) {
|
|
||||||
return this.Value == ts.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set(TE... teArr) {
|
|
||||||
Include(teArr);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Exclude(TS ts) {
|
|
||||||
this.Value = (~ts.Value) & this.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Include(TS ts) {
|
|
||||||
this.Value = ts.Value | this.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean equals(TE te) {
|
|
||||||
return this.Value == te.Ord();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set(TS... tsArr) {
|
|
||||||
for (TS ts : tsArr) {
|
|
||||||
Include(ts); // smali: Set(TS...) 循环调 Include(BaseSet)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean equals(int i4) {
|
|
||||||
return this.Value == i4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public static class TClass extends TObject {
|
|
||||||
public TClass(PascalObject pascalObject) {
|
|
||||||
super(pascalObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public static class TDateTime {
|
|
||||||
public static double get(Date date) {
|
|
||||||
return ((date.getTime() + TimeZone.getDefault().getOffset(date.getTime())) / 8.64E7d) + 25569.0d;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double getUTC(Date date) {
|
|
||||||
return (date.getTime() / 8.64E7d) + 25569.0d;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Date toDate(double d4) {
|
|
||||||
return new Date(Math.round((d4 - 25569.0d) * 8.64E7d) - TimeZone.getDefault().getOffset(Math.round((d4 - 25569.0d) * 8.64E7d))); // r3 = 前值(smali)
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Date toDateUTC(double d4) {
|
|
||||||
return new Date(Math.round((d4 - 25569.0d) * 8.64E7d));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public static class TObject extends PascalObject {
|
|
||||||
public TObject(PascalObject pascalObject) {
|
|
||||||
super(pascalObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TObject Class() {
|
|
||||||
return new TObject(system.GetClassRef(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static native TObject Create();
|
|
||||||
|
|
||||||
public static TClass TClass() {
|
|
||||||
return system.GetTClass(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public native String ClassName();
|
|
||||||
|
|
||||||
public native boolean ClassNameIs(String str);
|
|
||||||
|
|
||||||
public native TClass ClassParent();
|
|
||||||
|
|
||||||
public native TClass ClassType();
|
|
||||||
|
|
||||||
public native void Destroy();
|
|
||||||
|
|
||||||
public native void Free();
|
|
||||||
|
|
||||||
public native boolean InheritsFrom(TClass tClass);
|
|
||||||
|
|
||||||
public native int InstanceSize();
|
|
||||||
|
|
||||||
public TObject(long j4) {
|
|
||||||
super(j4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static {
|
|
||||||
InitJni();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static native long AllocMemory(int i4);
|
|
||||||
|
|
||||||
public static native long GetClassRef(int i4);
|
|
||||||
|
|
||||||
public static native byte[] GetMemoryAsArray(long j4, int i4);
|
|
||||||
|
|
||||||
/* JADX INFO: Access modifiers changed from: private */
|
|
||||||
public static native int GetRecordSize(int i4);
|
|
||||||
|
|
||||||
public static TClass GetTClass(int i4) {
|
|
||||||
TClass tClass = new TClass(null);
|
|
||||||
tClass._pasobj = GetClassRef(i4);
|
|
||||||
return tClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void InitJni() {
|
|
||||||
if (!_JniLibLoaded) {
|
|
||||||
_JniLibLoaded = true;
|
|
||||||
System.loadLibrary("nativedecoderjni");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* JADX INFO: Access modifiers changed from: private */
|
|
||||||
public static native long InterfaceCast(long j4, String str);
|
|
||||||
|
|
||||||
public static long Pointer(PascalObject pascalObject) {
|
|
||||||
if (pascalObject == null) {
|
|
||||||
return 0L;
|
|
||||||
}
|
|
||||||
return pascalObject._pasobj;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static native void SetMemoryFromArray(long j4, byte[] bArr);
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public static class PascalObject {
|
|
||||||
protected long _pasobj;
|
|
||||||
|
|
||||||
static {
|
|
||||||
system.InitJni();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PascalObject() {
|
|
||||||
this._pasobj = 0L;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if ((obj instanceof PascalObject) && this._pasobj == ((PascalObject) obj)._pasobj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void finalize() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public int hashCode() {
|
|
||||||
return (int) this._pasobj;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PascalObject(PascalObject pascalObject) {
|
|
||||||
this._pasobj = 0L;
|
|
||||||
if (pascalObject != null) {
|
|
||||||
this._pasobj = pascalObject._pasobj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PascalObject(long j4) {
|
|
||||||
this._pasobj = j4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public static class PascalObjectEx extends PascalObject {
|
|
||||||
protected boolean _cleanup;
|
|
||||||
|
|
||||||
public PascalObjectEx() {
|
|
||||||
this._cleanup = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void __Release() {
|
|
||||||
this._pasobj = 0L;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // pas.system.PascalObject
|
|
||||||
public void finalize() {
|
|
||||||
if (this._cleanup) {
|
|
||||||
__Release();
|
|
||||||
}
|
|
||||||
super.finalize();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PascalObjectEx(PascalObject pascalObject) {
|
|
||||||
super(pascalObject);
|
|
||||||
this._cleanup = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PascalObjectEx(long j4) {
|
|
||||||
super(j4);
|
|
||||||
this._cleanup = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package s;
|
|
||||||
|
|
||||||
/* renamed from: s.e, reason: case insensitive filesystem */
|
|
||||||
/* loaded from: classes.dex */
|
|
||||||
public abstract /* synthetic */ class AbstractC1880e {
|
|
||||||
|
|
||||||
/* renamed from: a, reason: collision with root package name */
|
|
||||||
public static final /* synthetic */ int[] f13163a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28};
|
|
||||||
|
|
||||||
public static /* synthetic */ int a(int i4) {
|
|
||||||
if (i4 != 0) {
|
|
||||||
return i4 - 1;
|
|
||||||
}
|
|
||||||
throw null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -1,11 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:height="24dp"
|
|
||||||
android:width="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:height="24dp"
|
|
||||||
android:width="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:height="24dp"
|
|
||||||
android:width="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:height="24dp"
|
|
||||||
android:width="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:id="@+id/verticalLayout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:keepScreenOn="true">
|
|
||||||
<TextView
|
|
||||||
android:textColor="#ffffff"
|
|
||||||
android:id="@+id/statusTextView"
|
|
||||||
android:background="@color/purple_500"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/idle"/>
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="100dp">
|
|
||||||
<com.ve3nea.morse_expert.WaterfallSurfaceView
|
|
||||||
android:id="@+id/waterfallView"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:contentDescription="@string/waterfall_display"/>
|
|
||||||
<com.ve3nea.morse_expert.ScaleView
|
|
||||||
android:id="@+id/scaleView"
|
|
||||||
android:background="#24545a"
|
|
||||||
android:layout_width="12dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="0"
|
|
||||||
android:contentDescription="@string/frequency_scale"/>
|
|
||||||
</LinearLayout>
|
|
||||||
<com.ve3nea.morse_expert.DecodedTextView
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:typeface="monospace"
|
|
||||||
android:textColor="#444444"
|
|
||||||
android:textColorHint="#888888"
|
|
||||||
android:gravity="start|top"
|
|
||||||
android:id="@+id/decodedTextView"
|
|
||||||
android:background="#d1f0f4"
|
|
||||||
android:scrollbars="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:hint="@string/decoded_text"
|
|
||||||
android:freezesText="false"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:inputType="none"
|
|
||||||
android:fadeScrollbars="false"
|
|
||||||
android:textIsSelectable="true"/>
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:id="@+id/verticalLayout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:keepScreenOn="true">
|
|
||||||
<TextView
|
|
||||||
android:textColor="#ffffff"
|
|
||||||
android:textSize="10sp"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:id="@+id/statusTextView"
|
|
||||||
android:background="@color/purple_500"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/idle"/>
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="80dp">
|
|
||||||
<com.ve3nea.morse_expert.WaterfallSurfaceView
|
|
||||||
android:id="@+id/waterfallView"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:contentDescription="@string/waterfall_display"/>
|
|
||||||
<com.ve3nea.morse_expert.ScaleView
|
|
||||||
android:id="@+id/scaleView"
|
|
||||||
android:background="#24545a"
|
|
||||||
android:layout_width="8dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="0"
|
|
||||||
android:contentDescription="@string/frequency_scale"/>
|
|
||||||
</LinearLayout>
|
|
||||||
<com.ve3nea.morse_expert.DecodedTextView
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:typeface="monospace"
|
|
||||||
android:textColor="#444444"
|
|
||||||
android:textColorHint="#888888"
|
|
||||||
android:gravity="start|top"
|
|
||||||
android:id="@+id/decodedTextView"
|
|
||||||
android:background="#d1f0f4"
|
|
||||||
android:scrollbars="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:hint="@string/decoded_text"
|
|
||||||
android:freezesText="false"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:minHeight="48dp"
|
|
||||||
android:inputType="none"
|
|
||||||
android:fadeScrollbars="false"
|
|
||||||
android:textIsSelectable="true"/>
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
||||||
<item
|
|
||||||
android:icon="@drawable/ic_baseline_pause_24"
|
|
||||||
android:id="@+id/pause_mnu"
|
|
||||||
android:title="@string/pause"
|
|
||||||
app:showAsAction="ifRoom"/>
|
|
||||||
<item
|
|
||||||
android:icon="@drawable/ic_baseline_delete_24"
|
|
||||||
android:id="@+id/clear_mnu"
|
|
||||||
android:title="@string/clear"
|
|
||||||
app:showAsAction="ifRoom"/>
|
|
||||||
<item
|
|
||||||
android:icon="@drawable/ic_baseline_save_24"
|
|
||||||
android:id="@+id/save_mnu"
|
|
||||||
android:title="@string/save_text"
|
|
||||||
app:showAsAction="ifRoom"/>
|
|
||||||
<item
|
|
||||||
android:id="@+id/record_signals_mnu"
|
|
||||||
android:title="@string/record_signals"/>
|
|
||||||
<item
|
|
||||||
android:id="@+id/settings_mnu"
|
|
||||||
android:title="@string/settings"/>
|
|
||||||
<item
|
|
||||||
android:id="@+id/help_mnu"
|
|
||||||
android:title="@string/help"/>
|
|
||||||
<item
|
|
||||||
android:id="@+id/rate_mnu"
|
|
||||||
android:title="@string/rate"/>
|
|
||||||
<item
|
|
||||||
android:id="@+id/premium_mnu"
|
|
||||||
android:title="@string/premium"/>
|
|
||||||
</menu>
|
|
||||||
@@ -8,8 +8,7 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":feature:mutual"))
|
implementation(project(":feature:mutual"))
|
||||||
// CW 解码面板: 复用 feature:cw 的 Morse Expert 引擎(布局 cw_panel_main + MainActivity 控制器)
|
// CW 解码面板: 复用 feature:cw 的 fldigi 解码引擎(纯 Compose 面板)
|
||||||
implementation(project(":feature:cw"))
|
implementation(project(":feature:cw"))
|
||||||
// 与 feature:cw 一致(控制器按 ID 递归查找 ConstraintLayout 视图)
|
|
||||||
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
|
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
|
||||||
}
|
}
|
||||||
+255
-47
@@ -17,11 +17,13 @@
|
|||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.feature.radar
|
package com.rtbishop.look4sat.feature.radar
|
||||||
|
|
||||||
import android.app.Activity
|
import android.media.AudioFormat
|
||||||
import android.view.LayoutInflater
|
import android.media.AudioRecord
|
||||||
|
import android.media.MediaRecorder
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.expandVertically
|
import androidx.compose.animation.expandVertically
|
||||||
import androidx.compose.animation.shrinkVertically
|
import androidx.compose.animation.shrinkVertically
|
||||||
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
@@ -41,6 +43,7 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.FilterChip
|
import androidx.compose.material3.FilterChip
|
||||||
@@ -53,35 +56,46 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.rotate
|
import androidx.compose.ui.draw.rotate
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.geometry.Size
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import com.rtbishop.look4sat.core.domain.cw.CwFldigiDecoder
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
import com.rtbishop.look4sat.core.domain.model.SatRadio
|
import com.rtbishop.look4sat.core.domain.model.SatRadio
|
||||||
import com.rtbishop.look4sat.core.domain.predict.OrbitalPos
|
import com.rtbishop.look4sat.core.domain.predict.OrbitalPos
|
||||||
import com.rtbishop.look4sat.core.domain.utility.DopplerFrequencyCalculator
|
import com.rtbishop.look4sat.core.domain.utility.DopplerFrequencyCalculator
|
||||||
import com.rtbishop.look4sat.core.presentation.CardButton
|
import com.rtbishop.look4sat.core.presentation.CardButton
|
||||||
|
import kotlin.math.roundToInt
|
||||||
import com.rtbishop.look4sat.core.presentation.R
|
import com.rtbishop.look4sat.core.presentation.R
|
||||||
import com.rtbishop.look4sat.core.presentation.formatFrequency
|
import com.rtbishop.look4sat.core.presentation.formatFrequency
|
||||||
import com.rtbishop.look4sat.core.presentation.infiniteMarquee
|
import com.rtbishop.look4sat.core.presentation.infiniteMarquee
|
||||||
import com.rtbishop.look4sat.feature.cw.R as CwR
|
|
||||||
import com.ve3nea.morse_expert.MainActivity
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
@@ -714,33 +728,47 @@ private fun CwDecoderPanel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
AnimatedVisibility(visible = cw.isExpanded) {
|
AnimatedVisibility(visible = cw.isExpanded) {
|
||||||
// PR #1 Morse Expert engine: mini layout with waterfall + decoded text.
|
// fldigi engine: pure Compose panel (waterfall + decoded text).
|
||||||
// Keep the old Kotlin decoder state/actions as fallback code, but this panel no longer feeds it.
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val activity = remember { context as? Activity }
|
val decoder = remember { CwFldigiDecoder(initialWpm = 18) }
|
||||||
val controller = remember { MainActivity() }
|
val waterfall = remember { CwPanelWaterfallState() }
|
||||||
val rootView = remember {
|
|
||||||
LayoutInflater.from(context).inflate(CwR.layout.cw_panel_main, null) as ConstraintLayout
|
|
||||||
}
|
|
||||||
var initialized by remember { mutableStateOf(false) }
|
|
||||||
var listening by remember { mutableStateOf(false) }
|
var listening by remember { mutableStateOf(false) }
|
||||||
|
var captureJob by remember { mutableStateOf<Job?>(null) }
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val decodedText by decoder.decodedTextFlow.collectAsState()
|
||||||
|
val signalStrength by decoder.signalStrength.collectAsState()
|
||||||
|
|
||||||
|
fun startPanelCapture() {
|
||||||
|
if (!cw.hasPermission) {
|
||||||
|
requestMicPermission()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
captureJob?.cancel()
|
||||||
|
captureJob = scope.launch(Dispatchers.IO) {
|
||||||
|
val capture = CwPanelMicCapture()
|
||||||
|
capture.audioFlow().collect { chunk ->
|
||||||
|
if (!isActive) return@collect
|
||||||
|
decoder.processBuffer(chunk)
|
||||||
|
waterfall.pushSamples(chunk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
listening = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopPanelCapture() {
|
||||||
|
captureJob?.cancel()
|
||||||
|
captureJob = null
|
||||||
|
listening = false
|
||||||
|
}
|
||||||
|
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
if (activity != null) {
|
onDispose { stopPanelCapture() }
|
||||||
controller.onCreate(activity, rootView, false)
|
|
||||||
}
|
|
||||||
onDispose {
|
|
||||||
controller.onPause()
|
|
||||||
controller.onDestroy()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(cw.hasPermission) {
|
LaunchedEffect(cw.hasPermission) {
|
||||||
if (cw.hasPermission && !initialized) {
|
if (cw.hasPermission && !listening) {
|
||||||
controller.onPermissionGranted()
|
startPanelCapture()
|
||||||
controller.onResume()
|
|
||||||
initialized = true
|
|
||||||
listening = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -751,48 +779,61 @@ private fun CwDecoderPanel(
|
|||||||
) {
|
) {
|
||||||
if (!listening) {
|
if (!listening) {
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = { startPanelCapture() },
|
||||||
if (!cw.hasPermission) {
|
|
||||||
requestMicPermission()
|
|
||||||
} else if (!initialized) {
|
|
||||||
controller.onPermissionGranted()
|
|
||||||
controller.onResume()
|
|
||||||
initialized = true
|
|
||||||
listening = true
|
|
||||||
} else {
|
|
||||||
controller.onResume()
|
|
||||||
listening = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.radar_cw_start))
|
Text(stringResource(R.string.radar_cw_start))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = { stopPanelCapture() },
|
||||||
controller.onPause()
|
|
||||||
listening = false
|
|
||||||
},
|
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.radar_cw_stop))
|
Text(stringResource(R.string.radar_cw_stop))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = { controller.clearDecoded() },
|
onClick = { decoder.resetDecoder(); waterfall.reset() },
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.radar_cw_reset))
|
Text(stringResource(R.string.radar_cw_reset))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AndroidView(
|
Box(
|
||||||
factory = { rootView },
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(150.dp)
|
.height(90.dp)
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
) {
|
||||||
|
CwPanelWaterfallView(state = waterfall)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = decodedText.takeLast(120),
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = if (listening) "● ACTIVE" else "○ IDLE",
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = if (listening) Color(0xFF4CAF50) else MaterialTheme.colorScheme.outline
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "LEVEL ${(signalStrength * 100).roundToInt()}%",
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -821,3 +862,170 @@ private fun transceiverTitle(radio: SatRadio): String {
|
|||||||
|
|
||||||
private val FREQ_ADJUSTMENTS =
|
private val FREQ_ADJUSTMENTS =
|
||||||
listOf(-10_000L to "-10k", -1_000L to "-1k", -100L to "-100", 100L to "+100", 1_000L to "+1k", 10_000L to "+10k")
|
listOf(-10_000L to "-10k", -1_000L to "-1k", -100L to "-100", 100L to "+100", 1_000L to "+1k", 10_000L to "+10k")
|
||||||
|
|
||||||
|
// --- CW panel components (fldigi engine, pure Compose) ---
|
||||||
|
|
||||||
|
/** Microphone capture at 8000 Hz mono PCM float (fldigi sample rate). */
|
||||||
|
private class CwPanelMicCapture {
|
||||||
|
private val sampleRate = 8000
|
||||||
|
private val channelConfig = AudioFormat.CHANNEL_IN_MONO
|
||||||
|
private val audioFormat = AudioFormat.ENCODING_PCM_FLOAT
|
||||||
|
|
||||||
|
fun audioFlow(): Flow<FloatArray> = flow {
|
||||||
|
val bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)
|
||||||
|
.coerceAtLeast(sampleRate / 5)
|
||||||
|
val recorder = AudioRecord(
|
||||||
|
MediaRecorder.AudioSource.MIC,
|
||||||
|
sampleRate,
|
||||||
|
channelConfig,
|
||||||
|
audioFormat,
|
||||||
|
bufferSize * 4
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
recorder.startRecording()
|
||||||
|
val chunkSize = sampleRate / 10
|
||||||
|
val buffer = FloatArray(chunkSize)
|
||||||
|
while (currentCoroutineContext().isActive) {
|
||||||
|
val read = recorder.read(buffer, 0, chunkSize, AudioRecord.READ_BLOCKING)
|
||||||
|
if (read > 0) {
|
||||||
|
emit(if (read == chunkSize) buffer.copyOf() else buffer.copyOfRange(0, read))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
recorder.stop()
|
||||||
|
recorder.release()
|
||||||
|
}
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sliding spectrogram ring buffer for the CW waterfall. */
|
||||||
|
private class CwPanelWaterfallState {
|
||||||
|
private val columns = 120
|
||||||
|
private val rows = 48
|
||||||
|
private val data = FloatArray(columns * rows)
|
||||||
|
private val pending = ArrayList<Float>(4096)
|
||||||
|
private val fftWindow = 512
|
||||||
|
|
||||||
|
/** Monotonic frame counter — reading this in composition triggers redraw. */
|
||||||
|
private val _version = androidx.compose.runtime.mutableIntStateOf(0)
|
||||||
|
val version: androidx.compose.runtime.State<Int> = _version
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun pushSamples(samples: FloatArray) {
|
||||||
|
pending.addAll(samples.toList())
|
||||||
|
var frames = 0
|
||||||
|
while (pending.size >= fftWindow) {
|
||||||
|
val window = FloatArray(fftWindow) { pending[it] }
|
||||||
|
repeat(fftWindow) { pending.removeAt(0) }
|
||||||
|
val spectrum = computeSpectrum(window)
|
||||||
|
for (r in 0 until rows - 1) {
|
||||||
|
System.arraycopy(data, (r + 1) * columns, data, r * columns, columns)
|
||||||
|
}
|
||||||
|
for (c in 0 until columns) {
|
||||||
|
val bin = c * spectrum.size / columns
|
||||||
|
data[(rows - 1) * columns + c] = spectrum[bin.coerceAtMost(spectrum.size - 1)]
|
||||||
|
}
|
||||||
|
frames++
|
||||||
|
}
|
||||||
|
if (frames > 0) _version.intValue++
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun computeSpectrum(window: FloatArray): FloatArray {
|
||||||
|
val n = window.size
|
||||||
|
val re = FloatArray(n)
|
||||||
|
val im = FloatArray(n)
|
||||||
|
for (i in 0 until n) {
|
||||||
|
val w = 0.5 - 0.5 * kotlin.math.cos(2.0 * Math.PI * i / (n - 1))
|
||||||
|
re[i] = window[i] * w.toFloat()
|
||||||
|
}
|
||||||
|
var j = 0
|
||||||
|
for (i in 1 until n) {
|
||||||
|
var bit = n shr 1
|
||||||
|
while (j and bit != 0) { j = j xor bit; bit = bit shr 1 }
|
||||||
|
j = j xor bit
|
||||||
|
if (i < j) {
|
||||||
|
val tr = re[i]; re[i] = re[j]; re[j] = tr
|
||||||
|
val ti = im[i]; im[i] = im[j]; im[j] = ti
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var len = 2
|
||||||
|
while (len <= n) {
|
||||||
|
val ang = -2.0 * Math.PI / len
|
||||||
|
val wRe = kotlin.math.cos(ang).toFloat()
|
||||||
|
val wIm = kotlin.math.sin(ang).toFloat()
|
||||||
|
var i = 0
|
||||||
|
while (i < n) {
|
||||||
|
var curRe = 1f
|
||||||
|
var curIm = 0f
|
||||||
|
for (k in 0 until len / 2) {
|
||||||
|
val uRe = re[i + k]
|
||||||
|
val uIm = im[i + k]
|
||||||
|
val vRe = re[i + k + len / 2] * curRe - im[i + k + len / 2] * curIm
|
||||||
|
val vIm = re[i + k + len / 2] * curIm + im[i + k + len / 2] * curRe
|
||||||
|
re[i + k] = uRe + vRe
|
||||||
|
im[i + k] = uIm + vIm
|
||||||
|
re[i + k + len / 2] = uRe - vRe
|
||||||
|
im[i + k + len / 2] = uIm - vIm
|
||||||
|
val nRe = curRe * wRe - curIm * wIm
|
||||||
|
val nIm = curRe * wIm + curIm * wRe
|
||||||
|
curRe = nRe; curIm = nIm
|
||||||
|
}
|
||||||
|
i += len
|
||||||
|
}
|
||||||
|
len = len shl 1
|
||||||
|
}
|
||||||
|
val bins = 48
|
||||||
|
val out = FloatArray(bins)
|
||||||
|
for (k in 0 until bins) {
|
||||||
|
out[k] = kotlin.math.sqrt(re[k] * re[k] + im[k] * im[k]) / n
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun getColumn(c: Int): FloatArray {
|
||||||
|
val col = FloatArray(rows)
|
||||||
|
for (r in 0 until rows) {
|
||||||
|
col[r] = data[r * columns + c.coerceIn(0, columns - 1)]
|
||||||
|
}
|
||||||
|
return col
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun reset() {
|
||||||
|
data.fill(0f)
|
||||||
|
pending.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CwPanelWaterfallView(state: CwPanelWaterfallState) {
|
||||||
|
val columns = 120
|
||||||
|
val rows = 48
|
||||||
|
// Read the frame counter so the canvas redraws as new spectra arrive.
|
||||||
|
state.version.value
|
||||||
|
Canvas(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(Color(0xFF0D1B2A))
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
) {
|
||||||
|
val cellW = size.width / columns
|
||||||
|
val cellH = size.height / rows
|
||||||
|
for (c in 0 until columns) {
|
||||||
|
val col = state.getColumn(c)
|
||||||
|
for (r in 0 until rows) {
|
||||||
|
val v = col[r]
|
||||||
|
val intensity = (v * 4000f).coerceIn(0f, 255f)
|
||||||
|
if (intensity > 6f) {
|
||||||
|
val green = (intensity * 1.3f).coerceAtMost(255f)
|
||||||
|
drawRect(
|
||||||
|
color = Color(0f, green / 255f, 0f, 1f),
|
||||||
|
topLeft = Offset(c * cellW, r * cellH),
|
||||||
|
size = Size(cellW + 0.5f, cellH + 0.5f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
[versions]
|
[versions]
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
appVersionCode = "458"
|
appVersionCode = "459"
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
appVersionName = "4.5.5"
|
appVersionName = "4.5.6"
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
compileSdk = "37"
|
compileSdk = "37"
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
|
|||||||
Reference in new issue
Block a user