Compare commits
27
Commits
v4.5.6
...
feat/cw-deepcw
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1668f1c27 | ||
|
|
d6b61eaa4b | ||
|
|
26c714fc24 | ||
|
|
2e1d8b9c00 | ||
|
|
f42d1f6c57 | ||
|
|
3e25e1aa2f | ||
|
|
e0405b541f | ||
|
|
c374058e1d | ||
|
|
ac5cbbc49a | ||
|
|
cf49dcfa01 | ||
|
|
5f297f9233 | ||
|
|
37300fb2a6 | ||
|
|
fc8096bdd5 | ||
|
|
d93e3b4028 | ||
|
|
9b0d543f12 | ||
|
|
57762613a8 | ||
|
|
13ef70810c | ||
|
|
6a62f951ad | ||
|
|
0d3e02c596 | ||
|
|
7c22bf6ac1 | ||
|
|
b8113fc757 | ||
|
|
86ab1b26b7 | ||
|
|
de6c964136 | ||
|
|
e477851adf | ||
|
|
d2d4a6fe86 | ||
|
|
284183836e | ||
|
|
de85aa1e8b |
No files matched your search
@@ -40,10 +40,14 @@ captures/
|
|||||||
|
|
||||||
# Keystore files
|
# Keystore files
|
||||||
*.jks
|
*.jks
|
||||||
|
*.keystore
|
||||||
/*.properties
|
/*.properties
|
||||||
/keystore.properties
|
/keystore.properties
|
||||||
/app/keystore.jks
|
/app/keystore.jks
|
||||||
|
|
||||||
|
# Hermes agent workspace (plans, local notes)
|
||||||
|
.hermes/
|
||||||
|
|
||||||
# External native build folder generated in Android Studio 2.2 and later
|
# External native build folder generated in Android Studio 2.2 and later
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,29 @@
|
|||||||
GNU GENERAL PUBLIC LICENSE
|
LOOK4SAT (MCKERO6423 FORK) — LICENSING NOTICE
|
||||||
Version 3, 29 June 2007
|
|
||||||
|
This repository combines two separately-licensed components:
|
||||||
|
|
||||||
|
1. Look4Sat application code (all modules except the DeepCW model)
|
||||||
|
— Copyright (C) 2019-2026 Arty Bishop (rt-bishop) and contributors
|
||||||
|
— Licensed under the GNU General Public License v3.0 (GPL-3.0)
|
||||||
|
|
||||||
|
2. The DeepCW neural decoding model in feature/cw/src/main/assets/deepcw/
|
||||||
|
— Copyright (C) e04 (https://github.com/e04/deepcw-engine)
|
||||||
|
— Licensed under the GNU Affero General Public License v3.0 only
|
||||||
|
(AGPL-3.0-only)
|
||||||
|
|
||||||
|
Because this combined work incorporates an AGPL-3.0 component, it is
|
||||||
|
distributed under the GNU Affero General Public License v3.0. GPL-3.0
|
||||||
|
Section 13 permits this combination; AGPL-3.0 Section 13 applies to the
|
||||||
|
combined work as a whole.
|
||||||
|
|
||||||
|
See feature/cw/licenses/NOTICE.md for model provenance, attribution, and
|
||||||
|
the applied int8 quantization. The original GPL-3.0 text for the Look4Sat
|
||||||
|
application code is preserved at feature/cw/licenses/Look4Sat-GPL-3.0.txt.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
@@ -7,17 +31,15 @@
|
|||||||
|
|
||||||
Preamble
|
Preamble
|
||||||
|
|
||||||
The GNU General Public License is a free, copyleft license for
|
The GNU Affero General Public License is a free, copyleft license for
|
||||||
software and other kinds of works.
|
software and other kinds of works, specifically designed to ensure
|
||||||
|
cooperation with the community in the case of network server software.
|
||||||
|
|
||||||
The licenses for most software and other practical works are designed
|
The licenses for most software and other practical works are designed
|
||||||
to take away your freedom to share and change the works. By contrast,
|
to take away your freedom to share and change the works. By contrast,
|
||||||
the GNU General Public License is intended to guarantee your freedom to
|
our General Public Licenses are intended to guarantee your freedom to
|
||||||
share and change all versions of a program--to make sure it remains free
|
share and change all versions of a program--to make sure it remains free
|
||||||
software for all its users. We, the Free Software Foundation, use the
|
software for all its users.
|
||||||
GNU General Public License for most of our software; it applies also to
|
|
||||||
any other work released this way by its authors. You can apply it to
|
|
||||||
your programs, too.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
When we speak of free software, we are referring to freedom, not
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
@@ -26,44 +48,34 @@ them if you wish), that you receive source code or can get it if you
|
|||||||
want it, that you can change the software or use pieces of it in new
|
want it, that you can change the software or use pieces of it in new
|
||||||
free programs, and that you know you can do these things.
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
To protect your rights, we need to prevent others from denying you
|
Developers that use our General Public Licenses protect your rights
|
||||||
these rights or asking you to surrender the rights. Therefore, you have
|
with two steps: (1) assert copyright on the software, and (2) offer
|
||||||
certain responsibilities if you distribute copies of the software, or if
|
you this License which gives you legal permission to copy, distribute
|
||||||
you modify it: responsibilities to respect the freedom of others.
|
and/or modify the software.
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether
|
A secondary benefit of defending all users' freedom is that
|
||||||
gratis or for a fee, you must pass on to the recipients the same
|
improvements made in alternate versions of the program, if they
|
||||||
freedoms that you received. You must make sure that they, too, receive
|
receive widespread use, become available for other developers to
|
||||||
or can get the source code. And you must show them these terms so they
|
incorporate. Many developers of free software are heartened and
|
||||||
know their rights.
|
encouraged by the resulting cooperation. However, in the case of
|
||||||
|
software used on network servers, this result may fail to come about.
|
||||||
|
The GNU General Public License permits making a modified version and
|
||||||
|
letting the public access it on a server without ever releasing its
|
||||||
|
source code to the public.
|
||||||
|
|
||||||
Developers that use the GNU GPL protect your rights with two steps:
|
The GNU Affero General Public License is designed specifically to
|
||||||
(1) assert copyright on the software, and (2) offer you this License
|
ensure that, in such cases, the modified source code becomes available
|
||||||
giving you legal permission to copy, distribute and/or modify it.
|
to the community. It requires the operator of a network server to
|
||||||
|
provide the source code of the modified version running there to the
|
||||||
|
users of that server. Therefore, public use of a modified version, on
|
||||||
|
a publicly accessible server, gives the public access to the source
|
||||||
|
code of the modified version.
|
||||||
|
|
||||||
For the developers' and authors' protection, the GPL clearly explains
|
An older license, called the Affero General Public License and
|
||||||
that there is no warranty for this free software. For both users' and
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
authors' sake, the GPL requires that modified versions be marked as
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
changed, so that their problems will not be attributed erroneously to
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
authors of previous versions.
|
this license.
|
||||||
|
|
||||||
Some devices are designed to deny users access to install or run
|
|
||||||
modified versions of the software inside them, although the manufacturer
|
|
||||||
can do so. This is fundamentally incompatible with the aim of
|
|
||||||
protecting users' freedom to change the software. The systematic
|
|
||||||
pattern of such abuse occurs in the area of products for individuals to
|
|
||||||
use, which is precisely where it is most unacceptable. Therefore, we
|
|
||||||
have designed this version of the GPL to prohibit the practice for those
|
|
||||||
products. If such problems arise substantially in other domains, we
|
|
||||||
stand ready to extend this provision to those domains in future versions
|
|
||||||
of the GPL, as needed to protect the freedom of users.
|
|
||||||
|
|
||||||
Finally, every program is threatened constantly by software patents.
|
|
||||||
States should not allow patents to restrict development and use of
|
|
||||||
software on general-purpose computers, but in those that do, we wish to
|
|
||||||
avoid the special danger that patents applied to a free program could
|
|
||||||
make it effectively proprietary. To prevent this, the GPL assures that
|
|
||||||
patents cannot be used to render the program non-free.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
The precise terms and conditions for copying, distribution and
|
||||||
modification follow.
|
modification follow.
|
||||||
@@ -72,7 +84,7 @@ modification follow.
|
|||||||
|
|
||||||
0. Definitions.
|
0. Definitions.
|
||||||
|
|
||||||
"This License" refers to version 3 of the GNU General Public License.
|
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||||
|
|
||||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
works, such as semiconductor masks.
|
works, such as semiconductor masks.
|
||||||
@@ -549,35 +561,45 @@ to collect a royalty for further conveying from those to whom you convey
|
|||||||
the Program, the only way you could satisfy both those terms and this
|
the Program, the only way you could satisfy both those terms and this
|
||||||
License would be to refrain entirely from conveying the Program.
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
13. Use with the GNU Affero General Public License.
|
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, if you modify the
|
||||||
|
Program, your modified version must prominently offer all users
|
||||||
|
interacting with it remotely through a computer network (if your version
|
||||||
|
supports such interaction) an opportunity to receive the Corresponding
|
||||||
|
Source of your version by providing access to the Corresponding Source
|
||||||
|
from a network server at no charge, through some standard or customary
|
||||||
|
means of facilitating copying of software. This Corresponding Source
|
||||||
|
shall include the Corresponding Source for any work covered by version 3
|
||||||
|
of the GNU General Public License that is incorporated pursuant to the
|
||||||
|
following paragraph.
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, you have
|
Notwithstanding any other provision of this License, you have
|
||||||
permission to link or combine any covered work with a work licensed
|
permission to link or combine any covered work with a work licensed
|
||||||
under version 3 of the GNU Affero General Public License into a single
|
under version 3 of the GNU General Public License into a single
|
||||||
combined work, and to convey the resulting work. The terms of this
|
combined work, and to convey the resulting work. The terms of this
|
||||||
License will continue to apply to the part which is the covered work,
|
License will continue to apply to the part which is the covered work,
|
||||||
but the special requirements of the GNU Affero General Public License,
|
but the work with which it is combined will remain governed by version
|
||||||
section 13, concerning interaction through a network will apply to the
|
3 of the GNU General Public License.
|
||||||
combination as such.
|
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
the GNU General Public License from time to time. Such new versions will
|
the GNU Affero General Public License from time to time. Such new versions
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
will be similar in spirit to the present version, but may differ in detail to
|
||||||
address new problems or concerns.
|
address new problems or concerns.
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
Each version is given a distinguishing version number. If the
|
||||||
Program specifies that a certain numbered version of the GNU General
|
Program specifies that a certain numbered version of the GNU Affero General
|
||||||
Public License "or any later version" applies to it, you have the
|
Public License "or any later version" applies to it, you have the
|
||||||
option of following the terms and conditions either of that numbered
|
option of following the terms and conditions either of that numbered
|
||||||
version or of any later version published by the Free Software
|
version or of any later version published by the Free Software
|
||||||
Foundation. If the Program does not specify a version number of the
|
Foundation. If the Program does not specify a version number of the
|
||||||
GNU General Public License, you may choose any version ever published
|
GNU Affero General Public License, you may choose any version ever published
|
||||||
by the Free Software Foundation.
|
by the Free Software Foundation.
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
If the Program specifies that a proxy can decide which future
|
||||||
versions of the GNU General Public License can be used, that proxy's
|
versions of the GNU Affero General Public License can be used, that proxy's
|
||||||
public statement of acceptance of a version permanently authorizes you
|
public statement of acceptance of a version permanently authorizes you
|
||||||
to choose that version for the Program.
|
to choose that version for the Program.
|
||||||
|
|
||||||
@@ -635,40 +657,29 @@ the "copyright" line and a pointer to where the full notice is found.
|
|||||||
Copyright (C) <year> <name of author>
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU Affero 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,
|
This program is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU General Public License for more details.
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU Affero General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
If the program does terminal interaction, make it output a short
|
If your software can interact with users remotely through a computer
|
||||||
notice like this when it starts in an interactive mode:
|
network, you should also make sure that it provides a way for users to
|
||||||
|
get its source. For example, if your program is a web application, its
|
||||||
<program> Copyright (C) <year> <name of author>
|
interface could display a "Source" link that leads users to an archive
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
of the code. There are many ways you could offer source, and different
|
||||||
This is free software, and you are welcome to redistribute it
|
solutions will be better for different programs; see section 13 for the
|
||||||
under certain conditions; type `show c' for details.
|
specific requirements.
|
||||||
|
|
||||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
|
||||||
parts of the General Public License. Of course, your program's commands
|
|
||||||
might be different; for a GUI interface, you would use an "about box".
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or school,
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
For more information on this, and how to apply and follow the GNU GPL, see
|
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||||
<https://www.gnu.org/licenses/>.
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
The GNU General Public License does not permit incorporating your program
|
|
||||||
into proprietary programs. If your program is a subroutine library, you
|
|
||||||
may consider it more useful to permit linking proprietary applications with
|
|
||||||
the library. If this is what you want to do, use the GNU Lesser General
|
|
||||||
Public License instead of this License. But first, please read
|
|
||||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
|
||||||
@@ -34,6 +34,21 @@ It is now and always will be completely ad-free and open-source.
|
|||||||
* Custom TLE satellite data import is available via Three Line Element .txt files
|
* Custom TLE satellite data import is available via Three Line Element .txt files
|
||||||
* Offline first: calculations are made offline. Weekly TLE data update is recommended.
|
* Offline first: calculations are made offline. Weekly TLE data update is recommended.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The Look4Sat application code is licensed under the GNU General Public License v3.0.
|
||||||
|
|
||||||
|
The CW decoder in `feature/cw` bundles the [DeepCW](https://github.com/e04/deepcw-engine)
|
||||||
|
neural decoding model, licensed under the GNU Affero General Public License v3.0 only
|
||||||
|
(AGPL-3.0-only). Because the combined work incorporates an AGPL-3.0 component, the
|
||||||
|
combined work is distributed under the
|
||||||
|
[GNU Affero General Public License v3.0](LICENSE) — GPL-3.0 Section 13 permits the
|
||||||
|
combination, and AGPL-3.0 Section 13 applies to the combined work as a whole.
|
||||||
|
Model provenance, attribution and the applied int8 quantization are documented in
|
||||||
|
[`feature/cw/licenses/NOTICE.md`](feature/cw/licenses/NOTICE.md); the original GPL-3.0
|
||||||
|
text is preserved at `feature/cw/licenses/Look4Sat-GPL-3.0.txt`. The CW model runs
|
||||||
|
locally on-device and does not provide services over a network.
|
||||||
|
|
||||||
## Star History
|
## Star History
|
||||||
|
|
||||||
<a href="https://www.star-history.com/?repos=rt-bishop%2FLook4Sat&type=timeline&legend=top-left">
|
<a href="https://www.star-history.com/?repos=rt-bishop%2FLook4Sat&type=timeline&legend=top-left">
|
||||||
|
|||||||
+11
-3
@@ -11,11 +11,12 @@ val keystoreProperties = Properties().apply {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
// CW 解码 native 库仅 armeabi-v7a(照搬 Morse Expert 1.15): 全 ABI 打包会在
|
|
||||||
// arm64 设备 loadLibrary 失败, 强制 32 位兼容(用户设备为 32 位软件)
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
|
// ONNX Runtime 的 AAR 自带 4 个架构共 115MB 原生库(arm64 28M / armv7 20M /
|
||||||
|
// x86 33M / x86_64 34M)。x86 系列只有模拟器用得到, 全打包会让 APK 从 8MB
|
||||||
|
// 涨到 135MB。仅保留真机需要的两个 ABI。
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters += listOf("armeabi-v7a")
|
abiFilters += listOf("arm64-v8a", "armeabi-v7a")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
androidResources {
|
androidResources {
|
||||||
@@ -23,6 +24,10 @@ android {
|
|||||||
localeFilters += listOf(
|
localeFilters += listOf(
|
||||||
"en", "zh", "tr", "in", "id", "es", "ru", "si", "uk"
|
"en", "zh", "tr", "in", "id", "es", "ru", "si", "uk"
|
||||||
)
|
)
|
||||||
|
// DeepCW 模型必须以未压缩形式打包: ONNX Runtime 通过 mmap 直接读取
|
||||||
|
// assets, 压缩后无法映射会导致 createSession 失败。noCompress 只在
|
||||||
|
// 打包 APK 的 app 模块生效, 在 feature 库模块声明无效。
|
||||||
|
noCompress += "onnx"
|
||||||
}
|
}
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
if (keystoreProperties["storeFile"] != null) {
|
if (keystoreProperties["storeFile"] != null) {
|
||||||
@@ -37,6 +42,9 @@ android {
|
|||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
signingConfig = signingConfigs.findByName("release")
|
signingConfig = signingConfigs.findByName("release")
|
||||||
|
// ONNX Runtime 走 JNI, R8 混淆会重命名 ai.onnxruntime.* 类导致 native
|
||||||
|
// 崩溃。convention 插件已开启 isMinifyEnabled, 必须补 keep 规则。
|
||||||
|
proguardFiles("proguard-rules.pro")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Vendored
+13
@@ -0,0 +1,13 @@
|
|||||||
|
# ProGuard / R8 rules for the Look4Sat application module.
|
||||||
|
#
|
||||||
|
# NOTE: release builds enable minification (isMinifyEnabled=true in the
|
||||||
|
# convention plugin), so anything whose classes are resolved reflectively or
|
||||||
|
# through JNI by name MUST be kept here.
|
||||||
|
|
||||||
|
# ONNX Runtime (ai.onnxruntime): the Java binding is backed by JNI. Native code
|
||||||
|
# resolves Java methods/classes by their original names; R8 renaming or
|
||||||
|
# stripping them causes a hard crash at runtime with no Java stack trace.
|
||||||
|
# This rule is required by the ONNX Runtime docs for minified Android builds.
|
||||||
|
# https://onnxruntime.ai/docs/get-started/with-java.html
|
||||||
|
-keep class ai.onnxruntime.** { *; }
|
||||||
|
-dontwarn ai.onnxruntime.**
|
||||||
@@ -5,3 +5,9 @@ plugins {
|
|||||||
android {
|
android {
|
||||||
namespace = "com.rtbishop.look4sat.core.data"
|
namespace = "com.rtbishop.look4sat.core.data"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// DeepCW 神经网络 CW 解码推理。ONNX 推理属 Android 平台依赖, 放此处而非
|
||||||
|
// core:domain —— 后者须保持纯 Kotlin/JVM 以留 KMP 迁移余地 (见 AGENTS.md)。
|
||||||
|
implementation(libs.other.onnxruntime)
|
||||||
|
}
|
||||||
@@ -0,0 +1,336 @@
|
|||||||
|
/*
|
||||||
|
* 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.data.cw
|
||||||
|
|
||||||
|
import ai.onnxruntime.OnnxTensor
|
||||||
|
import ai.onnxruntime.OrtEnvironment
|
||||||
|
import ai.onnxruntime.OrtSession
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import com.rtbishop.look4sat.core.domain.cw.CwCtcDecoder
|
||||||
|
import com.rtbishop.look4sat.core.domain.cw.CwDeepBuffer
|
||||||
|
import com.rtbishop.look4sat.core.domain.cw.CwDeepSpectrogram
|
||||||
|
import com.rtbishop.look4sat.core.domain.cw.ICwDecoder
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.nio.FloatBuffer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CW decoder backed by the DeepCW neural network (AGPL-3.0, see
|
||||||
|
* `feature/cw/licenses/NOTICE.md`).
|
||||||
|
*
|
||||||
|
* The model classifies a whole audio segment at once rather than streaming
|
||||||
|
* sample by sample, and it revises earlier characters once more context
|
||||||
|
* arrives. Incremental stitching therefore produces duplicated callsigns —
|
||||||
|
* measured character error rates of 67-294% against 0% for whole-segment
|
||||||
|
* decoding. Instead a [CwDeepBuffer] holds the last 20 seconds and the whole
|
||||||
|
* window is re-decoded every 1.5 seconds, replacing [decodedText] outright.
|
||||||
|
*
|
||||||
|
* The model's fixed 400-1200 Hz analysis window means pitch detection is built
|
||||||
|
* in; no spectral peak tracking or squelch gating is needed.
|
||||||
|
*/
|
||||||
|
class CwDeepDecoder(context: Context) : ICwDecoder {
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val TAG = "CwDeepDecoder"
|
||||||
|
const val MODEL_ASSET = "deepcw/model.onnx"
|
||||||
|
const val METADATA_ASSET = "deepcw/model.onnx.json"
|
||||||
|
|
||||||
|
/** Evicted audio is decoded into permanent history once this much accumulates. */
|
||||||
|
const val ARCHIVE_SECONDS = 15.0
|
||||||
|
val ARCHIVE_THRESHOLD: Int = (CwDeepSpectrogram.SAMPLE_RATE * ARCHIVE_SECONDS).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _decodedText = MutableStateFlow("")
|
||||||
|
override val decodedText: StateFlow<String> = _decodedText.asStateFlow()
|
||||||
|
|
||||||
|
private val _historyText = MutableStateFlow("")
|
||||||
|
override val historyText: StateFlow<String> = _historyText.asStateFlow()
|
||||||
|
|
||||||
|
private val _estimatedPitch = MutableStateFlow<Float?>(null)
|
||||||
|
override val estimatedPitch: StateFlow<Float?> = _estimatedPitch.asStateFlow()
|
||||||
|
|
||||||
|
private val _signalStrength = MutableStateFlow(0f)
|
||||||
|
override val signalStrength: StateFlow<Float> = _signalStrength.asStateFlow()
|
||||||
|
|
||||||
|
private val _lastInferenceMs = MutableStateFlow(0)
|
||||||
|
override val lastInferenceMs: StateFlow<Int> = _lastInferenceMs.asStateFlow()
|
||||||
|
|
||||||
|
private val _errorMessage = MutableStateFlow<String?>(null)
|
||||||
|
override val errorMessage: StateFlow<String?> = _errorMessage.asStateFlow()
|
||||||
|
|
||||||
|
private val buffer = CwDeepBuffer()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evicted audio accumulates here until it reaches [ARCHIVE_SECONDS], then
|
||||||
|
* is decoded once and appended to [historyText]. Archiving in ~15 s chunks
|
||||||
|
* keeps the extra inference cheap (short window) while long enough to be
|
||||||
|
* decoded accurately — the content has already been through the 20 s window
|
||||||
|
* many times, so a slightly shorter archive decode loses almost nothing.
|
||||||
|
*/
|
||||||
|
private val archiveBuffer = FloatArray(CwDeepBuffer.DEFAULT_MAX_SECONDS.toInt() * CwDeepSpectrogram.SAMPLE_RATE)
|
||||||
|
private var archiveSize = 0
|
||||||
|
|
||||||
|
/** Held while inference runs so slow devices skip work instead of queuing it. */
|
||||||
|
private val inferenceLock = Mutex()
|
||||||
|
|
||||||
|
private var environment: OrtEnvironment? = null
|
||||||
|
private var session: OrtSession? = null
|
||||||
|
private var chars: List<String> = emptyList()
|
||||||
|
private var blankIndex = 41
|
||||||
|
private var inputName = "spectrogram"
|
||||||
|
private var outputName = "log_probs"
|
||||||
|
|
||||||
|
private val appContext = context.applicationContext
|
||||||
|
private var loadAttempted = false
|
||||||
|
|
||||||
|
init {
|
||||||
|
CwProbe.init(appContext)
|
||||||
|
CwProbe.step("decoder_constructed")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads metadata and the ONNX session on first use.
|
||||||
|
*
|
||||||
|
* Deliberately not done in `init`: loading pulls in ONNX Runtime's native
|
||||||
|
* library, and a failure there surfaces as [UnsatisfiedLinkError]. Thrown
|
||||||
|
* from a constructor it would take down the whole composable that created
|
||||||
|
* the decoder, so the work happens here where it can be reported through
|
||||||
|
* [errorMessage] instead.
|
||||||
|
*
|
||||||
|
* @return true when the session is ready to run.
|
||||||
|
*/
|
||||||
|
private fun ensureLoaded(): Boolean {
|
||||||
|
if (session != null) return true
|
||||||
|
if (loadAttempted) return false
|
||||||
|
loadAttempted = true
|
||||||
|
CwProbe.step("load_begin")
|
||||||
|
try {
|
||||||
|
val metadata = JSONObject(
|
||||||
|
appContext.assets.open(METADATA_ASSET).bufferedReader().use { it.readText() }
|
||||||
|
)
|
||||||
|
val charArray = metadata.getJSONArray("chars")
|
||||||
|
chars = List(charArray.length()) { charArray.getString(it) }
|
||||||
|
blankIndex = metadata.getInt("blank_index")
|
||||||
|
inputName = metadata.getString("onnx_input_name")
|
||||||
|
outputName = metadata.getString("onnx_output_name")
|
||||||
|
|
||||||
|
val modelBytes = appContext.assets.open(MODEL_ASSET).use { it.readBytes() }
|
||||||
|
val env = OrtEnvironment.getEnvironment()
|
||||||
|
environment = env
|
||||||
|
val options = OrtSession.SessionOptions().apply {
|
||||||
|
// Keep a core free for audio capture and the UI; the default
|
||||||
|
// would spread inference across every core on the device.
|
||||||
|
val threads = (Runtime.getRuntime().availableProcessors() - 1).coerceIn(1, 4)
|
||||||
|
setIntraOpNumThreads(threads)
|
||||||
|
}
|
||||||
|
session = env.createSession(modelBytes, options)
|
||||||
|
CwProbe.step("load_session_ok")
|
||||||
|
Log.i(TAG, "DeepCW ready: ${modelBytes.size} bytes, ${chars.size} classes")
|
||||||
|
return true
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
// Catches UnsatisfiedLinkError (missing/mismatched .so) as well as
|
||||||
|
// asset and session failures.
|
||||||
|
CwProbe.step("load_failed:${t.javaClass.simpleName}")
|
||||||
|
Log.e(TAG, "DeepCW model failed to load", t)
|
||||||
|
_errorMessage.value =
|
||||||
|
"CW model failed to load: ${t.message ?: t.javaClass.simpleName}"
|
||||||
|
// Persist the failure for devices without logcat access.
|
||||||
|
runCatching {
|
||||||
|
val sw = java.io.StringWriter()
|
||||||
|
t.printStackTrace(java.io.PrintWriter(sw))
|
||||||
|
java.io.File(appContext.filesDir, "deepcw_load_error.txt")
|
||||||
|
.writeText("${t.javaClass.name}: ${t.message}\n${sw}\n")
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun processBuffer(samples: FloatArray, sampleRate: Int) {
|
||||||
|
if (samples.isEmpty()) return
|
||||||
|
if (!ensureLoaded()) return
|
||||||
|
|
||||||
|
val resampled = CwDeepSpectrogram.resampleLinear(
|
||||||
|
samples, sampleRate, CwDeepSpectrogram.SAMPLE_RATE
|
||||||
|
)
|
||||||
|
val shouldRedecode = buffer.append(resampled)
|
||||||
|
|
||||||
|
// Archive audio that scrolled out of the live window. It is decoded once
|
||||||
|
// when a full archive chunk has accumulated, so old text does not vanish.
|
||||||
|
val overflow = buffer.drainOverflow()
|
||||||
|
if (overflow.isNotEmpty()) {
|
||||||
|
for (v in overflow) {
|
||||||
|
if (archiveSize < archiveBuffer.size) archiveBuffer[archiveSize++] = v
|
||||||
|
}
|
||||||
|
if (archiveSize >= ARCHIVE_THRESHOLD) {
|
||||||
|
val audio = archiveBuffer.copyOf(archiveSize)
|
||||||
|
archiveSize = 0
|
||||||
|
try {
|
||||||
|
archiveDecode(audio)
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
if (t is CancellationException) throw t
|
||||||
|
Log.e(TAG, "archive decode failed", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldRedecode || !buffer.hasEnoughAudio) return
|
||||||
|
|
||||||
|
// Drop this cycle rather than queue when the previous run is still going.
|
||||||
|
if (!inferenceLock.tryLock()) {
|
||||||
|
Log.d(TAG, "inference still running, skipping this interval")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
decodeWindow(buffer.snapshot())
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
// Cancellation is normal when the user pauses: the capture coroutine
|
||||||
|
// is cancelled while an inference is in flight. Never swallow it as
|
||||||
|
// a decode error — rethrow so the coroutine machinery works, and do
|
||||||
|
// not flash a spurious "decode failed" banner.
|
||||||
|
if (t is CancellationException) throw t
|
||||||
|
Log.e(TAG, "inference failed", t)
|
||||||
|
_errorMessage.value = "CW decode failed: ${t.message ?: t.javaClass.simpleName}"
|
||||||
|
runCatching {
|
||||||
|
val sw = java.io.StringWriter()
|
||||||
|
t.printStackTrace(java.io.PrintWriter(sw))
|
||||||
|
java.io.File(appContext.filesDir, "deepcw_infer_error.txt")
|
||||||
|
.writeText("${t.javaClass.name}: ${t.message}\n${sw}\n")
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
inferenceLock.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun decodeWindow(window: FloatArray) = withContext(Dispatchers.Default) {
|
||||||
|
val activeSession = session ?: return@withContext
|
||||||
|
val activeEnvironment = environment ?: return@withContext
|
||||||
|
CwProbe.step("infer_begin frames=${window.size}")
|
||||||
|
|
||||||
|
val spectrogram = CwDeepSpectrogram.compute(window)
|
||||||
|
val text = runInference(activeSession, activeEnvironment, spectrogram)
|
||||||
|
|
||||||
|
// Replace, never append: the model rewrites earlier characters as more
|
||||||
|
// context arrives, so appending would leave stale guesses on screen.
|
||||||
|
_decodedText.value = text
|
||||||
|
|
||||||
|
updateSignalMetrics(spectrogram)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a chunk of audio that has scrolled out of the live window and
|
||||||
|
* append it to [historyText]. Unlike the live window this never replaces —
|
||||||
|
* the archived audio is final, so its text is permanent.
|
||||||
|
*/
|
||||||
|
private suspend fun archiveDecode(audio: FloatArray) = withContext(Dispatchers.Default) {
|
||||||
|
val activeSession = session ?: return@withContext
|
||||||
|
val activeEnvironment = environment ?: return@withContext
|
||||||
|
if (audio.size < CwDeepSpectrogram.FFT_LENGTH) return@withContext
|
||||||
|
val spectrogram = CwDeepSpectrogram.compute(audio)
|
||||||
|
val text = runInference(activeSession, activeEnvironment, spectrogram)
|
||||||
|
if (text.isNotEmpty()) {
|
||||||
|
_historyText.value += text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Run the ONNX model over a pre-computed spectrogram and return the decoded text. */
|
||||||
|
private fun runInference(
|
||||||
|
activeSession: OrtSession,
|
||||||
|
activeEnvironment: OrtEnvironment,
|
||||||
|
spectrogram: Array<FloatArray>
|
||||||
|
): String {
|
||||||
|
val frames = spectrogram.size
|
||||||
|
val bins = CwDeepSpectrogram.FREQUENCY_BINS
|
||||||
|
|
||||||
|
val flat = FloatBuffer.allocate(frames * bins)
|
||||||
|
for (frame in spectrogram) flat.put(frame)
|
||||||
|
flat.rewind()
|
||||||
|
|
||||||
|
val shape = longArrayOf(1, 1, frames.toLong(), bins.toLong())
|
||||||
|
val startedAt = System.currentTimeMillis()
|
||||||
|
val text: String
|
||||||
|
OnnxTensor.createTensor(activeEnvironment, flat, shape).use { input ->
|
||||||
|
activeSession.run(mapOf(inputName to input)).use { result ->
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val logits = result[outputName].get().value as Array<Array<FloatArray>>
|
||||||
|
text = CwCtcDecoder.greedy(logits, chars, blankIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_lastInferenceMs.value = (System.currentTimeMillis() - startedAt).toInt()
|
||||||
|
CwProbe.step("infer_done ms=${_lastInferenceMs.value}")
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report the loudest bin as the tone pitch and its prominence over the
|
||||||
|
* window mean as a 0..1 strength, purely for the UI readout.
|
||||||
|
*/
|
||||||
|
private fun updateSignalMetrics(spectrogram: Array<FloatArray>) {
|
||||||
|
if (spectrogram.isEmpty()) return
|
||||||
|
var bestBin = 0
|
||||||
|
var bestValue = 0f
|
||||||
|
var total = 0f
|
||||||
|
var count = 0
|
||||||
|
for (frame in spectrogram) {
|
||||||
|
for (bin in frame.indices) {
|
||||||
|
val value = frame[bin]
|
||||||
|
total += value
|
||||||
|
count++
|
||||||
|
if (value > bestValue) {
|
||||||
|
bestValue = value
|
||||||
|
bestBin = bin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count == 0 || bestValue <= 0f) return
|
||||||
|
|
||||||
|
val binHz = CwDeepSpectrogram.SAMPLE_RATE.toDouble() / CwDeepSpectrogram.FFT_LENGTH
|
||||||
|
// Relative bin 0 is 400 Hz; absolute bin index is 32 + bestBin.
|
||||||
|
val absoluteBin = 32 + bestBin
|
||||||
|
_estimatedPitch.value = (absoluteBin * binHz).toFloat()
|
||||||
|
|
||||||
|
val mean = total / count
|
||||||
|
_signalStrength.value = ((bestValue - mean) / bestValue).coerceIn(0f, 1f)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reset() {
|
||||||
|
buffer.reset()
|
||||||
|
_decodedText.value = ""
|
||||||
|
_historyText.value = ""
|
||||||
|
archiveSize = 0
|
||||||
|
_estimatedPitch.value = null
|
||||||
|
_signalStrength.value = 0f
|
||||||
|
_lastInferenceMs.value = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
try {
|
||||||
|
session?.close()
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
Log.w(TAG, "session close failed", t)
|
||||||
|
}
|
||||||
|
session = null
|
||||||
|
environment = null
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
-23
@@ -15,34 +15,32 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.rtbishop.look4sat.core.domain.cw
|
package com.rtbishop.look4sat.core.data.cw
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* First-order IIR filters.
|
* Minimal crash-probe logger for diagnosing crashes that produce no Java
|
||||||
* Ported from ggmorse/src/filter.h
|
* stack trace (native faults, low-memory kills). Each step appends one line
|
||||||
|
* to `files/probe_cw.txt`; if the process dies mid-way the last line shows
|
||||||
|
* exactly where. No adb or logcat required.
|
||||||
*/
|
*/
|
||||||
internal class CwFilter {
|
internal object CwProbe {
|
||||||
private var z1 = 0f
|
|
||||||
|
|
||||||
companion object {
|
private var dir: java.io.File? = null
|
||||||
private const val PI_F = 3.141592653589793f
|
|
||||||
|
fun init(context: Context) {
|
||||||
|
dir = context.filesDir
|
||||||
}
|
}
|
||||||
|
|
||||||
fun highPass(sample: Float, cutoffHz: Float, sampleRate: Float): Float {
|
fun step(label: String) {
|
||||||
val rc = 1.0f / (2f * PI_F * cutoffHz)
|
val target = dir ?: return
|
||||||
val dt = 1.0f / sampleRate
|
runCatching {
|
||||||
val alpha = dt / (rc + dt)
|
val line = "${System.currentTimeMillis()} $label"
|
||||||
z1 = alpha * (z1 + sample - z1)
|
val file = java.io.File(target, "probe_cw.txt")
|
||||||
return sample - z1
|
file.appendText("$line\n")
|
||||||
|
Log.i("CwProbe", line)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun lowPass(sample: Float, cutoffHz: Float, sampleRate: Float): Float {
|
|
||||||
val rc = 1.0f / (2f * PI_F * cutoffHz)
|
|
||||||
val dt = 1.0f / sampleRate
|
|
||||||
val alpha = dt / (rc + dt)
|
|
||||||
z1 += alpha * (sample - z1)
|
|
||||||
return z1
|
|
||||||
}
|
|
||||||
|
|
||||||
fun reset() { z1 = 0f }
|
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,6 @@ import com.rtbishop.look4sat.core.data.framework.Ic705Controller
|
|||||||
import com.rtbishop.look4sat.core.data.framework.NetworkReporter
|
import com.rtbishop.look4sat.core.data.framework.NetworkReporter
|
||||||
import com.rtbishop.look4sat.core.data.framework.RadioTrackingService
|
import com.rtbishop.look4sat.core.data.framework.RadioTrackingService
|
||||||
import com.rtbishop.look4sat.core.data.repository.AmSatRepository
|
import com.rtbishop.look4sat.core.data.repository.AmSatRepository
|
||||||
import com.rtbishop.look4sat.core.domain.amsat.AmSatApiClient
|
|
||||||
import com.rtbishop.look4sat.core.data.repository.DatabaseRepo
|
import com.rtbishop.look4sat.core.data.repository.DatabaseRepo
|
||||||
import com.rtbishop.look4sat.core.data.repository.SatelliteRepo
|
import com.rtbishop.look4sat.core.data.repository.SatelliteRepo
|
||||||
import com.rtbishop.look4sat.core.data.repository.SelectionRepo
|
import com.rtbishop.look4sat.core.data.repository.SelectionRepo
|
||||||
@@ -83,7 +82,7 @@ class MainContainer(private val context: Context) : IMainContainer {
|
|||||||
override val selectionRepo = provideSelectionRepo()
|
override val selectionRepo = provideSelectionRepo()
|
||||||
override val satelliteRepo = provideSatelliteRepo()
|
override val satelliteRepo = provideSatelliteRepo()
|
||||||
override val databaseRepo = provideDatabaseRepo()
|
override val databaseRepo = provideDatabaseRepo()
|
||||||
override val amSatRepo = AmSatRepository(com.rtbishop.look4sat.core.domain.amsat.AmSatApiClient())
|
override val amSatRepo by lazy { AmSatRepository(remoteSource) }
|
||||||
override val radioTrackingService: IRadioTrackingService by lazy {
|
override val radioTrackingService: IRadioTrackingService by lazy {
|
||||||
val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
||||||
RadioTrackingService(appScope, manager, satelliteRepo, settingsRepo)
|
RadioTrackingService(appScope, manager, satelliteRepo, settingsRepo)
|
||||||
@@ -102,6 +101,11 @@ class MainContainer(private val context: Context) : IMainContainer {
|
|||||||
|
|
||||||
override fun provideAudioCapture(): IAudioCapture = AudioCapture()
|
override fun provideAudioCapture(): IAudioCapture = AudioCapture()
|
||||||
|
|
||||||
|
// 每次调用返回新实例: 调用方负责 close() 释放 OrtSession, 且 Radar 内嵌
|
||||||
|
// 面板与独立 CW 页各自持有自己的解码器
|
||||||
|
override fun provideCwDecoder(): com.rtbishop.look4sat.core.domain.cw.ICwDecoder =
|
||||||
|
com.rtbishop.look4sat.core.data.cw.CwDeepDecoder(context)
|
||||||
|
|
||||||
override fun provideSaveImage(): ISaveImage = SaveImage(context)
|
override fun provideSaveImage(): ISaveImage = SaveImage(context)
|
||||||
|
|
||||||
// WaveLog logging (4.5.2): local queue + uploader (shared instance)
|
// WaveLog logging (4.5.2): local queue + uploader (shared instance)
|
||||||
|
|||||||
+79
-16
@@ -1,31 +1,92 @@
|
|||||||
package com.rtbishop.look4sat.core.data.repository
|
package com.rtbishop.look4sat.core.data.repository
|
||||||
|
|
||||||
import com.rtbishop.look4sat.core.domain.amsat.AmSatApiClient
|
|
||||||
import com.rtbishop.look4sat.core.domain.amsat.ApiReport
|
|
||||||
import com.rtbishop.look4sat.core.domain.model.SatDay
|
import com.rtbishop.look4sat.core.domain.model.SatDay
|
||||||
import com.rtbishop.look4sat.core.domain.model.SatReport
|
import com.rtbishop.look4sat.core.domain.model.SatReport
|
||||||
import com.rtbishop.look4sat.core.domain.model.SatSlot
|
import com.rtbishop.look4sat.core.domain.model.SatSlot
|
||||||
import com.rtbishop.look4sat.core.domain.model.SatStatus
|
import com.rtbishop.look4sat.core.domain.model.SatStatus
|
||||||
import com.rtbishop.look4sat.core.domain.model.SatStatusPage
|
import com.rtbishop.look4sat.core.domain.model.SatStatusPage
|
||||||
import com.rtbishop.look4sat.core.domain.repository.IAmSatRepository
|
import com.rtbishop.look4sat.core.domain.repository.IAmSatRepository
|
||||||
|
import com.rtbishop.look4sat.core.domain.source.IRemoteSource
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
import java.util.Locale
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
|
|
||||||
/** AMSAT status repository: official API v1 -> SatStatusPage (replaces the HTML parser). */
|
/** One report from the AMSAT API (data layer model). */
|
||||||
class AmSatRepository(private val apiClient: AmSatApiClient) : IAmSatRepository {
|
private data class ApiReport(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val callsign: String,
|
||||||
|
val report: String,
|
||||||
|
val gridSquare: String,
|
||||||
|
val reportedTimeUtcSec: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
/** AMSAT status repository using RemoteSource (Clean Architecture: data layer handles HTTP). */
|
||||||
|
class AmSatRepository(private val remoteSource: IRemoteSource) : IAmSatRepository {
|
||||||
|
|
||||||
|
private val isoUtcFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).apply {
|
||||||
|
timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun fetchStatus(): SatStatusPage? = withContext(Dispatchers.IO) {
|
override suspend fun fetchStatus(): SatStatusPage? = withContext(Dispatchers.IO) {
|
||||||
val nowSec = System.currentTimeMillis() / 1000
|
val nowSec = System.currentTimeMillis() / 1000
|
||||||
val names = apiClient.fetchCatalog()
|
val catalogJson = remoteSource.getAmSatCatalog() ?: return@withContext null
|
||||||
val reports = apiClient.fetchAllReports(hours = 168)
|
val reportsJson = remoteSource.getAmSatReports(hours = 168, limit = 500) ?: return@withContext null
|
||||||
|
|
||||||
|
val names = parseCatalog(catalogJson)
|
||||||
|
val reports = parseReports(reportsJson)
|
||||||
|
|
||||||
if (names.isEmpty() && reports.isEmpty()) return@withContext null
|
if (names.isEmpty() && reports.isEmpty()) return@withContext null
|
||||||
|
|
||||||
val statuses = buildStatuses(names, reports, nowSec)
|
val statuses = buildStatuses(names, reports, nowSec)
|
||||||
val reportMap = reports.associate { it.id to toSatReport(it) }
|
val reportMap = reports.associate { it.id to toSatReport(it) }
|
||||||
SatStatusPage(System.currentTimeMillis(), statuses, reportMap)
|
SatStatusPage(System.currentTimeMillis(), statuses, reportMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Parse catalog JSON to list of satellite names */
|
||||||
|
private fun parseCatalog(json: String): List<String> {
|
||||||
|
return try {
|
||||||
|
val arr = JSONObject(json).getJSONArray("data")
|
||||||
|
(0 until arr.length()).map { arr.getJSONObject(it).getString("name") }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parse reports JSON to list of ApiReport domain objects */
|
||||||
|
private fun parseReports(json: String): List<ApiReport> {
|
||||||
|
return try {
|
||||||
|
val arr = JSONObject(json).getJSONArray("data")
|
||||||
|
(0 until arr.length()).mapNotNull { i ->
|
||||||
|
val o = arr.getJSONObject(i)
|
||||||
|
val iso = o.optString("reported_time", "")
|
||||||
|
if (iso.isEmpty()) null else ApiReport(
|
||||||
|
id = o.optString("id", ""),
|
||||||
|
name = o.optString("name", ""),
|
||||||
|
callsign = o.optString("callsign", ""),
|
||||||
|
report = o.optString("report", ""),
|
||||||
|
gridSquare = o.optString("grid_square", ""),
|
||||||
|
reportedTimeUtcSec = parseIsoUtcSec(iso)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parse ISO 8601 UTC timestamp to epoch seconds (e.g., "2026-08-05T07:30:00Z") */
|
||||||
|
private fun parseIsoUtcSec(iso: String): Long {
|
||||||
|
return try {
|
||||||
|
(isoUtcFormat.parse(iso)?.time ?: 0L) / 1000
|
||||||
|
} catch (e: Exception) {
|
||||||
|
0L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Build one SatStatus (6 days x 12 slots) per catalog satellite, slotting reports by age. */
|
/** Build one SatStatus (6 days x 12 slots) per catalog satellite, slotting reports by age. */
|
||||||
private fun buildStatuses(names: List<String>, reports: List<ApiReport>, nowSec: Long): List<SatStatus> {
|
private fun buildStatuses(names: List<String>, reports: List<ApiReport>, nowSec: Long): List<SatStatus> {
|
||||||
val byName = reports.groupBy { it.name }
|
val byName = reports.groupBy { it.name }
|
||||||
@@ -41,7 +102,7 @@ class AmSatRepository(private val apiClient: AmSatApiClient) : IAmSatRepository
|
|||||||
val slotEnd = nowSec - slotIdx * 7200L
|
val slotEnd = nowSec - slotIdx * 7200L
|
||||||
val inSlot = byName[name].orEmpty().filter { it.reportedTimeUtcSec in slotStart until slotEnd }
|
val inSlot = byName[name].orEmpty().filter { it.reportedTimeUtcSec in slotStart until slotEnd }
|
||||||
if (inSlot.isEmpty()) {
|
if (inSlot.isEmpty()) {
|
||||||
SatSlot(statusColor = NoReportGray, count = 0)
|
SatSlot(statusColor = NO_REPORT_GRAY, count = 0)
|
||||||
} else {
|
} else {
|
||||||
val newest = inSlot.maxByOrNull { it.reportedTimeUtcSec }!!
|
val newest = inSlot.maxByOrNull { it.reportedTimeUtcSec }!!
|
||||||
SatSlot(
|
SatSlot(
|
||||||
@@ -76,18 +137,20 @@ class AmSatRepository(private val apiClient: AmSatApiClient) : IAmSatRepository
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Map status text to color value (for UI rendering). */
|
||||||
private fun statusColorOf(report: String): Long = when (report.lowercase()) {
|
private fun statusColorOf(report: String): Long = when (report.lowercase()) {
|
||||||
"heard", "crew active" -> ActiveBlue
|
"heard", "crew active" -> ACTIVE_BLUE
|
||||||
"telemetry only" -> TlmOrange
|
"telemetry only" -> TLM_ORANGE
|
||||||
"not heard" -> NotHeardPink
|
"not heard" -> NOT_HEARD_PINK
|
||||||
else -> ConflictDeepOrange
|
else -> CONFLICT_DEEP_ORANGE
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val ActiveBlue = 0xFF648FFF
|
// AMSAT official status colors (from amsat.org/status)
|
||||||
private const val TlmOrange = 0xFFFFB000
|
private const val ACTIVE_BLUE = 0xFF648FFF
|
||||||
private const val NotHeardPink = 0xFFDC267F
|
private const val TLM_ORANGE = 0xFFFFB000
|
||||||
private const val ConflictDeepOrange = 0xFFFE6100
|
private const val NOT_HEARD_PINK = 0xFFDC267F
|
||||||
private const val NoReportGray = 0xFFC0C0C0
|
private const val CONFLICT_DEEP_ORANGE = 0xFFFE6100
|
||||||
|
private const val NO_REPORT_GRAY = 0xFFC0C0C0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,4 +71,36 @@ class RemoteSource(
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getAmSatCatalog(): String? = withContext(dispatcher) {
|
||||||
|
try {
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url("https://www.amsat.org/status/api/v1/catalog.php")
|
||||||
|
.header("User-Agent", "Look4Sat/4.5.5")
|
||||||
|
.build()
|
||||||
|
httpClient.newCall(request).execute().use { response ->
|
||||||
|
if (!response.isSuccessful) return@use null
|
||||||
|
response.body?.string()
|
||||||
|
}
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
println("RemoteSource amsat catalog exception: $exception")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getAmSatReports(hours: Int, limit: Int): String? = withContext(dispatcher) {
|
||||||
|
try {
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url("https://www.amsat.org/status/api/v1/reports.php?hours=$hours&limit=$limit")
|
||||||
|
.header("User-Agent", "Look4Sat/4.5.5")
|
||||||
|
.build()
|
||||||
|
httpClient.newCall(request).execute().use { response ->
|
||||||
|
if (!response.isSuccessful) return@use null
|
||||||
|
response.body?.string()
|
||||||
|
}
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
println("RemoteSource amsat reports exception: $exception")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
/* AmSatApiClient.kt - AMSAT official status API v1 client (pure JVM).
|
|
||||||
* Endpoints (verified 2026-08):
|
|
||||||
* GET https://www.amsat.org/status/api/v1/catalog.php -> satellite list
|
|
||||||
* GET https://www.amsat.org/status/api/v1/reports.php?hours=N&limit=500 -> reports
|
|
||||||
* Report fields: id, name ("SO-50_[FM]"), callsign, report, grid_square, reported_time (ISO 8601 UTC).
|
|
||||||
* Status values: Heard / Telemetry Only / Not Heard / Crew Active.
|
|
||||||
*/
|
|
||||||
package com.rtbishop.look4sat.core.domain.amsat
|
|
||||||
|
|
||||||
import org.json.JSONObject
|
|
||||||
import org.json.JSONArray
|
|
||||||
import java.io.BufferedReader
|
|
||||||
import java.net.HttpURLConnection
|
|
||||||
import java.net.URL
|
|
||||||
|
|
||||||
/** One report from the AMSAT API. */
|
|
||||||
data class ApiReport(
|
|
||||||
val id: String,
|
|
||||||
val name: String, // "SO-50_[FM]" (API name, includes mode suffix)
|
|
||||||
val callsign: String,
|
|
||||||
val report: String, // Heard / Telemetry Only / Not Heard / Crew Active
|
|
||||||
val gridSquare: String,
|
|
||||||
val reportedTimeUtcSec: Long
|
|
||||||
)
|
|
||||||
|
|
||||||
/** AMSAT official satellite status API v1 client. */
|
|
||||||
class AmSatApiClient(private val baseUrl: String = "https://www.amsat.org/status/api/v1") {
|
|
||||||
|
|
||||||
/** Fetch the full satellite catalog. Returns API names (e.g. "SO-50_[FM]"). */
|
|
||||||
fun fetchCatalog(): List<String> {
|
|
||||||
val body = httpGet("$baseUrl/catalog.php") ?: return emptyList()
|
|
||||||
return try {
|
|
||||||
val arr = JSONObject(body).getJSONArray("data")
|
|
||||||
(0 until arr.length()).map { arr.getJSONObject(it).getString("name") }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Fetch reports for a rolling UTC window. Empty on failure. */
|
|
||||||
fun fetchAllReports(hours: Int = 168): List<ApiReport> {
|
|
||||||
val body = httpGet("$baseUrl/reports.php?hours=$hours&limit=500") ?: return emptyList()
|
|
||||||
return try {
|
|
||||||
val arr = JSONObject(body).getJSONArray("data")
|
|
||||||
(0 until arr.length()).mapNotNull { i ->
|
|
||||||
val o = arr.getJSONObject(i)
|
|
||||||
val iso = o.optString("reported_time", "")
|
|
||||||
if (iso.isEmpty()) null else ApiReport(
|
|
||||||
id = o.optString("id", ""),
|
|
||||||
name = o.optString("name", ""),
|
|
||||||
callsign = o.optString("callsign", ""),
|
|
||||||
report = o.optString("report", ""),
|
|
||||||
gridSquare = o.optString("grid_square", ""),
|
|
||||||
reportedTimeUtcSec = parseIsoUtcSec(iso)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Parse "2026-08-05T07:30:00Z" to epoch seconds (minSdk 24: no java.time). */
|
|
||||||
fun parseIsoUtcSec(iso: String): Long {
|
|
||||||
val m = Regex("""(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})""").find(iso) ?: return 0L
|
|
||||||
val (y, mo, d, h, mi, s) = m.destructured
|
|
||||||
val days = daysFromCivil(y.toInt(), mo.toInt(), d.toInt())
|
|
||||||
return days * 86400L + h.toInt() * 3600L + mi.toInt() * 60L + s.toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Days since 1970-01-01 (civil calendar, proleptic Gregorian). */
|
|
||||||
private fun daysFromCivil(year: Int, month: Int, day: Int): Long {
|
|
||||||
val y = if (month <= 2) year - 1 else year
|
|
||||||
val era = (if (y >= 0) y else y - 399) / 400
|
|
||||||
val yoe = y - era * 400
|
|
||||||
val doy = (153 * (if (month > 2) month - 3 else month + 9) + 2) / 5 + day - 1
|
|
||||||
val doe = yoe * 365 + yoe / 4 - yoe / 100 + doy
|
|
||||||
return era * 146097L + doe - 719468
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun httpGet(url: String): String? {
|
|
||||||
return try {
|
|
||||||
val conn = URL(url).openConnection() as HttpURLConnection
|
|
||||||
conn.requestMethod = "GET"
|
|
||||||
conn.connectTimeout = 15000
|
|
||||||
conn.readTimeout = 20000
|
|
||||||
conn.setRequestProperty("User-Agent", "Look4Sat/4.5.5")
|
|
||||||
if (conn.responseCode !in 200..299) {
|
|
||||||
conn.disconnect()
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
val text = conn.inputStream.bufferedReader().use(BufferedReader::readText)
|
|
||||||
conn.disconnect()
|
|
||||||
text
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bayesian Morse timing decoder.
|
|
||||||
* Replaces hard thresholds with probability-based decision making.
|
|
||||||
*
|
|
||||||
* Inspired by VE3NEA's CW Skimmer approach:
|
|
||||||
* "Instead of making a hard decision at every input sample whether the signal
|
|
||||||
* is present or not, compute the probability that the signal is present."
|
|
||||||
*
|
|
||||||
* Uses Gaussian probability density centered on expected durations:
|
|
||||||
* P(dit | duration) = exp(-(duration - dotMs)^2 / (2 * variance^2))
|
|
||||||
* P(dash | duration) = exp(-(duration - 3*dotMs)^2 / (2 * variance^2))
|
|
||||||
*/
|
|
||||||
internal class CwBayesianDecoder {
|
|
||||||
|
|
||||||
// Morse timing parameters
|
|
||||||
private var dotDurationMs = 60f // initial 20 WPM
|
|
||||||
private var speedWpm = 20f
|
|
||||||
|
|
||||||
// Current symbol being accumulated
|
|
||||||
private var currentSymbol = StringBuilder()
|
|
||||||
private var textBuffer = StringBuilder()
|
|
||||||
|
|
||||||
// Recent dit lengths for speed estimation
|
|
||||||
private val recentDits = mutableListOf<Float>()
|
|
||||||
|
|
||||||
// Output
|
|
||||||
private var _decodedText = ""
|
|
||||||
val decodedText: String get() = _decodedText
|
|
||||||
|
|
||||||
/** Gaussian probability. */
|
|
||||||
private fun gaussianProb(durationMs: Float, expectedMs: Float, varianceMs: Float): Float {
|
|
||||||
if (varianceMs <= 0f) return 0f
|
|
||||||
val diff = durationMs - expectedMs
|
|
||||||
return kotlin.math.exp(-(diff * diff) / (2 * varianceMs * varianceMs))
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Process a tone duration. Returns the symbol type with highest probability. */
|
|
||||||
fun processTone(durationMs: Float): ToneResult {
|
|
||||||
val ditProb = gaussianProb(durationMs, dotDurationMs, dotDurationMs * 0.4f)
|
|
||||||
val dashProb = gaussianProb(durationMs, dotDurationMs * 3f, dotDurationMs * 0.6f)
|
|
||||||
|
|
||||||
return if (ditProb > dashProb && ditProb > 0.05f) {
|
|
||||||
currentSymbol.append('0')
|
|
||||||
recentDits.add(durationMs)
|
|
||||||
updateSpeed()
|
|
||||||
ToneResult('0', ditProb)
|
|
||||||
} else if (dashProb > 0.05f) {
|
|
||||||
currentSymbol.append('1')
|
|
||||||
ToneResult('1', dashProb)
|
|
||||||
} else {
|
|
||||||
ToneResult(null, 0f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Process a gap duration. Returns decoded character or null. */
|
|
||||||
fun processGap(durationMs: Float): Char? {
|
|
||||||
if (currentSymbol.isEmpty()) {
|
|
||||||
val wordProb = gaussianProb(durationMs, dotDurationMs * 7f, dotDurationMs * 1.2f)
|
|
||||||
if (wordProb > 0.2f) {
|
|
||||||
textBuffer.append(' ')
|
|
||||||
_decodedText = textBuffer.toString()
|
|
||||||
return ' '
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
val interCharProb = gaussianProb(durationMs, dotDurationMs * 3f, dotDurationMs * 0.6f)
|
|
||||||
val wordProb = gaussianProb(durationMs, dotDurationMs * 7f, dotDurationMs * 1.2f)
|
|
||||||
|
|
||||||
if (wordProb > interCharProb && wordProb > 0.2f) {
|
|
||||||
val char = flushSymbol()
|
|
||||||
textBuffer.append(' ')
|
|
||||||
_decodedText = textBuffer.toString()
|
|
||||||
return char
|
|
||||||
}
|
|
||||||
if (interCharProb > 0.15f) {
|
|
||||||
val char = flushSymbol()
|
|
||||||
_decodedText = textBuffer.toString()
|
|
||||||
return char
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun flushSymbol(): Char? {
|
|
||||||
if (currentSymbol.isEmpty()) return null
|
|
||||||
val morse = currentSymbol.toString()
|
|
||||||
currentSymbol.clear()
|
|
||||||
val char = morseToChar(morse)
|
|
||||||
if (char != null) textBuffer.append(char)
|
|
||||||
return char
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateSpeed() {
|
|
||||||
if (recentDits.size < 3) return
|
|
||||||
val sorted = recentDits.sorted()
|
|
||||||
val median = sorted[sorted.size / 2]
|
|
||||||
if (median > 0f) {
|
|
||||||
dotDurationMs = dotDurationMs * 0.7f + median * 0.3f
|
|
||||||
val wpm = 60.0f / (50.0f * dotDurationMs / 1000.0f)
|
|
||||||
if (wpm in 5f..55f) speedWpm = wpm
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSpeed(): Float = speedWpm
|
|
||||||
|
|
||||||
fun reset() {
|
|
||||||
dotDurationMs = 60f
|
|
||||||
speedWpm = 20f
|
|
||||||
recentDits.clear()
|
|
||||||
currentSymbol.clear()
|
|
||||||
textBuffer.clear()
|
|
||||||
_decodedText = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val MORSE_TABLE = mapOf(
|
|
||||||
"01" to 'A', "1000" to 'B', "1010" to 'C', "100" to 'D', "0" to 'E',
|
|
||||||
"0010" to 'F', "110" to 'G', "0000" to 'H', "00" to 'I', "0111" to 'J',
|
|
||||||
"101" to 'K', "0100" to 'L', "11" to 'M', "10" to 'N', "111" to 'O',
|
|
||||||
"0110" to 'P', "1101" to 'Q', "010" to 'R', "000" to 'S', "1" to 'T',
|
|
||||||
"001" to 'U', "0001" to 'V', "011" to 'W', "1001" to 'X', "1011" to 'Y',
|
|
||||||
"1100" to 'Z', "01111" to '1', "00111" to '2', "00011" to '3',
|
|
||||||
"00001" to '4', "00000" to '5', "10000" to '6', "11000" to '7',
|
|
||||||
"11100" to '8', "11110" to '9', "11111" to '0',
|
|
||||||
"010101" to '.', "110011" to ',', "001100" to '?', "011110" to '\'',
|
|
||||||
"101011" to '!', "10010" to '/', "10110" to '(', "101101" to ')',
|
|
||||||
"01000" to '&', "111000" to ':', "101010" to ';', "10001" to '=',
|
|
||||||
"01010" to '+', "100001" to '-', "001101" to '_', "010010" to '"',
|
|
||||||
"0001001" to '$', "011010" to '@'
|
|
||||||
)
|
|
||||||
fun morseToChar(morse: String): Char? = MORSE_TABLE[morse]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class ToneResult(val symbol: Char?, val probability: Float)
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Multi-channel CW signal tracker.
|
|
||||||
* Monitors the spectrogram for active frequency bins and extracts
|
|
||||||
* energy envelopes for each detected signal.
|
|
||||||
*
|
|
||||||
* Inspired by CW Skimmer's multi-channel approach:
|
|
||||||
* tracks all active signals in the passband simultaneously,
|
|
||||||
* selects the best one for decoded output.
|
|
||||||
*/
|
|
||||||
internal class CwChannelTracker(
|
|
||||||
private val spectrogram: CwSpectrogram,
|
|
||||||
private val maxChannels: Int = 3
|
|
||||||
) {
|
|
||||||
data class Channel(
|
|
||||||
val bin: Int,
|
|
||||||
val frequency: Float,
|
|
||||||
var active: Boolean = false,
|
|
||||||
var energy: Float = 0f,
|
|
||||||
val history: MutableList<Float> = mutableListOf(),
|
|
||||||
var confidence: Float = 0f
|
|
||||||
)
|
|
||||||
|
|
||||||
private val channels = Array(maxChannels) { Channel(0, 0f) }
|
|
||||||
|
|
||||||
/** Scan the current spectrogram column and update channel tracking. */
|
|
||||||
fun update(): List<Channel> {
|
|
||||||
val col = spectrogram.getCurrentColumn()
|
|
||||||
val peaks = findPeaks(col, threshold = 0.3f, minDistance = 2)
|
|
||||||
|
|
||||||
// Update existing channels
|
|
||||||
for (ch in channels) {
|
|
||||||
if (ch.active) {
|
|
||||||
if (peaks.contains(ch.bin)) {
|
|
||||||
ch.energy = col[ch.bin]
|
|
||||||
ch.history.add(ch.energy)
|
|
||||||
if (ch.history.size > 40) ch.history.removeAt(0)
|
|
||||||
ch.confidence = computeConfidence(ch.history)
|
|
||||||
} else {
|
|
||||||
// Signal lost — decay confidence
|
|
||||||
ch.history.add(0f)
|
|
||||||
if (ch.history.size > 40) ch.history.removeAt(0)
|
|
||||||
ch.confidence *= 0.9f
|
|
||||||
if (ch.confidence < 0.1f) ch.active = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assign new peaks to inactive channels
|
|
||||||
var peakIdx = 0
|
|
||||||
for (ch in channels) {
|
|
||||||
if (!ch.active && peakIdx < peaks.size) {
|
|
||||||
val bin = peaks[peakIdx]
|
|
||||||
val freq = spectrogram.binToFreq(bin)
|
|
||||||
// Re-initialize channel
|
|
||||||
channels[peakIdx] = Channel(bin, freq, true, col[bin], mutableListOf(), 0.5f)
|
|
||||||
peakIdx++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return channels.filter { it.active }
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Find peak bins in the spectrum. */
|
|
||||||
private fun findPeaks(spectrum: FloatArray, threshold: Float, minDistance: Int): List<Int> {
|
|
||||||
val peaks = mutableListOf<Int>()
|
|
||||||
for (i in 1 until spectrum.size - 1) {
|
|
||||||
if (spectrum[i] > spectrum[i - 1] && spectrum[i] > spectrum[i + 1] && spectrum[i] > threshold) {
|
|
||||||
if (peaks.isEmpty() || i - peaks.last() >= minDistance) {
|
|
||||||
peaks.add(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return peaks.sortedByDescending { spectrum[it] }
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Compute confidence from energy history. Lower variance = higher confidence. */
|
|
||||||
private fun computeConfidence(history: List<Float>): Float {
|
|
||||||
if (history.size < 10) return 0.3f
|
|
||||||
val recent = history.takeLast(10)
|
|
||||||
val mean = recent.average().toFloat()
|
|
||||||
val variance = recent.map { (it - mean) * (it - mean) }.average().toFloat()
|
|
||||||
return if (mean > 0f) (mean / (mean + variance + 0.1f)).coerceIn(0f, 1f) else 0f
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the channel with highest confidence. */
|
|
||||||
fun getBestChannel(): Channel? {
|
|
||||||
return channels.filter { it.active }.maxByOrNull { it.confidence }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun reset() {
|
|
||||||
for (i in channels.indices) {
|
|
||||||
channels[i] = Channel(0, 0f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns the model's `log_probs` output into text.
|
||||||
|
*
|
||||||
|
* Mirrors the reference `greedy_ctc_decode`: take the best class per frame,
|
||||||
|
* drop blanks, and collapse runs of the same label. A blank between two
|
||||||
|
* identical labels is what keeps a genuine double letter (for example the
|
||||||
|
* two N's in "5NN") from collapsing into one.
|
||||||
|
*/
|
||||||
|
object CwCtcDecoder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param logProbs `[batch, time, class]`; only batch 0 is read.
|
||||||
|
* @param chars class index to symbol, excluding the blank.
|
||||||
|
* @param blankIndex the CTC blank class (41 for this model).
|
||||||
|
*/
|
||||||
|
fun greedy(
|
||||||
|
logProbs: Array<Array<FloatArray>>,
|
||||||
|
chars: List<String>,
|
||||||
|
blankIndex: Int
|
||||||
|
): String {
|
||||||
|
if (logProbs.isEmpty()) return ""
|
||||||
|
val frames = logProbs[0]
|
||||||
|
val builder = StringBuilder()
|
||||||
|
var previous = -1
|
||||||
|
|
||||||
|
for (frame in frames) {
|
||||||
|
var best = 0
|
||||||
|
for (i in 1 until frame.size) {
|
||||||
|
if (frame[i] > frame[best]) best = i
|
||||||
|
}
|
||||||
|
if (best == blankIndex) {
|
||||||
|
previous = -1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (best != previous && best < chars.size) {
|
||||||
|
builder.append(chars[best])
|
||||||
|
}
|
||||||
|
previous = best
|
||||||
|
}
|
||||||
|
return builder.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CW (Morse code) decoder v3 — Spectrogram-based multi-channel Bayesian decoder.
|
|
||||||
*
|
|
||||||
* Architecture inspired by Morse Expert / CW Skimmer (VE3NEA):
|
|
||||||
* 1. FFT spectrogram creates a frequency×time matrix
|
|
||||||
* 2. Multi-channel peak detector finds all active signals
|
|
||||||
* 3. Per-channel energy envelope extraction
|
|
||||||
* 4. Bayesian probability for symbol timing (Gaussian likelihood)
|
|
||||||
* 5. Best channel selected for output
|
|
||||||
*
|
|
||||||
* Timing analysis is performed per spectrogram column (hop).
|
|
||||||
* Each column represents hopSize/sampleRate seconds of audio.
|
|
||||||
*/
|
|
||||||
class CwDecoder(
|
|
||||||
val sampleRate: Int = 8000,
|
|
||||||
cwToneFreq: Float = -1f // ignored in v3 (auto-detect via spectrogram)
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
private const val FFT_SIZE = 256
|
|
||||||
private const val HOP_SIZE = 64
|
|
||||||
}
|
|
||||||
|
|
||||||
private val spectrogram = CwSpectrogram(
|
|
||||||
fftSize = FFT_SIZE,
|
|
||||||
hopSize = HOP_SIZE,
|
|
||||||
sampleRate = sampleRate,
|
|
||||||
minBin = 6,
|
|
||||||
maxBin = 38,
|
|
||||||
historyCols = 40
|
|
||||||
)
|
|
||||||
private val channelTracker = CwChannelTracker(spectrogram, maxChannels = 3)
|
|
||||||
private val bayesianDecoder = CwBayesianDecoder()
|
|
||||||
|
|
||||||
// Timing state per channel
|
|
||||||
private data class ChannelTiming(
|
|
||||||
var isSignal: Boolean = false,
|
|
||||||
var toneTicks: Int = 0,
|
|
||||||
var gapTicks: Int = 0
|
|
||||||
)
|
|
||||||
private val timingStates = Array(3) { ChannelTiming() }
|
|
||||||
|
|
||||||
// Time per spectrogram column in milliseconds
|
|
||||||
private val tickMs = 1000f * HOP_SIZE / sampleRate
|
|
||||||
|
|
||||||
// Output flows
|
|
||||||
private val _decodedTextFlow = MutableStateFlow("")
|
|
||||||
val decodedTextFlow: StateFlow<String> = _decodedTextFlow
|
|
||||||
|
|
||||||
private val _signalStrength = MutableStateFlow(0f)
|
|
||||||
val signalStrength: StateFlow<Float> = _signalStrength
|
|
||||||
|
|
||||||
private val _estimatedPitch = MutableStateFlow<Float?>(null)
|
|
||||||
val estimatedPitch: StateFlow<Float?> = _estimatedPitch
|
|
||||||
|
|
||||||
private val _estimatedSpeed = MutableStateFlow<Float?>(null)
|
|
||||||
val estimatedSpeed: StateFlow<Float?> = _estimatedSpeed
|
|
||||||
|
|
||||||
private var frameCount = 0
|
|
||||||
|
|
||||||
init {
|
|
||||||
if (cwToneFreq > 0f) {
|
|
||||||
_estimatedPitch.value = cwToneFreq
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun processBuffer(buffer: FloatArray) {
|
|
||||||
// 1. Feed samples to spectrogram
|
|
||||||
spectrogram.addSamples(buffer)
|
|
||||||
|
|
||||||
// 2. Get number of new columns generated
|
|
||||||
val newCols = spectrogram.getNewColumns()
|
|
||||||
if (newCols == 0) return
|
|
||||||
|
|
||||||
// 3. Update channel tracker (uses latest column for peak detection)
|
|
||||||
val activeChannels = channelTracker.update()
|
|
||||||
|
|
||||||
// 4. Process each new column for timing analysis
|
|
||||||
// Columns are indexed 0..historyCols-1, where historyCols-1 is the newest
|
|
||||||
val baseIdx = (spectrogram.historyCols - newCols).coerceAtLeast(0)
|
|
||||||
for (colOffset in 0 until newCols) {
|
|
||||||
val col = spectrogram.getColumn(baseIdx + colOffset)
|
|
||||||
|
|
||||||
for ((idx, channel) in activeChannels.withIndex()) {
|
|
||||||
if (idx >= timingStates.size) break
|
|
||||||
val state = timingStates[idx]
|
|
||||||
val energy = if (channel.bin in col.indices) col[channel.bin] else 0f
|
|
||||||
|
|
||||||
// Adaptive threshold
|
|
||||||
val threshold = 0.3f + (energy - 0.3f) * 0.3f
|
|
||||||
|
|
||||||
if (energy > threshold) {
|
|
||||||
if (!state.isSignal) {
|
|
||||||
if (state.gapTicks > 0) {
|
|
||||||
val gapMs = state.gapTicks * tickMs
|
|
||||||
bayesianDecoder.processGap(gapMs)
|
|
||||||
}
|
|
||||||
state.gapTicks = 0
|
|
||||||
state.isSignal = true
|
|
||||||
}
|
|
||||||
state.toneTicks++
|
|
||||||
} else {
|
|
||||||
if (state.isSignal) {
|
|
||||||
if (state.toneTicks > 0) {
|
|
||||||
val toneMs = state.toneTicks * tickMs
|
|
||||||
bayesianDecoder.processTone(toneMs)
|
|
||||||
}
|
|
||||||
state.toneTicks = 0
|
|
||||||
state.isSignal = false
|
|
||||||
}
|
|
||||||
state.gapTicks++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Update outputs
|
|
||||||
frameCount++
|
|
||||||
if (frameCount % 5 == 0) {
|
|
||||||
val bestChannel = channelTracker.getBestChannel()
|
|
||||||
if (bestChannel != null) {
|
|
||||||
_estimatedPitch.value = bestChannel.frequency
|
|
||||||
_signalStrength.value = bestChannel.confidence
|
|
||||||
_estimatedSpeed.value = bayesianDecoder.getSpeed()
|
|
||||||
}
|
|
||||||
_decodedTextFlow.value = bayesianDecoder.decodedText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun resetDecoder() {
|
|
||||||
spectrogram.reset()
|
|
||||||
channelTracker.reset()
|
|
||||||
bayesianDecoder.reset()
|
|
||||||
for (state in timingStates) {
|
|
||||||
state.isSignal = false
|
|
||||||
state.toneTicks = 0
|
|
||||||
state.gapTicks = 0
|
|
||||||
}
|
|
||||||
frameCount = 0
|
|
||||||
_decodedTextFlow.value = ""
|
|
||||||
_signalStrength.value = 0f
|
|
||||||
_estimatedPitch.value = null
|
|
||||||
_estimatedSpeed.value = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bounded rolling audio buffer that drives periodic full re-decodes.
|
||||||
|
*
|
||||||
|
* DeepCW is a whole-segment CTC model. It rewrites earlier output as more
|
||||||
|
* context arrives — measured on one clip, 6 of 11 progressively longer reads
|
||||||
|
* revised the prefix (`BM` -> `BG7` -> `BG7NTI` -> `BG7NTA`). Appending only
|
||||||
|
* the newest fragment therefore leaves those intermediate guesses on screen
|
||||||
|
* forever; measured character error rate for sliding-window stitching ranged
|
||||||
|
* from 67% to 294%, against 0% for decoding the whole segment at once.
|
||||||
|
*
|
||||||
|
* So we keep a fixed window, re-run the model over all of it every
|
||||||
|
* [redecodeIntervalMs], and replace the displayed text outright.
|
||||||
|
*
|
||||||
|
* Defaults come from measurement: 20 s is the smallest window that reaches
|
||||||
|
* 0.0% CER (16 s still errs at 5.9%), while inference cost grows
|
||||||
|
* super-linearly — 60 s of audio needs roughly 13x longer to decode than
|
||||||
|
* 20 s does, leaving too little real-time headroom.
|
||||||
|
*/
|
||||||
|
class CwDeepBuffer(
|
||||||
|
sampleRate: Int = CwDeepSpectrogram.SAMPLE_RATE,
|
||||||
|
maxSeconds: Double = DEFAULT_MAX_SECONDS,
|
||||||
|
private val redecodeIntervalMs: Int = DEFAULT_REDECODE_INTERVAL_MS
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
const val DEFAULT_MAX_SECONDS = 20.0
|
||||||
|
const val DEFAULT_REDECODE_INTERVAL_MS = 1500
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Maximum number of samples retained. */
|
||||||
|
val capacity: Int = (sampleRate * maxSeconds).toInt()
|
||||||
|
|
||||||
|
private val samplesPerInterval: Int = sampleRate * redecodeIntervalMs / 1000
|
||||||
|
private val ring = FloatArray(capacity)
|
||||||
|
private var writeIndex = 0
|
||||||
|
private var filled = 0
|
||||||
|
private var sinceLastRedecode = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Samples evicted from the ring once it is full. They are the audio that
|
||||||
|
* has scrolled out of the 20 s window, and are handed off (via
|
||||||
|
* [drainOverflow]) so the decoder can archive them into permanent history
|
||||||
|
* instead of silently dropping the corresponding text. Pre-allocated to
|
||||||
|
* [capacity]: overflow never exceeds one window before it is drained.
|
||||||
|
*/
|
||||||
|
private val overflow = FloatArray(capacity)
|
||||||
|
private var overflowSize = 0
|
||||||
|
|
||||||
|
/** Samples currently buffered, never above [capacity]. */
|
||||||
|
val size: Int get() = filled
|
||||||
|
|
||||||
|
/** Samples currently held in the overflow (awaiting archival). */
|
||||||
|
val overflowCount: Int get() = overflowSize
|
||||||
|
|
||||||
|
/** True once there is enough audio for the spectrogram to yield a frame. */
|
||||||
|
val hasEnoughAudio: Boolean get() = filled >= CwDeepSpectrogram.FFT_LENGTH
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append captured audio, overwriting the oldest samples when full.
|
||||||
|
*
|
||||||
|
* @return true when [redecodeIntervalMs] of audio has accumulated since
|
||||||
|
* the last time this returned true, meaning the caller should re-decode.
|
||||||
|
*/
|
||||||
|
fun append(chunk: FloatArray): Boolean {
|
||||||
|
if (chunk.isNotEmpty()) {
|
||||||
|
// A chunk longer than the window can only contribute its tail.
|
||||||
|
val start = maxOf(0, chunk.size - capacity)
|
||||||
|
for (i in start until chunk.size) {
|
||||||
|
if (filled == capacity) {
|
||||||
|
// The slot we are about to overwrite holds the oldest
|
||||||
|
// sample — move it to the overflow for archival.
|
||||||
|
overflow[overflowSize++] = ring[writeIndex]
|
||||||
|
}
|
||||||
|
ring[writeIndex] = chunk[i]
|
||||||
|
writeIndex = (writeIndex + 1) % capacity
|
||||||
|
if (filled < capacity) filled++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sinceLastRedecode += chunk.size
|
||||||
|
if (sinceLastRedecode >= samplesPerInterval) {
|
||||||
|
sinceLastRedecode -= samplesPerInterval
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Buffered audio in chronological order, as a copy safe to hand off. */
|
||||||
|
fun snapshot(): FloatArray {
|
||||||
|
val out = FloatArray(filled)
|
||||||
|
if (filled == 0) return out
|
||||||
|
val start = (writeIndex - filled + capacity) % capacity
|
||||||
|
val firstRun = minOf(filled, capacity - start)
|
||||||
|
ring.copyInto(out, 0, start, start + firstRun)
|
||||||
|
if (firstRun < filled) {
|
||||||
|
ring.copyInto(out, firstRun, 0, filled - firstRun)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the evicted samples (chronological order) and clear the overflow.
|
||||||
|
* Safe to call every append; returns an empty array when nothing has been
|
||||||
|
* evicted yet.
|
||||||
|
*/
|
||||||
|
fun drainOverflow(): FloatArray {
|
||||||
|
if (overflowSize == 0) return FloatArray(0)
|
||||||
|
val out = overflow.copyOf(overflowSize)
|
||||||
|
overflowSize = 0
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Drop all audio (including pending overflow) and restart the interval. */
|
||||||
|
fun reset() {
|
||||||
|
writeIndex = 0
|
||||||
|
filled = 0
|
||||||
|
sinceLastRedecode = 0
|
||||||
|
overflowSize = 0
|
||||||
|
ring.fill(0f)
|
||||||
|
overflow.fill(0f)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
/*
|
||||||
|
* 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.ceil
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.floor
|
||||||
|
import kotlin.math.ln1p
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audio front-end for the DeepCW model: turns PCM samples into the
|
||||||
|
* `[time, frequency]` log-magnitude spectrogram the network expects.
|
||||||
|
*
|
||||||
|
* Mirrors the upstream Python reference (deepcw-engine
|
||||||
|
* `examples/python/decode_morse.py`) step for step:
|
||||||
|
*
|
||||||
|
* resample -> 3200 Hz, reflect-pad by fft/2, periodic Hann window of 256,
|
||||||
|
* real FFT, keep bins [32, 97) i.e. 400-1200 Hz, then log1p.
|
||||||
|
*
|
||||||
|
* The model's fixed 400-1200 Hz window means pitch detection is built in —
|
||||||
|
* no spectral peak tracking or squelch gating is needed on our side.
|
||||||
|
*/
|
||||||
|
object CwDeepSpectrogram {
|
||||||
|
|
||||||
|
/** Model input sample rate, from `model.onnx.json`. */
|
||||||
|
const val SAMPLE_RATE = 3200
|
||||||
|
|
||||||
|
/** FFT window length in samples. */
|
||||||
|
const val FFT_LENGTH = 256
|
||||||
|
|
||||||
|
/** Hop between consecutive frames; 48/3200 = 15.0 ms per frame. */
|
||||||
|
const val HOP_LENGTH = 48
|
||||||
|
|
||||||
|
private const val MIN_FREQ_HZ = 400.0
|
||||||
|
private const val MAX_FREQ_HZ = 1200.0
|
||||||
|
|
||||||
|
/** Number of frequency bins the model expects. */
|
||||||
|
const val FREQUENCY_BINS = 65
|
||||||
|
|
||||||
|
/** Milliseconds of audio represented by one output frame. */
|
||||||
|
const val MS_PER_FRAME = 1000.0 * HOP_LENGTH / SAMPLE_RATE
|
||||||
|
|
||||||
|
private val hannWindow: FloatArray = FloatArray(FFT_LENGTH) { i ->
|
||||||
|
// numpy: np.hanning(N + 1)[:-1] — the periodic (not symmetric) variant.
|
||||||
|
(0.5 - 0.5 * cos(2.0 * PI * i / FFT_LENGTH)).toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inclusive-exclusive bin range covering [minHz, maxHz].
|
||||||
|
* Returns `start to stop`, matching the reference's `frequency_bin_range`.
|
||||||
|
*/
|
||||||
|
fun frequencyBinRange(
|
||||||
|
sampleRate: Int,
|
||||||
|
fftLength: Int,
|
||||||
|
minHz: Double,
|
||||||
|
maxHz: Double
|
||||||
|
): Pair<Int, Int> {
|
||||||
|
val binHz = sampleRate.toDouble() / fftLength
|
||||||
|
val start = ceil(minHz / binHz).toInt()
|
||||||
|
val stop = floor(maxHz / binHz).toInt() + 1
|
||||||
|
return start to stop
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Linear-interpolation resampler. Deliberately dependency-light and
|
||||||
|
* identical to the reference implementation so spectrograms match.
|
||||||
|
*/
|
||||||
|
fun resampleLinear(audio: FloatArray, sourceRate: Int, targetRate: Int): FloatArray {
|
||||||
|
if (sourceRate == targetRate || audio.isEmpty()) return audio
|
||||||
|
val targetLength = (audio.size.toDouble() * targetRate / sourceRate).roundToInt()
|
||||||
|
val out = FloatArray(targetLength)
|
||||||
|
val ratio = sourceRate.toDouble() / targetRate
|
||||||
|
for (i in 0 until targetLength) {
|
||||||
|
val position = i * ratio
|
||||||
|
val left = floor(position).toInt()
|
||||||
|
val right = minOf(left + 1, audio.size - 1)
|
||||||
|
val fraction = (position - left).toFloat()
|
||||||
|
out[i] = audio[left] * (1f - fraction) + audio[right] * fraction
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the log-magnitude spectrogram. Input must already be at
|
||||||
|
* [SAMPLE_RATE]; use [resampleLinear] first when it is not.
|
||||||
|
*
|
||||||
|
* @return `[frames][FREQUENCY_BINS]` values, all non-negative.
|
||||||
|
*/
|
||||||
|
fun compute(audio: FloatArray): Array<FloatArray> {
|
||||||
|
require(audio.size >= FFT_LENGTH) {
|
||||||
|
"audio is too short for fftLength=$FFT_LENGTH, got ${audio.size}"
|
||||||
|
}
|
||||||
|
|
||||||
|
val (startBin, stopBin) = frequencyBinRange(
|
||||||
|
SAMPLE_RATE, FFT_LENGTH, MIN_FREQ_HZ, MAX_FREQ_HZ
|
||||||
|
)
|
||||||
|
val bins = stopBin - startBin
|
||||||
|
require(bins == FREQUENCY_BINS) {
|
||||||
|
"expected $FREQUENCY_BINS bins, computed $bins"
|
||||||
|
}
|
||||||
|
|
||||||
|
val padded = reflectPad(audio, FFT_LENGTH / 2)
|
||||||
|
val frames = 1 + (padded.size - FFT_LENGTH) / HOP_LENGTH
|
||||||
|
val result = Array(frames) { FloatArray(bins) }
|
||||||
|
|
||||||
|
val real = FloatArray(FFT_LENGTH)
|
||||||
|
val imag = FloatArray(FFT_LENGTH)
|
||||||
|
for (frame in 0 until frames) {
|
||||||
|
val offset = frame * HOP_LENGTH
|
||||||
|
for (i in 0 until FFT_LENGTH) {
|
||||||
|
real[i] = padded[offset + i] * hannWindow[i]
|
||||||
|
imag[i] = 0f
|
||||||
|
}
|
||||||
|
fftInPlace(real, imag)
|
||||||
|
val row = result[frame]
|
||||||
|
for (bin in startBin until stopBin) {
|
||||||
|
val magnitude = sqrt(real[bin] * real[bin] + imag[bin] * imag[bin])
|
||||||
|
row[bin - startBin] = ln1p(magnitude.toDouble()).toFloat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* numpy `mode="reflect"`: mirrors around the edge samples without
|
||||||
|
* repeating them, so [1,2,3] padded by 2 becomes [3,2,1,2,3,2,1].
|
||||||
|
*/
|
||||||
|
private fun reflectPad(audio: FloatArray, pad: Int): FloatArray {
|
||||||
|
if (pad == 0) return audio
|
||||||
|
val out = FloatArray(audio.size + 2 * pad)
|
||||||
|
for (i in 0 until pad) out[i] = audio[pad - i]
|
||||||
|
audio.copyInto(out, pad)
|
||||||
|
val last = audio.size - 1
|
||||||
|
for (i in 0 until pad) out[pad + audio.size + i] = audio[last - 1 - i]
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterative radix-2 Cooley-Tukey FFT. [FFT_LENGTH] is a power of two, so
|
||||||
|
* no padding case is needed. Only the first half of the output is read by
|
||||||
|
* [compute], which is the real-input equivalent of numpy's `rfft`.
|
||||||
|
*/
|
||||||
|
private fun fftInPlace(real: FloatArray, imag: FloatArray) {
|
||||||
|
val n = real.size
|
||||||
|
|
||||||
|
// Bit-reversal permutation.
|
||||||
|
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 or bit
|
||||||
|
if (i < j) {
|
||||||
|
var tmp = real[i]; real[i] = real[j]; real[j] = tmp
|
||||||
|
tmp = imag[i]; imag[i] = imag[j]; imag[j] = tmp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var length = 2
|
||||||
|
while (length <= n) {
|
||||||
|
val angle = -2.0 * PI / length
|
||||||
|
val wReal = cos(angle).toFloat()
|
||||||
|
val wImag = kotlin.math.sin(angle).toFloat()
|
||||||
|
var i = 0
|
||||||
|
while (i < n) {
|
||||||
|
var curReal = 1f
|
||||||
|
var curImag = 0f
|
||||||
|
for (k in 0 until length / 2) {
|
||||||
|
val evenReal = real[i + k]
|
||||||
|
val evenImag = imag[i + k]
|
||||||
|
val oddReal = real[i + k + length / 2]
|
||||||
|
val oddImag = imag[i + k + length / 2]
|
||||||
|
val mulReal = oddReal * curReal - oddImag * curImag
|
||||||
|
val mulImag = oddReal * curImag + oddImag * curReal
|
||||||
|
real[i + k] = evenReal + mulReal
|
||||||
|
imag[i + k] = evenImag + mulImag
|
||||||
|
real[i + k + length / 2] = evenReal - mulReal
|
||||||
|
imag[i + k + length / 2] = evenImag - mulImag
|
||||||
|
val nextReal = curReal * wReal - curImag * wImag
|
||||||
|
curImag = curReal * wImag + curImag * wReal
|
||||||
|
curReal = nextReal
|
||||||
|
}
|
||||||
|
i += length
|
||||||
|
}
|
||||||
|
length = length shl 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.cos
|
|
||||||
import kotlin.math.sin
|
|
||||||
import kotlin.math.sqrt
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DSP utilities for CW (Morse code) decoding.
|
|
||||||
* Pure Kotlin, no NDK required.
|
|
||||||
*/
|
|
||||||
internal object CwDsp {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Design a simple bandpass FIR filter coefficients using windowed sinc method.
|
|
||||||
* @param lowCutoff lower cutoff frequency (Hz) as fraction of sampleRate
|
|
||||||
* @param highCutoff upper cutoff frequency (Hz) as fraction of sampleRate
|
|
||||||
* @param taps filter length (must be odd)
|
|
||||||
*/
|
|
||||||
fun bandpassFir(lowCutoff: Double, highCutoff: Double, taps: Int): FloatArray {
|
|
||||||
val n = if (taps % 2 == 0) taps + 1 else taps
|
|
||||||
val half = n / 2
|
|
||||||
val coeffs = FloatArray(n)
|
|
||||||
for (i in 0 until n) {
|
|
||||||
val idx = i - half
|
|
||||||
if (idx == 0) {
|
|
||||||
coeffs[i] = (2.0 * (highCutoff - lowCutoff)).toFloat()
|
|
||||||
} else {
|
|
||||||
val x = PI * idx
|
|
||||||
coeffs[i] = ((sin(2 * highCutoff * x) - sin(2 * lowCutoff * x)) / x).toFloat()
|
|
||||||
}
|
|
||||||
// Hamming window
|
|
||||||
coeffs[i] = (coeffs[i] * (0.54 - 0.46 * cos(2 * PI * i / (n - 1)))).toFloat()
|
|
||||||
}
|
|
||||||
// Normalize
|
|
||||||
val sum = coeffs.sum()
|
|
||||||
if (sum != 0f) for (i in 0 until n) coeffs[i] /= sum
|
|
||||||
return coeffs
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Apply FIR filter to a buffer. */
|
|
||||||
fun applyFir(buffer: FloatArray, coeffs: FloatArray): FloatArray {
|
|
||||||
val out = FloatArray(buffer.size)
|
|
||||||
for (i in buffer.indices) {
|
|
||||||
var sum = 0f
|
|
||||||
for (j in coeffs.indices) {
|
|
||||||
val idx = i - j
|
|
||||||
if (idx >= 0) sum += buffer[idx] * coeffs[j]
|
|
||||||
}
|
|
||||||
out[i] = sum
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Simple envelope detector: abs + low-pass smoothing. */
|
|
||||||
fun envelope(signal: FloatArray, alpha: Float = 0.1f): FloatArray {
|
|
||||||
val env = FloatArray(signal.size)
|
|
||||||
var s = 0f
|
|
||||||
for (i in signal.indices) {
|
|
||||||
s = alpha * kotlin.math.abs(signal[i]) + (1 - alpha) * s
|
|
||||||
env[i] = s
|
|
||||||
}
|
|
||||||
return env
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Estimate noise floor from envelope for adaptive thresholding. */
|
|
||||||
fun noiseFloor(env: FloatArray, fraction: Float = 0.3f): Float {
|
|
||||||
val sorted = env.sortedArray()
|
|
||||||
val median = sorted[sorted.size / 2]
|
|
||||||
return median + (sorted[sorted.size * 9 / 10] - median) * fraction
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Simple Goertzel to detect a specific tone frequency. */
|
|
||||||
fun goertzel(buffer: FloatArray, targetFreq: Float, sampleRate: Int): Float {
|
|
||||||
val omega = 2.0 * PI * targetFreq / sampleRate
|
|
||||||
val coeff = 2.0 * cos(omega)
|
|
||||||
var s0 = 0.0; var s1 = 0.0; var s2 = 0.0
|
|
||||||
for (sample in buffer) {
|
|
||||||
s0 = sample.toDouble() + coeff * s1 - s2
|
|
||||||
s2 = s1; s1 = s0
|
|
||||||
}
|
|
||||||
val power = s2 * s2 + s1 * s1 - coeff * s1 * s2
|
|
||||||
return sqrt(kotlin.math.abs(power)).toFloat()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.cos
|
|
||||||
import kotlin.math.sqrt
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Radix-2 FFT for real-valued input.
|
|
||||||
* Produces magnitude spectrum for the first N/2+1 bins.
|
|
||||||
* Used by CwSpectrogram for time-frequency analysis.
|
|
||||||
*/
|
|
||||||
internal class CwFFT(private val n: Int) {
|
|
||||||
init {
|
|
||||||
require(n > 0 && n and (n - 1) == 0) { "FFT size must be power of 2, got $n" }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val cosTable = FloatArray(n / 2)
|
|
||||||
private val sinTable = FloatArray(n / 2)
|
|
||||||
|
|
||||||
init {
|
|
||||||
for (i in 0 until n / 2) {
|
|
||||||
val angle = -2.0 * kotlin.math.PI * i / n
|
|
||||||
cosTable[i] = cos(angle).toFloat()
|
|
||||||
sinTable[i] = kotlin.math.sin(angle).toFloat()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Compute magnitude spectrum for real input. Returns array of size n/2+1. */
|
|
||||||
fun magnitudeSpectrum(input: FloatArray): FloatArray {
|
|
||||||
require(input.size == n) { "Input size must be $n, got ${input.size}" }
|
|
||||||
|
|
||||||
val real = input.copyOf()
|
|
||||||
val imag = FloatArray(n)
|
|
||||||
|
|
||||||
// Bit-reversal permutation
|
|
||||||
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) {
|
|
||||||
var tmp = real[i]; real[i] = real[j]; real[j] = tmp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Radix-2 Cooley-Tukey FFT
|
|
||||||
var len = 2
|
|
||||||
while (len <= n) {
|
|
||||||
val half = len / 2
|
|
||||||
val step = n / len
|
|
||||||
for (i in 0 until n step len) {
|
|
||||||
for (k in 0 until half) {
|
|
||||||
val tReal = real[i + k + half] * cosTable[k * step] - imag[i + k + half] * sinTable[k * step]
|
|
||||||
val tImag = real[i + k + half] * sinTable[k * step] + imag[i + k + half] * cosTable[k * step]
|
|
||||||
real[i + k + half] = real[i + k] - tReal
|
|
||||||
imag[i + k + half] = imag[i + k] - tImag
|
|
||||||
real[i + k] += tReal
|
|
||||||
imag[i + k] += tImag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
len = len shl 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Magnitude spectrum (first N/2+1 bins)
|
|
||||||
val mag = FloatArray(n / 2 + 1)
|
|
||||||
for (i in 0..n / 2) {
|
|
||||||
mag[i] = sqrt(real[i] * real[i] + imag[i] * imag[i]) / n
|
|
||||||
}
|
|
||||||
return mag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.cos
|
|
||||||
import kotlin.math.sqrt
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Running Goertzel filter for CW tone detection.
|
|
||||||
* Tracks a specific frequency over time with a sliding window.
|
|
||||||
* Ported from ggmorse/src/goertzel.h
|
|
||||||
*/
|
|
||||||
internal class CwGoertzel {
|
|
||||||
private var s1 = 0.0
|
|
||||||
private var s2 = 0.0
|
|
||||||
private var coeff = 0.0
|
|
||||||
|
|
||||||
fun init(sampleRate: Float, targetFreq: Float) {
|
|
||||||
val omega = 2.0 * kotlin.math.PI * targetFreq / sampleRate
|
|
||||||
coeff = 2.0 * cos(omega)
|
|
||||||
s1 = 0.0
|
|
||||||
s2 = 0.0
|
|
||||||
}
|
|
||||||
|
|
||||||
fun process(sample: Float) {
|
|
||||||
val s0 = sample.toDouble() + coeff * s1 - s2
|
|
||||||
s2 = s1
|
|
||||||
s1 = s0
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getPower(): Float {
|
|
||||||
return sqrt(s2 * s2 + s1 * s1 - coeff * s1 * s2).toFloat()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun reset() {
|
|
||||||
s1 = 0.0
|
|
||||||
s2 = 0.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple linear resampler.
|
|
||||||
* Downsamples from input sample rate to output sample rate.
|
|
||||||
* Ported from ggmorse/src/resampler.h
|
|
||||||
*/
|
|
||||||
internal class CwResampler(private val inputRate: Float, private val outputRate: Float) {
|
|
||||||
private val ratio = inputRate / outputRate
|
|
||||||
private var lastSample = 0f
|
|
||||||
|
|
||||||
fun process(input: FloatArray): FloatArray {
|
|
||||||
if (ratio <= 0f || input.isEmpty()) return input
|
|
||||||
val outputLen = (input.size / ratio).toInt() + 1
|
|
||||||
val output = FloatArray(outputLen)
|
|
||||||
var idx = 0f
|
|
||||||
for (i in output.indices) {
|
|
||||||
val intIdx = idx.toInt()
|
|
||||||
val frac = idx - intIdx
|
|
||||||
if (intIdx + 1 < input.size) {
|
|
||||||
output[i] = input[intIdx] * (1 - frac) + input[intIdx + 1] * frac
|
|
||||||
} else if (intIdx < input.size) {
|
|
||||||
output[i] = input[intIdx] * (1 - frac) + lastSample * frac
|
|
||||||
} else {
|
|
||||||
output[i] = lastSample
|
|
||||||
}
|
|
||||||
idx += ratio
|
|
||||||
}
|
|
||||||
lastSample = input.lastOrNull() ?: lastSample
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
fun reset() {
|
|
||||||
lastSample = 0f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.cos
|
|
||||||
import kotlin.math.sin
|
|
||||||
import kotlin.math.sqrt
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lightweight pitch detector using DFT at specific frequency bins.
|
|
||||||
* Only scans [200, 1200] Hz in configurable steps — much faster than full FFT.
|
|
||||||
* Ported from ggmorse/src/stfft.h (simplified for CW use case).
|
|
||||||
*/
|
|
||||||
internal class CwPitchDetector(
|
|
||||||
private val sampleRate: Float,
|
|
||||||
private val minFreq: Float = 200f,
|
|
||||||
private val maxFreq: Float = 1200f,
|
|
||||||
private val stepHz: Float = 10f
|
|
||||||
) {
|
|
||||||
/**
|
|
||||||
* Find the dominant pitch frequency in the buffer.
|
|
||||||
* Returns null if no significant pitch found.
|
|
||||||
*/
|
|
||||||
fun findPitch(buffer: FloatArray): Float? {
|
|
||||||
if (buffer.isEmpty()) return null
|
|
||||||
var bestFreq = 0f
|
|
||||||
var bestPower = 0f
|
|
||||||
var freq = minFreq
|
|
||||||
while (freq <= maxFreq) {
|
|
||||||
var real = 0.0
|
|
||||||
var imag = 0.0
|
|
||||||
val omega = 2.0 * kotlin.math.PI * freq / sampleRate
|
|
||||||
for (i in buffer.indices) {
|
|
||||||
real += buffer[i] * cos(omega * i)
|
|
||||||
imag += buffer[i] * -sin(omega * i)
|
|
||||||
}
|
|
||||||
val power = (real * real + imag * imag).toFloat()
|
|
||||||
if (power > bestPower) {
|
|
||||||
bestPower = power
|
|
||||||
bestFreq = freq
|
|
||||||
}
|
|
||||||
freq += stepHz
|
|
||||||
}
|
|
||||||
return if (bestPower > 0.001f) bestFreq else null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sliding-window spectrogram for CW decoding.
|
|
||||||
* Maintains a time-frequency matrix updated with each audio frame.
|
|
||||||
*
|
|
||||||
* FFT size: 256, hop size: 64, sample rate: 4000 (or native)
|
|
||||||
* Frequency bins: 6..38 (187-1187 Hz, covers typical CW range)
|
|
||||||
* History: 40 columns (320 ms window)
|
|
||||||
* Time resolution: 64/4000 = 16 ms, Frequency resolution: 4000/256 = 15.625 Hz
|
|
||||||
*/
|
|
||||||
internal class CwSpectrogram(
|
|
||||||
private val fftSize: Int = 256,
|
|
||||||
private val hopSize: Int = 64,
|
|
||||||
private val sampleRate: Int = 4000,
|
|
||||||
private val minBin: Int = 6,
|
|
||||||
private val maxBin: Int = 38,
|
|
||||||
val historyCols: Int = 40
|
|
||||||
) {
|
|
||||||
private val fft = CwFFT(fftSize)
|
|
||||||
val numBins: Int get() = maxBin - minBin + 1
|
|
||||||
|
|
||||||
// Hanning window
|
|
||||||
private val hanning = FloatArray(fftSize) {
|
|
||||||
(0.5 - 0.5 * kotlin.math.cos(2.0 * kotlin.math.PI * it / (fftSize - 1))).toFloat()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spectrogram data: [timeCol][freqBin]
|
|
||||||
private val spectrogram = Array(historyCols) { FloatArray(numBins) }
|
|
||||||
private var currentCol = 0
|
|
||||||
private var samplesBuffered = 0
|
|
||||||
private val buffer = FloatArray(fftSize)
|
|
||||||
|
|
||||||
// Per-bin running energy for normalization
|
|
||||||
private val binEnergy = FloatArray(numBins) { 1f }
|
|
||||||
private val alpha = 0.95f
|
|
||||||
|
|
||||||
// Counter for new columns generated since last check
|
|
||||||
private var newColumnCount = 0
|
|
||||||
|
|
||||||
/** Add audio samples, compute FFTs for each complete hop. */
|
|
||||||
fun addSamples(samples: FloatArray) {
|
|
||||||
var offset = 0
|
|
||||||
while (offset < samples.size) {
|
|
||||||
val needed = fftSize - samplesBuffered
|
|
||||||
val copyLen = minOf(needed, samples.size - offset)
|
|
||||||
System.arraycopy(samples, offset, buffer, samplesBuffered, copyLen)
|
|
||||||
samplesBuffered += copyLen
|
|
||||||
offset += copyLen
|
|
||||||
|
|
||||||
if (samplesBuffered >= fftSize) {
|
|
||||||
processFrame()
|
|
||||||
newColumnCount++
|
|
||||||
// Shift buffer: keep last (fftSize - hopSize) samples
|
|
||||||
System.arraycopy(buffer, hopSize, buffer, 0, fftSize - hopSize)
|
|
||||||
samplesBuffered = fftSize - hopSize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get number of new columns generated since the last call to this method. */
|
|
||||||
fun getNewColumns(): Int {
|
|
||||||
val count = newColumnCount
|
|
||||||
newColumnCount = 0
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun processFrame() {
|
|
||||||
// Apply Hanning window
|
|
||||||
val windowed = FloatArray(fftSize) { buffer[it] * hanning[it] }
|
|
||||||
|
|
||||||
// Compute FFT magnitude spectrum
|
|
||||||
val mag = fft.magnitudeSpectrum(windowed)
|
|
||||||
|
|
||||||
// Update spectrogram column
|
|
||||||
val col = spectrogram[currentCol]
|
|
||||||
for (b in 0 until numBins) {
|
|
||||||
val binIdx = minBin + b
|
|
||||||
val rawMag = mag[binIdx]
|
|
||||||
// Running energy normalization
|
|
||||||
binEnergy[b] = alpha * binEnergy[b] + (1 - alpha) * rawMag
|
|
||||||
col[b] = if (binEnergy[b] > 1e-6f) rawMag / binEnergy[b] else 0f
|
|
||||||
}
|
|
||||||
|
|
||||||
currentCol = (currentCol + 1) % historyCols
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the current spectrogram as a 2D array in chronological order. */
|
|
||||||
fun getSpectrogram(): Array<FloatArray> {
|
|
||||||
val result = Array(historyCols) { i ->
|
|
||||||
val srcIdx = (currentCol + i) % historyCols
|
|
||||||
spectrogram[srcIdx].copyOf()
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the most recent column (current energy across all frequencies). */
|
|
||||||
fun getCurrentColumn(): FloatArray {
|
|
||||||
val prevCol = (currentCol - 1 + historyCols) % historyCols
|
|
||||||
return spectrogram[prevCol].copyOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get a column by index from the history (0 = oldest, historyCols-1 = newest). */
|
|
||||||
fun getColumn(index: Int): FloatArray {
|
|
||||||
val clamped = index.coerceIn(0, historyCols - 1)
|
|
||||||
val srcIdx = (currentCol - historyCols + clamped + historyCols) % historyCols
|
|
||||||
return spectrogram[srcIdx].copyOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Find the frequency bin with peak energy. Returns -1 if no significant signal. */
|
|
||||||
fun findPeakBin(): Int {
|
|
||||||
val col = getCurrentColumn()
|
|
||||||
var maxBin = -1
|
|
||||||
var maxVal = 0f
|
|
||||||
for (i in col.indices) {
|
|
||||||
if (col[i] > maxVal) {
|
|
||||||
maxVal = col[i]
|
|
||||||
maxBin = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return if (maxVal > 0.3f) maxBin else -1
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get energy at a specific bin over the last N columns in chronological order. */
|
|
||||||
fun getBinEnergy(bin: Int, numCols: Int): FloatArray {
|
|
||||||
val clamped = minOf(numCols, historyCols)
|
|
||||||
val result = FloatArray(clamped)
|
|
||||||
for (i in 0 until clamped) {
|
|
||||||
val colIdx = (currentCol - clamped + i + historyCols) % historyCols
|
|
||||||
result[i] = spectrogram[colIdx][bin]
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the bin index for a frequency in Hz. */
|
|
||||||
fun freqToBin(freqHz: Float): Int {
|
|
||||||
val bin = (freqHz * fftSize / sampleRate).toInt()
|
|
||||||
return (bin - minBin).coerceIn(0, numBins - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the center frequency for a bin. */
|
|
||||||
fun binToFreq(bin: Int): Float {
|
|
||||||
return (minBin + bin).toFloat() * sampleRate / fftSize
|
|
||||||
}
|
|
||||||
|
|
||||||
fun reset() {
|
|
||||||
for (col in spectrogram) col.fill(0f)
|
|
||||||
currentCol = 0
|
|
||||||
samplesBuffered = 0
|
|
||||||
buffer.fill(0f)
|
|
||||||
binEnergy.fill(1f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* 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.StateFlow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A CW (Morse code) decoder fed with microphone PCM.
|
||||||
|
*
|
||||||
|
* Implementations live outside `core:domain` when they need platform APIs;
|
||||||
|
* this contract stays pure Kotlin so the UI can depend on it directly.
|
||||||
|
*/
|
||||||
|
interface ICwDecoder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decoded text for the *current* window.
|
||||||
|
*
|
||||||
|
* Note this is **replace** semantics, not append: a whole-segment model
|
||||||
|
* revises earlier characters as more audio arrives, so consumers must show
|
||||||
|
* the current value rather than accumulating emissions.
|
||||||
|
*/
|
||||||
|
val decodedText: StateFlow<String>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permanent transcript of everything that has scrolled out of the live
|
||||||
|
* window. Unlike [decodedText] this only ever grows (until [reset]); it is
|
||||||
|
* what the user reads back after a signal has passed.
|
||||||
|
*/
|
||||||
|
val historyText: StateFlow<String>
|
||||||
|
|
||||||
|
/** Detected tone frequency in Hz, or null before a tone is found. */
|
||||||
|
val estimatedPitch: StateFlow<Float?>
|
||||||
|
|
||||||
|
/** Relative signal strength in 0..1 for level meters. */
|
||||||
|
val signalStrength: StateFlow<Float>
|
||||||
|
|
||||||
|
/** Most recent inference duration in milliseconds, for diagnostics. */
|
||||||
|
val lastInferenceMs: StateFlow<Int>
|
||||||
|
|
||||||
|
/** Non-null when the decoder cannot run, for example the model failed to load. */
|
||||||
|
val errorMessage: StateFlow<String?>
|
||||||
|
|
||||||
|
/** Feed captured mono PCM in -1..1. Safe to call from a capture thread. */
|
||||||
|
suspend fun processBuffer(samples: FloatArray, sampleRate: Int)
|
||||||
|
|
||||||
|
/** Clear decoded text and buffered audio. */
|
||||||
|
fun reset()
|
||||||
|
|
||||||
|
/** Release native resources. Must be called when the decoder goes away. */
|
||||||
|
fun close()
|
||||||
|
}
|
||||||
+1
@@ -42,6 +42,7 @@ interface IMainContainer {
|
|||||||
fun provideTxRadioController(): IRadioController
|
fun provideTxRadioController(): IRadioController
|
||||||
fun provideRxRadioController(): IRadioController
|
fun provideRxRadioController(): IRadioController
|
||||||
fun provideAudioCapture(): IAudioCapture
|
fun provideAudioCapture(): IAudioCapture
|
||||||
|
fun provideCwDecoder(): com.rtbishop.look4sat.core.domain.cw.ICwDecoder
|
||||||
fun provideSaveImage(): ISaveImage
|
fun provideSaveImage(): ISaveImage
|
||||||
// WaveLog logging (4.5.2)
|
// WaveLog logging (4.5.2)
|
||||||
val wavelogQueue: com.rtbishop.look4sat.core.domain.wavelog.WavelogQueue
|
val wavelogQueue: com.rtbishop.look4sat.core.domain.wavelog.WavelogQueue
|
||||||
|
|||||||
@@ -25,4 +25,10 @@ interface IRemoteSource {
|
|||||||
|
|
||||||
/** Fetch AMSAT status page HTML (with UA; null = failure) */
|
/** Fetch AMSAT status page HTML (with UA; null = failure) */
|
||||||
suspend fun getStatusHtml(): String?
|
suspend fun getStatusHtml(): String?
|
||||||
|
|
||||||
|
/** Fetch AMSAT API catalog (JSON string; null on failure) */
|
||||||
|
suspend fun getAmSatCatalog(): String?
|
||||||
|
|
||||||
|
/** Fetch AMSAT API reports for the past N hours (JSON string; null on failure) */
|
||||||
|
suspend fun getAmSatReports(hours: Int, limit: Int): String?
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* 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.Test
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Greedy CTC collapse, matching the reference implementation's
|
||||||
|
* `greedy_ctc_decode`: drop blanks, then drop runs of the same label.
|
||||||
|
*
|
||||||
|
* Alphabet from `model.onnx.json` — 41 symbols plus blank at index 41.
|
||||||
|
*/
|
||||||
|
class CwCtcDecoderTest {
|
||||||
|
|
||||||
|
private val chars = listOf(
|
||||||
|
",", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "?",
|
||||||
|
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N",
|
||||||
|
"O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", " "
|
||||||
|
)
|
||||||
|
private val blank = 41
|
||||||
|
|
||||||
|
/** Build a `[1, T, 42]` log-prob tensor whose argmax follows [path]. */
|
||||||
|
private fun logits(path: IntArray): Array<Array<FloatArray>> {
|
||||||
|
val frames = Array(path.size) { t ->
|
||||||
|
FloatArray(42) { -10f }.also { it[path[t]] = 0f }
|
||||||
|
}
|
||||||
|
return arrayOf(frames)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun alphabetSizeMatchesModelMetadata() {
|
||||||
|
assertEquals("41 symbols + blank = 42 classes", 41, chars.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun greedy_dropsRunsOfTheSameLabel() {
|
||||||
|
assertEquals("A", CwCtcDecoder.greedy(logits(intArrayOf(14, 14, 14)), chars, blank))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun greedy_keepsRepeatsSeparatedByBlank() {
|
||||||
|
// A A <blank> A collapses to "AA": the blank breaks the run.
|
||||||
|
assertEquals("AA", CwCtcDecoder.greedy(logits(intArrayOf(14, 14, blank, 14)), chars, blank))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun greedy_allBlanksYieldEmptyString() {
|
||||||
|
assertEquals("", CwCtcDecoder.greedy(logits(intArrayOf(blank, blank, blank)), chars, blank))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun greedy_emptyInputYieldsEmptyString() {
|
||||||
|
assertEquals("", CwCtcDecoder.greedy(logits(intArrayOf()), chars, blank))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun greedy_decodesCallsignWithSpaceAndDigits() {
|
||||||
|
// "CQ BG7" — C=16 Q=30 space=40 B=15 G=20 7=10
|
||||||
|
val path = intArrayOf(
|
||||||
|
blank, 16, 16, blank, 30, blank, 40,
|
||||||
|
15, blank, 20, blank, 10, blank
|
||||||
|
)
|
||||||
|
assertEquals("CQ BG7", CwCtcDecoder.greedy(logits(path), chars, blank))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun greedy_picksHighestScoringClassPerFrame() {
|
||||||
|
// Frame favours S (32) over T (33); only S must survive.
|
||||||
|
val frame = FloatArray(42) { -10f }
|
||||||
|
frame[33] = -1f
|
||||||
|
frame[32] = -0.1f
|
||||||
|
assertEquals("S", CwCtcDecoder.greedy(arrayOf(arrayOf(frame)), chars, blank))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,301 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.*
|
|
||||||
import org.junit.Test
|
|
||||||
import kotlin.math.PI
|
|
||||||
import kotlin.math.sin
|
|
||||||
|
|
||||||
class CwDecoderTest {
|
|
||||||
|
|
||||||
// --- Morse table ---
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun morseToChar_basicLetters() {
|
|
||||||
assertEquals('A', CwBayesianDecoder.morseToChar("01"))
|
|
||||||
assertEquals('S', CwBayesianDecoder.morseToChar("000"))
|
|
||||||
assertEquals('O', CwBayesianDecoder.morseToChar("111"))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun morseToChar_numbers() {
|
|
||||||
assertEquals('1', CwBayesianDecoder.morseToChar("01111"))
|
|
||||||
assertEquals('0', CwBayesianDecoder.morseToChar("11111"))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun morseToChar_unknown_returnsNull() {
|
|
||||||
assertNull(CwBayesianDecoder.morseToChar("......."))
|
|
||||||
assertNull(CwBayesianDecoder.morseToChar(""))
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- FFT ---
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun fft_magnitudeSpectrum_detectsTone() {
|
|
||||||
val fft = CwFFT(256)
|
|
||||||
val sampleRate = 8000f
|
|
||||||
val freq = 700f
|
|
||||||
val buffer = FloatArray(256) { (sin(2.0 * PI * freq * it / sampleRate)).toFloat() }
|
|
||||||
val mag = fft.magnitudeSpectrum(buffer)
|
|
||||||
// Peak should be at bin around 700 * 256 / 8000 ≈ 22.4
|
|
||||||
var maxBin = 0
|
|
||||||
var maxVal = 0f
|
|
||||||
for (i in mag.indices) {
|
|
||||||
if (mag[i] > maxVal) { maxVal = mag[i]; maxBin = i }
|
|
||||||
}
|
|
||||||
assertTrue("Peak bin $maxBin should be near 22", maxBin in 18..26)
|
|
||||||
assertTrue("Peak value $maxVal should be positive", maxVal > 0.01f)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun fft_magnitudeSpectrum_silence_isFlat() {
|
|
||||||
val fft = CwFFT(256)
|
|
||||||
val buffer = FloatArray(256) { 0f }
|
|
||||||
val mag = fft.magnitudeSpectrum(buffer)
|
|
||||||
for (v in mag) assertEquals("Silence spectrum should be 0, got $v", 0f, v, 1e-6f)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun fft_rejectsWrongSize() {
|
|
||||||
assertThrows(IllegalArgumentException::class.java) { CwFFT(100) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Spectrogram ---
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun spectrogram_addSamples_updatesEnergy() {
|
|
||||||
val spec = CwSpectrogram(sampleRate = 8000)
|
|
||||||
val freq = 700f
|
|
||||||
// Feed multiple frames to stabilize energy normalization
|
|
||||||
for (i in 0..5) {
|
|
||||||
val buffer = FloatArray(256) { (sin(2.0 * PI * freq * it / 8000.0)).toFloat() }
|
|
||||||
spec.addSamples(buffer)
|
|
||||||
}
|
|
||||||
val col = spec.getCurrentColumn()
|
|
||||||
val peakBin = spec.findPeakBin()
|
|
||||||
assertTrue("Peak bin $peakBin should be >= 0", peakBin >= 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun spectrogram_findPeakBin_returnsValidBin() {
|
|
||||||
val spec = CwSpectrogram(sampleRate = 8000)
|
|
||||||
// Add multiple frames of 700 Hz tone
|
|
||||||
for (i in 0..5) {
|
|
||||||
val buffer = FloatArray(256) { (sin(2.0 * PI * 700.0 * it / 8000.0)).toFloat() }
|
|
||||||
spec.addSamples(buffer)
|
|
||||||
}
|
|
||||||
val peakBin = spec.findPeakBin()
|
|
||||||
assertTrue("Peak bin should be >= 0, got $peakBin", peakBin >= 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun spectrogram_freqToBin_roundtrip() {
|
|
||||||
val spec = CwSpectrogram(sampleRate = 8000)
|
|
||||||
val freq = 700f
|
|
||||||
val bin = spec.freqToBin(freq)
|
|
||||||
val backFreq = spec.binToFreq(bin)
|
|
||||||
assertTrue("Freq $freq → bin $bin → freq $backFreq", backFreq > 600f && backFreq < 800f)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun spectrogram_getBinEnergy_returnsCorrectLength() {
|
|
||||||
val spec = CwSpectrogram(sampleRate = 8000)
|
|
||||||
val energy = spec.getBinEnergy(0, 10)
|
|
||||||
assertEquals(10, energy.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun spectrogram_reset() {
|
|
||||||
val spec = CwSpectrogram(sampleRate = 8000)
|
|
||||||
spec.addSamples(FloatArray(256) { 1f })
|
|
||||||
spec.reset()
|
|
||||||
assertEquals(-1, spec.findPeakBin())
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Bayesian decoder ---
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun bayesian_processTone_dit() {
|
|
||||||
val decoder = CwBayesianDecoder()
|
|
||||||
// At 20 WPM, dot = 60 ms
|
|
||||||
val result = decoder.processTone(60f)
|
|
||||||
assertEquals('0', result.symbol)
|
|
||||||
assertTrue("Dit probability should be positive", result.probability > 0.1f)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun bayesian_processTone_dash() {
|
|
||||||
val decoder = CwBayesianDecoder()
|
|
||||||
// Dash = 3 * dot = 180 ms
|
|
||||||
val result = decoder.processTone(180f)
|
|
||||||
assertEquals('1', result.symbol)
|
|
||||||
assertTrue("Dash probability should be positive", result.probability > 0.1f)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun bayesian_processTone_unknown_returnsNull() {
|
|
||||||
val decoder = CwBayesianDecoder()
|
|
||||||
// Very long tone — low probability for both dit and dash
|
|
||||||
val result = decoder.processTone(5000f)
|
|
||||||
assertNull(result.symbol)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun bayesian_processGap_interChar_returnsChar() {
|
|
||||||
val decoder = CwBayesianDecoder()
|
|
||||||
decoder.processTone(60f) // dit
|
|
||||||
decoder.processTone(60f) // dit
|
|
||||||
decoder.processTone(60f) // dit
|
|
||||||
// 3 dots = "000" = 'S'
|
|
||||||
val char = decoder.processGap(180f) // 3 * dot = inter-char gap
|
|
||||||
assertEquals('S', char)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun bayesian_processGap_wordGap_addsSpace() {
|
|
||||||
val decoder = CwBayesianDecoder()
|
|
||||||
decoder.processTone(60f) // dit = 'E'
|
|
||||||
decoder.processGap(180f) // inter-char gap
|
|
||||||
// Now word gap
|
|
||||||
val space = decoder.processGap(420f) // 7 * dot
|
|
||||||
assertEquals(' ', space)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun bayesian_decodedText_accumulates() {
|
|
||||||
val decoder = CwBayesianDecoder()
|
|
||||||
decoder.processTone(60f) // dit = 'E'
|
|
||||||
decoder.processGap(180f) // inter-char
|
|
||||||
assertTrue(decoder.decodedText.isNotEmpty())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun bayesian_reset() {
|
|
||||||
val decoder = CwBayesianDecoder()
|
|
||||||
decoder.processTone(60f)
|
|
||||||
decoder.reset()
|
|
||||||
assertEquals("", decoder.decodedText)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun bayesian_getSpeed() {
|
|
||||||
val decoder = CwBayesianDecoder()
|
|
||||||
// Send 3 dits at 20 WPM (60 ms each)
|
|
||||||
decoder.processTone(60f)
|
|
||||||
decoder.processTone(60f)
|
|
||||||
decoder.processTone(60f)
|
|
||||||
val speed = decoder.getSpeed()
|
|
||||||
assertTrue("Speed should be ~20 WPM, got $speed", speed > 15f && speed < 30f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Channel tracker ---
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun channelTracker_initialState() {
|
|
||||||
val spec = CwSpectrogram(sampleRate = 8000)
|
|
||||||
val tracker = CwChannelTracker(spec)
|
|
||||||
val channels = tracker.update()
|
|
||||||
assertTrue("No channels should be active initially", channels.isEmpty())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun channelTracker_detectsTone() {
|
|
||||||
val spec = CwSpectrogram(sampleRate = 8000)
|
|
||||||
// Feed a tone
|
|
||||||
for (i in 0..5) {
|
|
||||||
spec.addSamples(FloatArray(256) { (sin(2.0 * PI * 700.0 * it / 8000.0)).toFloat() })
|
|
||||||
}
|
|
||||||
val tracker = CwChannelTracker(spec)
|
|
||||||
val channels = tracker.update()
|
|
||||||
assertTrue("Should detect at least 1 channel", channels.isNotEmpty())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun channelTracker_bestChannel() {
|
|
||||||
val spec = CwSpectrogram(sampleRate = 8000)
|
|
||||||
for (i in 0..5) {
|
|
||||||
spec.addSamples(FloatArray(256) { (sin(2.0 * PI * 700.0 * it / 8000.0)).toFloat() })
|
|
||||||
}
|
|
||||||
val tracker = CwChannelTracker(spec)
|
|
||||||
tracker.update()
|
|
||||||
val best = tracker.getBestChannel()
|
|
||||||
assertNotNull("Best channel should exist", best)
|
|
||||||
if (best != null) assertTrue(best.frequency in 600f..800f)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun channelTracker_reset() {
|
|
||||||
val spec = CwSpectrogram(sampleRate = 8000)
|
|
||||||
for (i in 0..5) {
|
|
||||||
spec.addSamples(FloatArray(256) { (sin(2.0 * PI * 700.0 * it / 8000.0)).toFloat() })
|
|
||||||
}
|
|
||||||
val tracker = CwChannelTracker(spec)
|
|
||||||
tracker.update()
|
|
||||||
tracker.reset()
|
|
||||||
assertNull(tracker.getBestChannel())
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Full decoder ---
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun decoder_initialState() {
|
|
||||||
val decoder = CwDecoder()
|
|
||||||
assertEquals("", decoder.decodedTextFlow.value)
|
|
||||||
assertEquals(0f, decoder.signalStrength.value, 0.001f)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun decoder_processSilence_doesNotCrash() {
|
|
||||||
val decoder = CwDecoder()
|
|
||||||
decoder.processBuffer(FloatArray(256) { 0f })
|
|
||||||
assertEquals("", decoder.decodedTextFlow.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun decoder_processNoise_doesNotCrash() {
|
|
||||||
val decoder = CwDecoder()
|
|
||||||
decoder.processBuffer(FloatArray(256) { (Math.random() * 2 - 1).toFloat() * 0.1f })
|
|
||||||
assertNotNull(decoder.decodedTextFlow.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun decoder_processTone_doesNotCrash() {
|
|
||||||
val decoder = CwDecoder()
|
|
||||||
for (i in 0..20) {
|
|
||||||
decoder.processBuffer(FloatArray(256) { (sin(2.0 * PI * 700.0 * it / 8000.0)).toFloat() })
|
|
||||||
}
|
|
||||||
assertNotNull(decoder.decodedTextFlow.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun decoder_reset() {
|
|
||||||
val decoder = CwDecoder()
|
|
||||||
decoder.processBuffer(FloatArray(256) { 1f })
|
|
||||||
decoder.resetDecoder()
|
|
||||||
assertEquals("", decoder.decodedTextFlow.value)
|
|
||||||
assertEquals(0f, decoder.signalStrength.value, 0.001f)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun decoder_withFixedPitch() {
|
|
||||||
val decoder = CwDecoder(sampleRate = 8000, cwToneFreq = 700f)
|
|
||||||
assertEquals(700f, decoder.estimatedPitch.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* 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.assertArrayEquals
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The rolling audio buffer feeding DeepCW.
|
||||||
|
*
|
||||||
|
* DeepCW is a whole-segment CTC model, not a sample-by-sample decoder: it
|
||||||
|
* rewrites earlier output whenever more context arrives, so incremental
|
||||||
|
* stitching is impossible. Instead we keep a bounded window and re-decode all
|
||||||
|
* of it periodically, replacing the displayed text.
|
||||||
|
*/
|
||||||
|
class CwDeepBufferTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun capacityIsCappedAtMaxSeconds() {
|
||||||
|
val buffer = CwDeepBuffer(sampleRate = 3200, maxSeconds = 20.0)
|
||||||
|
repeat(30) { buffer.append(FloatArray(3200)) }
|
||||||
|
assertEquals(3200 * 20, buffer.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun oldestSamplesAreDiscardedFirst() {
|
||||||
|
val buffer = CwDeepBuffer(sampleRate = 4, maxSeconds = 1.0)
|
||||||
|
buffer.append(floatArrayOf(1f, 2f, 3f))
|
||||||
|
buffer.append(floatArrayOf(4f, 5f))
|
||||||
|
assertArrayEquals(floatArrayOf(2f, 3f, 4f, 5f), buffer.snapshot(), 0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun snapshotIsChronologicalAfterWrapAround() {
|
||||||
|
val buffer = CwDeepBuffer(sampleRate = 4, maxSeconds = 1.0)
|
||||||
|
buffer.append(floatArrayOf(1f, 2f, 3f, 4f, 5f, 6f))
|
||||||
|
assertArrayEquals(floatArrayOf(3f, 4f, 5f, 6f), buffer.snapshot(), 0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun appendLargerThanCapacityKeepsOnlyTheTail() {
|
||||||
|
val buffer = CwDeepBuffer(sampleRate = 4, maxSeconds = 1.0)
|
||||||
|
buffer.append(floatArrayOf(1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f))
|
||||||
|
assertEquals(4, buffer.size)
|
||||||
|
assertArrayEquals(floatArrayOf(6f, 7f, 8f, 9f), buffer.snapshot(), 0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun redecodeIsSignalledOncePerInterval() {
|
||||||
|
// 1.5 s at 3200 Hz is 4800 samples; 1600 samples is 0.5 s.
|
||||||
|
val buffer = CwDeepBuffer(3200, 20.0, redecodeIntervalMs = 1500)
|
||||||
|
assertFalse("1.0s elapsed: interval not reached", buffer.append(FloatArray(3200)))
|
||||||
|
assertTrue("1.5s elapsed: first trigger", buffer.append(FloatArray(1600)))
|
||||||
|
assertFalse("2.0s: only 0.5s since trigger", buffer.append(FloatArray(1600)))
|
||||||
|
assertFalse("2.5s: only 1.0s since trigger", buffer.append(FloatArray(1600)))
|
||||||
|
assertTrue("3.0s: 1.5s since trigger, fires again", buffer.append(FloatArray(1600)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun redecodeIntervalDoesNotDriftOverManyChunks() {
|
||||||
|
// 100 ms chunks, as AudioCapture emits them: exactly 15 chunks per
|
||||||
|
// 1.5 s interval, so 150 chunks must fire exactly 10 times.
|
||||||
|
val buffer = CwDeepBuffer(3200, 20.0, redecodeIntervalMs = 1500)
|
||||||
|
var fired = 0
|
||||||
|
repeat(150) { if (buffer.append(FloatArray(320))) fired++ }
|
||||||
|
assertEquals(10, fired)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun snapshotDoesNotAliasInternalStorage() {
|
||||||
|
val buffer = CwDeepBuffer(sampleRate = 4, maxSeconds = 1.0)
|
||||||
|
buffer.append(floatArrayOf(1f, 2f, 3f, 4f))
|
||||||
|
buffer.snapshot()[0] = 99f
|
||||||
|
assertEquals("caller must not be able to mutate the buffer", 1f, buffer.snapshot()[0], 0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun resetClearsSamplesAndIntervalCounter() {
|
||||||
|
val buffer = CwDeepBuffer(3200, 20.0, redecodeIntervalMs = 1500)
|
||||||
|
buffer.append(FloatArray(3200))
|
||||||
|
buffer.reset()
|
||||||
|
assertEquals(0, buffer.size)
|
||||||
|
assertFalse("counter restarted, 1.0s must not trigger", buffer.append(FloatArray(3200)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun hasEnoughAudioTracksTheModelMinimum() {
|
||||||
|
// compute() needs at least FFT_LENGTH samples to produce one frame.
|
||||||
|
val buffer = CwDeepBuffer(3200, 20.0)
|
||||||
|
buffer.append(FloatArray(100))
|
||||||
|
assertFalse(buffer.hasEnoughAudio)
|
||||||
|
buffer.append(FloatArray(200))
|
||||||
|
assertTrue(buffer.hasEnoughAudio)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun defaultsMatchTheMeasuredOptimum() {
|
||||||
|
// 20s / 1.5s were chosen from measurements: 20s is the smallest window
|
||||||
|
// reaching 0.0% CER, and keeps inference well inside real time.
|
||||||
|
val buffer = CwDeepBuffer()
|
||||||
|
assertEquals(CwDeepSpectrogram.SAMPLE_RATE * 20, buffer.capacity)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun overflowCollectsEvictedSamplesInOrder() {
|
||||||
|
val buffer = CwDeepBuffer(sampleRate = 4, maxSeconds = 1.0) // capacity 4
|
||||||
|
buffer.append(floatArrayOf(1f, 2f, 3f, 4f))
|
||||||
|
assertEquals("nothing evicted before the window is full", 0, buffer.overflowCount)
|
||||||
|
buffer.append(floatArrayOf(5f, 6f)) // overwrites 1, 2
|
||||||
|
assertArrayEquals("evicted samples, oldest first", floatArrayOf(1f, 2f), buffer.drainOverflow(), 0f)
|
||||||
|
assertArrayEquals("live window still correct", floatArrayOf(3f, 4f, 5f, 6f), buffer.snapshot(), 0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun drainOverflowClearsItself() {
|
||||||
|
val buffer = CwDeepBuffer(sampleRate = 4, maxSeconds = 1.0)
|
||||||
|
buffer.append(floatArrayOf(1f, 2f, 3f, 4f))
|
||||||
|
buffer.append(floatArrayOf(5f))
|
||||||
|
assertEquals(1, buffer.overflowCount)
|
||||||
|
buffer.drainOverflow()
|
||||||
|
assertEquals(0, buffer.overflowCount)
|
||||||
|
assertArrayEquals(FloatArray(0), buffer.drainOverflow(), 0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun resetClearsOverflow() {
|
||||||
|
val buffer = CwDeepBuffer(sampleRate = 4, maxSeconds = 1.0)
|
||||||
|
buffer.append(floatArrayOf(1f, 2f, 3f, 4f))
|
||||||
|
buffer.append(floatArrayOf(5f))
|
||||||
|
assertEquals(1, buffer.overflowCount)
|
||||||
|
buffer.reset()
|
||||||
|
assertEquals(0, buffer.overflowCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
+130
@@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* 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.abs
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pins the Kotlin front-end to the upstream Python reference implementation.
|
||||||
|
*
|
||||||
|
* `golden_spec.txt` was produced by deepcw-engine's own preprocessing code
|
||||||
|
* (numpy reflect padding, `np.hanning(N+1)[:-1]`, `np.fft.rfft`, `log1p`) over
|
||||||
|
* the audio [generateTestAudio] builds. Both sides synthesise the audio from
|
||||||
|
* the same deterministic formula, so only the spectrogram needs pinning.
|
||||||
|
*
|
||||||
|
* A mismatch here means the model would receive subtly wrong input and emit
|
||||||
|
* plausible-looking garbage, which is very hard to diagnose downstream — so
|
||||||
|
* this test guards the whole pipeline.
|
||||||
|
*
|
||||||
|
* Regenerate with `scripts/deepcw_gen_golden.py` from the skill library if the
|
||||||
|
* model metadata ever changes.
|
||||||
|
*/
|
||||||
|
class CwDeepGoldenVectorTest {
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val SRC_RATE = 8000
|
||||||
|
const val TONE_HZ = 700.0
|
||||||
|
const val AMPLITUDE = 0.6
|
||||||
|
const val DOT_SAMPLES = 480 // 20 WPM at 8000 Hz: 1.2/20*8000
|
||||||
|
const val PATTERN = "-.-." // the letter C
|
||||||
|
const val TOLERANCE = 1e-4f
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Square-keyed 700 Hz tone: 4 dots of silence, "C", 4 dots of silence. */
|
||||||
|
private fun generateTestAudio(): FloatArray {
|
||||||
|
val keying = ArrayList<Int>()
|
||||||
|
repeat(4 * DOT_SAMPLES) { keying.add(0) }
|
||||||
|
for ((i, element) in PATTERN.withIndex()) {
|
||||||
|
val length = if (element == '-') 3 * DOT_SAMPLES else DOT_SAMPLES
|
||||||
|
repeat(length) { keying.add(1) }
|
||||||
|
if (i < PATTERN.length - 1) repeat(DOT_SAMPLES) { keying.add(0) }
|
||||||
|
}
|
||||||
|
repeat(4 * DOT_SAMPLES) { keying.add(0) }
|
||||||
|
|
||||||
|
return FloatArray(keying.size) { i ->
|
||||||
|
if (keying[i] == 1) {
|
||||||
|
(AMPLITUDE * sin(2.0 * PI * TONE_HZ * i / SRC_RATE)).toFloat()
|
||||||
|
} else {
|
||||||
|
0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readGoldenSpectrogram(): Array<FloatArray> {
|
||||||
|
val stream = javaClass.classLoader?.getResourceAsStream("cw/golden_spec.txt")
|
||||||
|
?: throw IllegalStateException("cw/golden_spec.txt missing from test resources")
|
||||||
|
stream.bufferedReader().use { reader ->
|
||||||
|
val (frames, bins) = reader.readLine().trim().split(" ").map(String::toInt)
|
||||||
|
return Array(frames) {
|
||||||
|
val row = reader.readLine().trim().split(" ")
|
||||||
|
require(row.size == bins) { "expected $bins values, got ${row.size}" }
|
||||||
|
FloatArray(bins) { i -> row[i].toFloat() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun spectrogramMatchesPythonReferenceFrameByFrame() {
|
||||||
|
val expected = readGoldenSpectrogram()
|
||||||
|
val audio = CwDeepSpectrogram.resampleLinear(
|
||||||
|
generateTestAudio(), SRC_RATE, CwDeepSpectrogram.SAMPLE_RATE
|
||||||
|
)
|
||||||
|
val actual = CwDeepSpectrogram.compute(audio)
|
||||||
|
|
||||||
|
assertEquals("frame count", expected.size, actual.size)
|
||||||
|
assertEquals("bin count", expected[0].size, actual[0].size)
|
||||||
|
|
||||||
|
var worstDelta = 0f
|
||||||
|
var worstAt = ""
|
||||||
|
for (t in expected.indices) {
|
||||||
|
for (f in expected[t].indices) {
|
||||||
|
val delta = abs(expected[t][f] - actual[t][f])
|
||||||
|
if (delta > worstDelta) {
|
||||||
|
worstDelta = delta
|
||||||
|
worstAt = "frame $t bin $f: expected ${expected[t][f]}, got ${actual[t][f]}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(
|
||||||
|
"front-end diverges from the Python reference — worst delta $worstDelta at $worstAt",
|
||||||
|
worstDelta <= TOLERANCE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun resampledLengthMatchesReference() {
|
||||||
|
val audio = generateTestAudio()
|
||||||
|
assertEquals("source audio length", 9120, audio.size)
|
||||||
|
val resampled = CwDeepSpectrogram.resampleLinear(
|
||||||
|
audio, SRC_RATE, CwDeepSpectrogram.SAMPLE_RATE
|
||||||
|
)
|
||||||
|
assertEquals("resampled length", 3648, resampled.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun goldenVectorHasExpectedShape() {
|
||||||
|
val golden = readGoldenSpectrogram()
|
||||||
|
assertEquals("frames", 77, golden.size)
|
||||||
|
assertEquals("bins", CwDeepSpectrogram.FREQUENCY_BINS, golden[0].size)
|
||||||
|
}
|
||||||
|
}
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* 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.abs
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the DeepCW front-end against the upstream Python reference
|
||||||
|
* implementation (deepcw-engine examples/python/decode_morse.py).
|
||||||
|
*
|
||||||
|
* Model metadata: sampleRate 3200, fftLength 256, hopLength 48,
|
||||||
|
* 400-1200 Hz -> 65 bins, log1p normalization.
|
||||||
|
*/
|
||||||
|
class CwDeepSpectrogramTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun frequencyBinRange_matchesModelMetadata() {
|
||||||
|
// binHz = 3200/256 = 12.5; start = ceil(400/12.5) = 32; stop = floor(1200/12.5)+1 = 97
|
||||||
|
val (start, stop) = CwDeepSpectrogram.frequencyBinRange(3200, 256, 400.0, 1200.0)
|
||||||
|
assertEquals(32, start)
|
||||||
|
assertEquals(97, stop)
|
||||||
|
assertEquals("metadata declares 65 frequency bins", 65, stop - start)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun compute_producesTimeBy65Matrix() {
|
||||||
|
// 1 second at 3200 Hz. Reflect padding adds fft/2 on both sides,
|
||||||
|
// so frames = 1 + (3200 + 256 - 256)/48 = 1 + 66 = 67
|
||||||
|
val spec = CwDeepSpectrogram.compute(FloatArray(3200))
|
||||||
|
assertEquals(67, spec.size)
|
||||||
|
assertEquals(65, spec[0].size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun compute_toneLandsInExpectedBin() {
|
||||||
|
// 700 Hz -> absolute bin 700/12.5 = 56 -> relative index 56 - 32 = 24
|
||||||
|
val audio = FloatArray(3200) { (0.6 * sin(2.0 * PI * 700.0 * it / 3200.0)).toFloat() }
|
||||||
|
val spec = CwDeepSpectrogram.compute(audio)
|
||||||
|
val middle = spec[spec.size / 2]
|
||||||
|
val peak = middle.indices.maxByOrNull { middle[it] } ?: -1
|
||||||
|
assertTrue("peak at index $peak, expected near 24", abs(peak - 24) <= 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun compute_appliesLog1pSoValuesAreNonNegative() {
|
||||||
|
val audio = FloatArray(3200) { (0.6 * sin(2.0 * PI * 700.0 * it / 3200.0)).toFloat() }
|
||||||
|
val spec = CwDeepSpectrogram.compute(audio)
|
||||||
|
for (frame in spec) {
|
||||||
|
for (v in frame) {
|
||||||
|
assertTrue("log1p of a magnitude must be >= 0, got $v", v >= 0f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun resampleLinear_convertsRateAndLength() {
|
||||||
|
assertEquals(3200, CwDeepSpectrogram.resampleLinear(FloatArray(8000), 8000, 3200).size)
|
||||||
|
assertEquals(3200, CwDeepSpectrogram.resampleLinear(FloatArray(44100), 44100, 3200).size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun resampleLinear_sameRateIsIdentity() {
|
||||||
|
val input = floatArrayOf(0.1f, 0.2f, 0.3f)
|
||||||
|
val out = CwDeepSpectrogram.resampleLinear(input, 3200, 3200)
|
||||||
|
assertEquals(3, out.size)
|
||||||
|
assertEquals(0.2f, out[1], 1e-6f)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun resampleLinear_preservesToneFrequency() {
|
||||||
|
// A 700 Hz tone sampled at 8000 Hz must still peak at bin 24 after
|
||||||
|
// resampling to 3200 Hz — this is the path real microphone audio takes.
|
||||||
|
val at8k = FloatArray(8000) { (0.6 * sin(2.0 * PI * 700.0 * it / 8000.0)).toFloat() }
|
||||||
|
val at3200 = CwDeepSpectrogram.resampleLinear(at8k, 8000, 3200)
|
||||||
|
val spec = CwDeepSpectrogram.compute(at3200)
|
||||||
|
val middle = spec[spec.size / 2]
|
||||||
|
val peak = middle.indices.maxByOrNull { middle[it] } ?: -1
|
||||||
|
assertTrue("resampled tone peak at $peak, expected near 24", abs(peak - 24) <= 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun compute_rejectsAudioShorterThanFftLength() {
|
||||||
|
try {
|
||||||
|
CwDeepSpectrogram.compute(FloatArray(100))
|
||||||
|
throw AssertionError("expected an exception for audio shorter than fftLength")
|
||||||
|
} catch (expected: IllegalArgumentException) {
|
||||||
|
// desired path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
77 65
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0.07889045 0.08153424 0.08419035 0.08701893 0.09030894 0.09436007 0.09932096 0.1050817 0.1113084 0.1176305 0.1239188 0.1305645 0.1386389 0.1497532 0.1654913 0.1866336 0.2127252 0.2422527 0.2731474 0.3032433 0.3305584 0.3534314 0.3705729 0.3810754 0.3844073 0.3804049 0.3692688 0.3515675 0.3282437 0.3006178 0.2703726 0.239492 0.2101091 0.1842086 0.1631677 0.1472881 0.1356851 0.126759 0.1189501 0.1112676 0.1034146 0.09563871 0.0884626 0.08238604 0.07764256 0.07410739 0.07139879 0.06908784 0.06687377 0.06464756 0.06245377 0.06040035 0.05856907 0.05696608 0.05552753 0.05416456 0.05281367 0.05146397 0.05015182 0.0489313 0.04783952 0.04687586 0.04600478 0.04517753 0.04435825
|
||||||
|
0.3260067 0.3363644 0.3473228 0.3584008 0.3717064 0.3864512 0.4010351 0.4184499 0.4389309 0.4590358 0.4825051 0.5122178 0.5418502 0.5749596 0.6208939 0.6692876 0.7196215 0.7983373 0.8930331 0.9810481 1.1455 1.435858 1.723972 1.914222 1.97871 1.912684 1.720891 1.431183 1.139257 0.9739592 0.885756 0.7909085 0.7123318 0.6630843 0.6163642 0.5723305 0.5442435 0.5254506 0.5046235 0.4722382 0.4203468 0.3605282 0.3242991 0.3175806 0.3153524 0.3077487 0.2989038 0.2901882 0.2814592 0.2735062 0.2657845 0.2581551 0.2513816 0.2449855 0.2385921 0.2328572 0.2275815 0.2222787 0.2174161 0.213032 0.2086437 0.2045162 0.2008394 0.1972071 0.1937068
|
||||||
|
0.4438076 0.4559436 0.4704448 0.4847386 0.5020207 0.5190093 0.5398599 0.5602847 0.5858377 0.6107538 0.6427084 0.6736734 0.7147121 0.7541329 0.8087943 0.8606219 0.9373714 1.008648 1.125829 1.230708 1.440158 1.613244 2.198886 2.788961 2.987679 2.787503 2.195585 1.607394 1.433426 1.222683 1.117479 0.9998917 0.9291879 0.853102 0.8035754 0.7514675 0.7191642 0.6857824 0.6938878 0.7097453 0.6016893 0.4058476 0.4104768 0.4370055 0.4281905 0.4192973 0.4079929 0.3965591 0.3857173 0.3747567 0.3649065 0.3549583 0.346147 0.3372574 0.3294108 0.3215023 0.3145201 0.3074889 0.3012713 0.2950149 0.2894724 0.2838991 0.2789537 0.2739837 0.2695685
|
||||||
|
0.3260067 0.3363645 0.3473228 0.3584008 0.3717065 0.3864512 0.401035 0.4184498 0.438931 0.4590358 0.4825051 0.5122178 0.5418503 0.5749596 0.620894 0.6692876 0.7196215 0.7983373 0.8930331 0.9810481 1.1455 1.435858 1.723972 3.06281 3.479569 3.061841 1.720891 1.431183 1.139257 0.9739593 0.8857559 0.7909085 0.7123318 0.6630843 0.6163641 0.5723306 0.5442436 0.5254506 0.5046235 0.6600668 0.5916568 0.1053787 0.324299 0.3175806 0.3153523 0.3077486 0.2989039 0.2901882 0.2814592 0.2735063 0.2657846 0.2581551 0.2513817 0.2449855 0.2385924 0.2328572 0.2275815 0.2222788 0.2174162 0.213032 0.2086438 0.2045162 0.2008394 0.1972071 0.1937068
|
||||||
|
0.07889044 0.08153418 0.08419043 0.08701882 0.09030894 0.09435995 0.09932118 0.1050815 0.1113084 0.1176306 0.1239187 0.1305645 0.1386389 0.1497532 0.1654913 0.1866337 0.2127252 0.2422527 0.2731474 0.3032434 0.3305583 0.3534314 0.3705729 3.007106 3.643108 3.00695 0.3692689 0.3515675 0.3282436 0.300618 0.2703725 0.239492 0.210109 0.1842086 0.1631676 0.1472882 0.1356852 0.1267589 0.1189501 0.3679682 0.5516769 0.2577798 0.08846281 0.08238595 0.07764257 0.07410715 0.07139895 0.06908786 0.06687377 0.06464756 0.06245382 0.06040037 0.05856909 0.05696621 0.05552716 0.0541646 0.05281365 0.05146411 0.05015175 0.04893123 0.04783951 0.04687591 0.0460047 0.04517756 0.04435825
|
||||||
|
1.564492e-07 1.122884e-07 8.888702e-08 1.762498e-07 8.600421e-09 1.938033e-07 2.387622e-07 2.871272e-07 2.21893e-07 1.239545e-07 8.498516e-08 5.176826e-08 1.897711e-07 8.593484e-08 4.070753e-08 7.40209e-08 2.278317e-07 8.807671e-08 9.716474e-08 1.372644e-07 1.501705e-07 2.285518e-08 9.67386e-08 2.987679 3.6553 2.987679 1.266364e-07 1.456021e-08 5.034151e-08 2.597979e-07 1.838301e-07 6.343754e-08 8.150528e-08 5.003271e-09 9.525829e-08 2.182818e-07 1.843608e-07 1.385002e-08 6.420205e-09 0.3078006 0.542824 0.3078008 2.254644e-07 1.478402e-07 1.590096e-07 3.480243e-07 1.698739e-07 5.310058e-08 4.452434e-08 4.024338e-09 5.150024e-08 2.914611e-08 1.276424e-07 1.826463e-07 3.983407e-07 5.450444e-08 2.266521e-07 1.942693e-07 7.255422e-08 1.167992e-07 1.510635e-07 6.353517e-08 7.878904e-08 3.480655e-08 4.585462e-09
|
||||||
|
1.564492e-07 1.122884e-07 8.888694e-08 1.762498e-07 8.600284e-09 1.938033e-07 2.387623e-07 2.871272e-07 2.21893e-07 1.239545e-07 8.498505e-08 5.176823e-08 1.897709e-07 8.593484e-08 4.070745e-08 7.402096e-08 2.278317e-07 8.807665e-08 9.716481e-08 1.372644e-07 1.501703e-07 2.285522e-08 9.673848e-08 2.987679 3.6553 2.987679 1.266363e-07 1.456018e-08 5.034165e-08 2.597979e-07 1.8383e-07 6.34376e-08 8.150528e-08 5.003333e-09 9.525822e-08 2.182818e-07 1.843609e-07 1.385006e-08 6.420322e-09 0.3078006 0.542824 0.3078008 2.254643e-07 1.478403e-07 1.590095e-07 3.480243e-07 1.698739e-07 5.310051e-08 4.452434e-08 4.0244e-09 5.150032e-08 2.914612e-08 1.276426e-07 1.826462e-07 3.983408e-07 5.450447e-08 2.266521e-07 1.942694e-07 7.255434e-08 1.167993e-07 1.510637e-07 6.353518e-08 7.878896e-08 3.480649e-08 4.585462e-09
|
||||||
|
1.564492e-07 1.122884e-07 8.888694e-08 1.762499e-07 8.600289e-09 1.938033e-07 2.387623e-07 2.871272e-07 2.21893e-07 1.239545e-07 8.498504e-08 5.176823e-08 1.897709e-07 8.593484e-08 4.070745e-08 7.402096e-08 2.278317e-07 8.807665e-08 9.716482e-08 1.372644e-07 1.501703e-07 2.285522e-08 9.673848e-08 2.987679 3.6553 2.987679 1.266363e-07 1.456017e-08 5.034164e-08 2.597979e-07 1.8383e-07 6.34376e-08 8.150528e-08 5.003331e-09 9.525821e-08 2.182818e-07 1.843609e-07 1.385006e-08 6.420325e-09 0.3078006 0.542824 0.3078008 2.254643e-07 1.478403e-07 1.590095e-07 3.480243e-07 1.698739e-07 5.310052e-08 4.452434e-08 4.024398e-09 5.150032e-08 2.914612e-08 1.276426e-07 1.826462e-07 3.983408e-07 5.450447e-08 2.266521e-07 1.942694e-07 7.255434e-08 1.167993e-07 1.510637e-07 6.353518e-08 7.878896e-08 3.480649e-08 4.585462e-09
|
||||||
|
1.564492e-07 1.122884e-07 8.888702e-08 1.762499e-07 8.600421e-09 1.938034e-07 2.387622e-07 2.871271e-07 2.21893e-07 1.239546e-07 8.498516e-08 5.176833e-08 1.897711e-07 8.593477e-08 4.070753e-08 7.402097e-08 2.278317e-07 8.807664e-08 9.716474e-08 1.372644e-07 1.501705e-07 2.285512e-08 9.67386e-08 2.987679 3.6553 2.987679 1.266364e-07 1.456028e-08 5.034151e-08 2.59798e-07 1.838301e-07 6.343761e-08 8.150528e-08 5.003339e-09 9.525829e-08 2.182819e-07 1.843608e-07 1.384995e-08 6.420205e-09 0.3078006 0.542824 0.3078008 2.254644e-07 1.478402e-07 1.590096e-07 3.480242e-07 1.698739e-07 5.310051e-08 4.452434e-08 4.024406e-09 5.150024e-08 2.914618e-08 1.276424e-07 1.826463e-07 3.983407e-07 5.450437e-08 2.266521e-07 1.942693e-07 7.255422e-08 1.167992e-07 1.510635e-07 6.353524e-08 7.878904e-08 3.480648e-08 4.585462e-09
|
||||||
|
1.564492e-07 1.122885e-07 8.888715e-08 1.762501e-07 8.600158e-09 1.938033e-07 2.387623e-07 2.871272e-07 2.21893e-07 1.239545e-07 8.498502e-08 5.176819e-08 1.897708e-07 8.593463e-08 4.070766e-08 7.402083e-08 2.278317e-07 8.807678e-08 9.716461e-08 1.372642e-07 1.501702e-07 2.285526e-08 9.673845e-08 2.987679 3.6553 2.987679 1.266363e-07 1.456013e-08 5.034177e-08 2.597981e-07 1.838302e-07 6.343747e-08 8.150528e-08 5.003199e-09 9.525842e-08 2.18282e-07 1.843611e-07 1.38501e-08 6.420349e-09 0.3078006 0.542824 0.3078008 2.254643e-07 1.478403e-07 1.590094e-07 3.480241e-07 1.698737e-07 5.310065e-08 4.452434e-08 4.024266e-09 5.150011e-08 2.914633e-08 1.276427e-07 1.826462e-07 3.983409e-07 5.45045e-08 2.266521e-07 1.942694e-07 7.255437e-08 1.167993e-07 1.510638e-07 6.353538e-08 7.878917e-08 3.480662e-08 4.585462e-09
|
||||||
|
1.564492e-07 1.122885e-07 8.888713e-08 1.762501e-07 8.600153e-09 1.938034e-07 2.387623e-07 2.871272e-07 2.21893e-07 1.239545e-07 8.498504e-08 5.176827e-08 1.897708e-07 8.593455e-08 4.070764e-08 7.402088e-08 2.278317e-07 8.807672e-08 9.716463e-08 1.372642e-07 1.501702e-07 2.285517e-08 9.673848e-08 2.987679 3.6553 2.987679 1.266363e-07 1.456022e-08 5.034178e-08 2.597982e-07 1.838302e-07 6.343753e-08 8.150528e-08 5.003255e-09 9.52584e-08 2.182821e-07 1.843611e-07 1.385001e-08 6.420325e-09 0.3078006 0.542824 0.3078008 2.254643e-07 1.478402e-07 1.590094e-07 3.48024e-07 1.698737e-07 5.310059e-08 4.452434e-08 4.024322e-09 5.150013e-08 2.91464e-08 1.276427e-07 1.826463e-07 3.983408e-07 5.450445e-08 2.266521e-07 1.942693e-07 7.255434e-08 1.167992e-07 1.510638e-07 6.353546e-08 7.878916e-08 3.480656e-08 4.585462e-09
|
||||||
|
1.564492e-07 1.122884e-07 8.888702e-08 1.762501e-07 8.600201e-09 1.938033e-07 2.387621e-07 2.871272e-07 2.21893e-07 1.239545e-07 8.49852e-08 5.176818e-08 1.897709e-07 8.59346e-08 4.070753e-08 7.402099e-08 2.278317e-07 8.807662e-08 9.716474e-08 1.372642e-07 1.501702e-07 2.285526e-08 9.673864e-08 2.987679 3.6553 2.987679 1.266365e-07 1.456013e-08 5.034173e-08 2.597982e-07 1.838301e-07 6.343763e-08 8.150528e-08 5.00336e-09 9.525829e-08 2.182821e-07 1.84361e-07 1.38501e-08 6.420163e-09 0.3078006 0.542824 0.3078008 2.254645e-07 1.478403e-07 1.590094e-07 3.480241e-07 1.698739e-07 5.310049e-08 4.452434e-08 4.024427e-09 5.150024e-08 2.914636e-08 1.276426e-07 1.826462e-07 3.983407e-07 5.450442e-08 2.266521e-07 1.942693e-07 7.255419e-08 1.167993e-07 1.510638e-07 6.353542e-08 7.878904e-08 3.480646e-08 4.585462e-09
|
||||||
|
0.07889044 0.08153418 0.08419043 0.08701882 0.09030894 0.09435995 0.09932118 0.1050815 0.1113084 0.1176306 0.1239187 0.1305645 0.1386389 0.1497532 0.1654913 0.1866337 0.2127252 0.2422527 0.2731474 0.3032434 0.3305583 0.3534314 0.3705729 3.007106 3.643108 3.00695 0.3692689 0.3515675 0.3282436 0.300618 0.2703725 0.239492 0.210109 0.1842086 0.1631676 0.1472882 0.1356852 0.1267589 0.1189501 0.3679682 0.5516769 0.2577798 0.08846281 0.08238595 0.07764257 0.07410715 0.07139895 0.06908786 0.06687377 0.06464756 0.06245382 0.06040037 0.05856909 0.05696621 0.05552716 0.0541646 0.05281365 0.05146411 0.05015175 0.04893123 0.04783951 0.04687591 0.0460047 0.04517756 0.04435825
|
||||||
|
0.3260067 0.3363645 0.3473228 0.3584008 0.3717065 0.3864512 0.401035 0.4184498 0.438931 0.4590358 0.4825051 0.5122178 0.5418503 0.5749596 0.620894 0.6692876 0.7196215 0.7983373 0.8930331 0.9810481 1.1455 1.435858 1.723972 3.06281 3.479569 3.061841 1.720891 1.431183 1.139257 0.9739593 0.8857559 0.7909085 0.7123318 0.6630843 0.6163641 0.5723306 0.5442436 0.5254506 0.5046235 0.6600668 0.5916568 0.1053787 0.324299 0.3175806 0.3153523 0.3077486 0.2989039 0.2901882 0.2814592 0.2735063 0.2657846 0.2581551 0.2513817 0.2449855 0.2385924 0.2328572 0.2275815 0.2222788 0.2174162 0.213032 0.2086438 0.2045162 0.2008394 0.1972071 0.1937068
|
||||||
|
0.4438076 0.4559436 0.4704448 0.4847386 0.5020207 0.5190093 0.5398599 0.5602847 0.5858377 0.6107538 0.6427084 0.6736734 0.7147121 0.7541329 0.8087943 0.8606219 0.9373714 1.008648 1.125829 1.230708 1.440158 1.613244 2.198886 2.788961 2.987679 2.787503 2.195585 1.607394 1.433426 1.222683 1.117479 0.9998917 0.9291879 0.853102 0.8035754 0.7514675 0.7191642 0.6857824 0.6938878 0.7097453 0.6016893 0.4058476 0.4104768 0.4370055 0.4281905 0.4192973 0.4079929 0.3965591 0.3857173 0.3747567 0.3649065 0.3549583 0.346147 0.3372574 0.3294108 0.3215023 0.3145201 0.3074889 0.3012713 0.2950149 0.2894724 0.2838991 0.2789537 0.2739837 0.2695685
|
||||||
|
0.3260067 0.3363644 0.3473228 0.3584008 0.3717064 0.3864512 0.4010351 0.4184499 0.4389309 0.4590358 0.4825051 0.5122178 0.5418502 0.5749596 0.6208939 0.6692876 0.7196215 0.7983373 0.8930331 0.9810481 1.1455 1.435858 1.723972 1.914222 1.97871 1.912684 1.720891 1.431183 1.139257 0.9739592 0.885756 0.7909085 0.7123318 0.6630843 0.6163642 0.5723305 0.5442435 0.5254506 0.5046235 0.4722382 0.4203468 0.3605282 0.3242991 0.3175806 0.3153524 0.3077487 0.2989038 0.2901882 0.2814592 0.2735062 0.2657845 0.2581551 0.2513816 0.2449855 0.2385921 0.2328572 0.2275815 0.2222787 0.2174161 0.213032 0.2086437 0.2045162 0.2008394 0.1972071 0.1937068
|
||||||
|
0.0133671 0.103174 0.161102 0.1321454 0.01981753 0.1171069 0.1886141 0.1606799 0.03154613 0.1368369 0.230862 0.208004 0.05647711 0.1650847 0.3020293 0.298915 0.1252058 0.1979204 0.4401451 0.5311838 0.4472875 0.1536819 0.2990841 0.5738236 0.6613865 0.5702119 0.2925259 0.1586363 0.4468553 0.5274072 0.4351405 0.1946639 0.1232734 0.2943546 0.2986827 0.1671092 0.04655298 0.1966405 0.2236971 0.1398044 0.01435895 0.1384342 0.1696025 0.109466 0.009560954 0.1101514 0.1376043 0.08825757 0.01127213 0.09668601 0.1209162 0.07821064 0.008807627 0.0850097 0.1078746 0.07068323 0.006762736 0.07595089 0.09776526 0.06483996 0.005235632 0.0689388 0.08987227 0.0602516 0.004088645
|
||||||
|
0.3260067 0.3363644 0.3473228 0.3584008 0.3717064 0.3864512 0.4010351 0.4184499 0.4389309 0.4590358 0.4825051 0.5122178 0.5418502 0.5749596 0.6208939 0.6692876 0.7196215 0.7983373 0.8930331 0.9810481 1.1455 1.435858 1.723972 1.914222 1.97871 1.912684 1.720891 1.431183 1.139257 0.9739592 0.885756 0.7909085 0.7123318 0.6630843 0.6163642 0.5723305 0.5442435 0.5254506 0.5046235 0.4722382 0.4203468 0.3605282 0.3242991 0.3175806 0.3153524 0.3077487 0.2989038 0.2901882 0.2814592 0.2735062 0.2657845 0.2581551 0.2513816 0.2449855 0.2385921 0.2328572 0.2275815 0.2222787 0.2174161 0.213032 0.2086437 0.2045162 0.2008394 0.1972071 0.1937068
|
||||||
|
0.4438076 0.4559436 0.4704448 0.4847386 0.5020207 0.5190093 0.5398599 0.5602847 0.5858377 0.6107538 0.6427084 0.6736734 0.7147121 0.7541329 0.8087943 0.8606219 0.9373714 1.008648 1.125829 1.230708 1.440158 1.613244 2.198886 2.788961 2.987679 2.787503 2.195585 1.607394 1.433426 1.222683 1.117479 0.9998917 0.9291879 0.853102 0.8035754 0.7514675 0.7191642 0.6857824 0.6938878 0.7097453 0.6016893 0.4058476 0.4104768 0.4370055 0.4281905 0.4192973 0.4079929 0.3965591 0.3857173 0.3747567 0.3649065 0.3549583 0.346147 0.3372574 0.3294108 0.3215023 0.3145201 0.3074889 0.3012713 0.2950149 0.2894724 0.2838991 0.2789537 0.2739837 0.2695685
|
||||||
|
0.3260067 0.3363645 0.3473228 0.3584008 0.3717065 0.3864512 0.401035 0.4184498 0.438931 0.4590358 0.4825051 0.5122178 0.5418503 0.5749596 0.620894 0.6692876 0.7196215 0.7983373 0.8930331 0.9810481 1.1455 1.435858 1.723972 3.06281 3.479569 3.061841 1.720891 1.431183 1.139257 0.9739593 0.8857559 0.7909085 0.7123318 0.6630843 0.6163641 0.5723306 0.5442436 0.5254506 0.5046235 0.6600668 0.5916568 0.1053787 0.324299 0.3175806 0.3153523 0.3077486 0.2989039 0.2901882 0.2814592 0.2735063 0.2657846 0.2581551 0.2513817 0.2449855 0.2385924 0.2328572 0.2275815 0.2222788 0.2174162 0.213032 0.2086438 0.2045162 0.2008394 0.1972071 0.1937068
|
||||||
|
0.01336695 0.1031739 0.161102 0.1321453 0.01981753 0.1171067 0.1886142 0.1606797 0.03154635 0.1368371 0.2308619 0.2080039 0.05647729 0.1650848 0.3020293 0.298915 0.1252055 0.1979204 0.440145 0.531184 0.4472874 0.1536818 0.299084 3.026 3.630765 3.02569 0.2925259 0.1586363 0.4468552 0.5274073 0.4351404 0.1946639 0.1232734 0.2943546 0.2986827 0.1671094 0.04655316 0.1966404 0.2236971 0.4124265 0.5511931 0.1922355 0.1696027 0.1094658 0.009561111 0.110151 0.1376044 0.08825763 0.01127217 0.09668601 0.1209163 0.07821067 0.008807753 0.08500987 0.1078743 0.07068328 0.00676251 0.07595107 0.0977652 0.06483985 0.005235482 0.06893886 0.0898722 0.06025163 0.004088641
|
||||||
|
0.3260067 0.3363645 0.3473228 0.3584008 0.3717065 0.3864512 0.401035 0.4184498 0.438931 0.4590358 0.4825051 0.5122178 0.5418503 0.5749596 0.620894 0.6692876 0.7196215 0.7983373 0.8930331 0.9810481 1.1455 1.435858 1.723972 3.06281 3.479569 3.061841 1.720891 1.431183 1.139257 0.9739593 0.8857559 0.7909085 0.7123318 0.6630843 0.6163641 0.5723306 0.5442436 0.5254506 0.5046235 0.6600668 0.5916568 0.1053787 0.324299 0.3175806 0.3153523 0.3077486 0.2989039 0.2901882 0.2814592 0.2735063 0.2657846 0.2581551 0.2513817 0.2449855 0.2385924 0.2328572 0.2275815 0.2222788 0.2174162 0.213032 0.2086438 0.2045162 0.2008394 0.1972071 0.1937068
|
||||||
|
0.4438076 0.4559436 0.4704448 0.4847386 0.5020207 0.5190093 0.5398599 0.5602847 0.5858377 0.6107538 0.6427084 0.6736734 0.7147121 0.7541329 0.8087943 0.8606219 0.9373714 1.008648 1.125829 1.230708 1.440158 1.613244 2.198886 2.788961 2.987679 2.787503 2.195585 1.607394 1.433426 1.222683 1.117479 0.9998917 0.9291879 0.853102 0.8035754 0.7514675 0.7191642 0.6857824 0.6938878 0.7097453 0.6016893 0.4058476 0.4104768 0.4370055 0.4281905 0.4192973 0.4079929 0.3965591 0.3857173 0.3747567 0.3649065 0.3549583 0.346147 0.3372574 0.3294108 0.3215023 0.3145201 0.3074889 0.3012713 0.2950149 0.2894724 0.2838991 0.2789537 0.2739837 0.2695685
|
||||||
|
0.3260067 0.3363644 0.3473228 0.3584008 0.3717064 0.3864512 0.4010351 0.4184499 0.4389309 0.4590358 0.4825051 0.5122178 0.5418502 0.5749596 0.6208939 0.6692876 0.7196215 0.7983373 0.8930331 0.9810481 1.1455 1.435858 1.723972 1.914222 1.97871 1.912684 1.720891 1.431183 1.139257 0.9739592 0.885756 0.7909085 0.7123318 0.6630843 0.6163642 0.5723305 0.5442435 0.5254506 0.5046235 0.4722382 0.4203468 0.3605282 0.3242991 0.3175806 0.3153524 0.3077487 0.2989038 0.2901882 0.2814592 0.2735062 0.2657845 0.2581551 0.2513816 0.2449855 0.2385921 0.2328572 0.2275815 0.2222787 0.2174161 0.213032 0.2086437 0.2045162 0.2008394 0.1972071 0.1937068
|
||||||
|
0.0133671 0.103174 0.161102 0.1321454 0.01981753 0.1171069 0.1886141 0.1606799 0.03154613 0.1368369 0.230862 0.208004 0.05647711 0.1650847 0.3020293 0.298915 0.1252058 0.1979204 0.4401451 0.5311838 0.4472875 0.1536819 0.2990841 0.5738236 0.6613865 0.5702119 0.2925259 0.1586363 0.4468553 0.5274072 0.4351405 0.1946639 0.1232734 0.2943546 0.2986827 0.1671092 0.04655298 0.1966405 0.2236971 0.1398044 0.01435895 0.1384342 0.1696025 0.109466 0.009560954 0.1101514 0.1376043 0.08825757 0.01127213 0.09668601 0.1209162 0.07821064 0.008807627 0.0850097 0.1078746 0.07068323 0.006762736 0.07595089 0.09776526 0.06483996 0.005235632 0.0689388 0.08987227 0.0602516 0.004088645
|
||||||
|
0.3260067 0.3363644 0.3473228 0.3584008 0.3717064 0.3864512 0.4010351 0.4184499 0.4389309 0.4590358 0.4825051 0.5122178 0.5418502 0.5749596 0.6208939 0.6692876 0.7196215 0.7983373 0.8930331 0.9810481 1.1455 1.435858 1.723972 1.914222 1.97871 1.912684 1.720891 1.431183 1.139257 0.9739592 0.885756 0.7909085 0.7123318 0.6630843 0.6163642 0.5723305 0.5442435 0.5254506 0.5046235 0.4722382 0.4203468 0.3605282 0.3242991 0.3175806 0.3153524 0.3077487 0.2989038 0.2901882 0.2814592 0.2735062 0.2657845 0.2581551 0.2513816 0.2449855 0.2385921 0.2328572 0.2275815 0.2222787 0.2174161 0.213032 0.2086437 0.2045162 0.2008394 0.1972071 0.1937068
|
||||||
|
0.4438076 0.4559436 0.4704448 0.4847386 0.5020207 0.5190093 0.5398599 0.5602847 0.5858377 0.6107538 0.6427084 0.6736734 0.7147121 0.7541329 0.8087943 0.8606219 0.9373714 1.008648 1.125829 1.230708 1.440158 1.613244 2.198886 2.788961 2.987679 2.787503 2.195585 1.607394 1.433426 1.222683 1.117479 0.9998917 0.9291879 0.853102 0.8035754 0.7514675 0.7191642 0.6857824 0.6938878 0.7097453 0.6016893 0.4058476 0.4104768 0.4370055 0.4281905 0.4192973 0.4079929 0.3965591 0.3857173 0.3747567 0.3649065 0.3549583 0.346147 0.3372574 0.3294108 0.3215023 0.3145201 0.3074889 0.3012713 0.2950149 0.2894724 0.2838991 0.2789537 0.2739837 0.2695685
|
||||||
|
0.3260067 0.3363645 0.3473228 0.3584008 0.3717065 0.3864512 0.401035 0.4184498 0.438931 0.4590358 0.4825051 0.5122178 0.5418503 0.5749596 0.620894 0.6692876 0.7196215 0.7983373 0.8930331 0.9810481 1.1455 1.435858 1.723972 3.06281 3.479569 3.061841 1.720891 1.431183 1.139257 0.9739593 0.8857559 0.7909085 0.7123318 0.6630843 0.6163641 0.5723306 0.5442436 0.5254506 0.5046235 0.6600668 0.5916568 0.1053787 0.324299 0.3175806 0.3153523 0.3077486 0.2989039 0.2901882 0.2814592 0.2735063 0.2657846 0.2581551 0.2513817 0.2449855 0.2385924 0.2328572 0.2275815 0.2222788 0.2174162 0.213032 0.2086438 0.2045162 0.2008394 0.1972071 0.1937068
|
||||||
|
0.07889044 0.08153418 0.08419043 0.08701882 0.09030894 0.09435995 0.09932118 0.1050815 0.1113084 0.1176306 0.1239187 0.1305645 0.1386389 0.1497532 0.1654913 0.1866337 0.2127252 0.2422527 0.2731474 0.3032434 0.3305583 0.3534314 0.3705729 3.007106 3.643108 3.00695 0.3692689 0.3515675 0.3282436 0.300618 0.2703725 0.239492 0.210109 0.1842086 0.1631676 0.1472882 0.1356852 0.1267589 0.1189501 0.3679682 0.5516769 0.2577798 0.08846281 0.08238595 0.07764257 0.07410715 0.07139895 0.06908786 0.06687377 0.06464756 0.06245382 0.06040037 0.05856909 0.05696621 0.05552716 0.0541646 0.05281365 0.05146411 0.05015175 0.04893123 0.04783951 0.04687591 0.0460047 0.04517756 0.04435825
|
||||||
|
1.564492e-07 1.122885e-07 8.888702e-08 1.762501e-07 8.600116e-09 1.938031e-07 2.387619e-07 2.871269e-07 2.21893e-07 1.239548e-07 8.498547e-08 5.176805e-08 1.897708e-07 8.59346e-08 4.070753e-08 7.402088e-08 2.278317e-07 8.807672e-08 9.716474e-08 1.372642e-07 1.501702e-07 2.28554e-08 9.673891e-08 2.987679 3.6553 2.987679 1.266367e-07 1.456e-08 5.034181e-08 2.597982e-07 1.838301e-07 6.343753e-08 8.150528e-08 5.003254e-09 9.525829e-08 2.182821e-07 1.843611e-07 1.385024e-08 6.419893e-09 0.3078006 0.542824 0.3078008 2.254647e-07 1.478404e-07 1.590093e-07 3.480241e-07 1.698739e-07 5.31006e-08 4.452434e-08 4.024321e-09 5.150024e-08 2.914635e-08 1.276427e-07 1.82646e-07 3.983404e-07 5.450418e-08 2.266521e-07 1.942691e-07 7.255392e-08 1.167995e-07 1.510639e-07 6.353541e-08 7.878904e-08 3.480657e-08 4.585462e-09
|
||||||
|
1.564492e-07 1.122886e-07 8.888708e-08 1.762499e-07 8.600304e-09 1.938035e-07 2.387625e-07 2.871271e-07 2.21893e-07 1.239546e-07 8.49849e-08 5.176842e-08 1.89771e-07 8.593476e-08 4.070759e-08 7.402079e-08 2.278317e-07 8.807682e-08 9.716468e-08 1.372644e-07 1.501704e-07 2.285502e-08 9.673834e-08 2.987679 3.6553 2.987679 1.266362e-07 1.456037e-08 5.034163e-08 2.59798e-07 1.838302e-07 6.343743e-08 8.150528e-08 5.003165e-09 9.525835e-08 2.182819e-07 1.843609e-07 1.384986e-08 6.420458e-09 0.3078006 0.542824 0.3078008 2.254642e-07 1.478401e-07 1.590095e-07 3.480242e-07 1.698738e-07 5.310068e-08 4.452434e-08 4.024231e-09 5.150018e-08 2.914619e-08 1.276425e-07 1.826464e-07 3.98341e-07 5.45043e-08 2.266521e-07 1.942692e-07 7.255448e-08 1.167991e-07 1.510637e-07 6.353525e-08 7.87891e-08 3.480666e-08 4.585462e-09
|
||||||
|
1.564492e-07 1.122883e-07 8.888687e-08 1.762498e-07 8.600378e-09 1.938033e-07 2.387621e-07 2.87127e-07 2.21893e-07 1.239547e-07 8.498525e-08 5.176826e-08 1.89771e-07 8.593487e-08 4.070738e-08 7.402101e-08 2.278317e-07 8.80766e-08 9.716489e-08 1.372645e-07 1.501704e-07 2.285518e-08 9.673868e-08 2.987679 3.6553 2.987679 1.266365e-07 1.456021e-08 5.034155e-08 2.597979e-07 1.8383e-07 6.343765e-08 8.150528e-08 5.003383e-09 9.525814e-08 2.182818e-07 1.843608e-07 1.385002e-08 6.420119e-09 0.3078006 0.542824 0.3078008 2.254645e-07 1.478402e-07 1.590096e-07 3.480243e-07 1.69874e-07 5.310046e-08 4.452434e-08 4.02445e-09 5.150039e-08 2.914608e-08 1.276425e-07 1.826463e-07 3.983406e-07 5.450425e-08 2.266521e-07 1.942691e-07 7.255414e-08 1.167992e-07 1.510636e-07 6.353514e-08 7.878889e-08 3.480644e-08 4.585462e-09
|
||||||
|
1.564492e-07 1.122883e-07 8.888674e-08 1.762497e-07 8.60019e-09 1.938031e-07 2.387623e-07 2.871273e-07 2.21893e-07 1.239544e-07 8.498511e-08 5.176805e-08 1.897708e-07 8.593494e-08 4.070725e-08 7.402105e-08 2.278317e-07 8.807655e-08 9.716502e-08 1.372645e-07 1.501702e-07 2.285539e-08 9.673855e-08 2.987679 3.6553 2.987679 1.266364e-07 1.456e-08 5.034174e-08 2.597978e-07 1.838298e-07 6.343769e-08 8.150528e-08 5.00342e-09 9.525801e-08 2.182817e-07 1.84361e-07 1.385023e-08 6.420252e-09 0.3078006 0.542824 0.3078008 2.254644e-07 1.478404e-07 1.590094e-07 3.480244e-07 1.698741e-07 5.310043e-08 4.452434e-08 4.024487e-09 5.150052e-08 2.914602e-08 1.276427e-07 1.82646e-07 3.983407e-07 5.450456e-08 2.266521e-07 1.942695e-07 7.255427e-08 1.167994e-07 1.510638e-07 6.353508e-08 7.878877e-08 3.48064e-08 4.585462e-09
|
||||||
|
1.564492e-07 1.122884e-07 8.888679e-08 1.762497e-07 8.600452e-09 1.938035e-07 2.387627e-07 2.871276e-07 2.21893e-07 1.239541e-07 8.49847e-08 5.176846e-08 1.897711e-07 8.593501e-08 4.07073e-08 7.402098e-08 2.278317e-07 8.807663e-08 9.716497e-08 1.372646e-07 1.501705e-07 2.285498e-08 9.673813e-08 2.987679 3.6553 2.987679 1.266359e-07 1.456041e-08 5.034148e-08 2.597978e-07 1.838299e-07 6.343762e-08 8.150528e-08 5.003356e-09 9.525806e-08 2.182816e-07 1.843608e-07 1.384982e-08 6.42067e-09 0.3078006 0.542824 0.3078008 2.254639e-07 1.4784e-07 1.590097e-07 3.480245e-07 1.698741e-07 5.310049e-08 4.452434e-08 4.024423e-09 5.150047e-08 2.914594e-08 1.276424e-07 1.826465e-07 3.983412e-07 5.450482e-08 2.266521e-07 1.942697e-07 7.255469e-08 1.16799e-07 1.510635e-07 6.3535e-08 7.878881e-08 3.480647e-08 4.585462e-09
|
||||||
|
1.564492e-07 1.122884e-07 8.888694e-08 1.762497e-07 8.600463e-09 1.938032e-07 2.387621e-07 2.871272e-07 2.21893e-07 1.239545e-07 8.498531e-08 5.17681e-08 1.897711e-07 8.593498e-08 4.070744e-08 7.402095e-08 2.278317e-07 8.807665e-08 9.716483e-08 1.372646e-07 1.501705e-07 2.285535e-08 9.673875e-08 2.987679 3.6553 2.987679 1.266366e-07 1.456004e-08 5.034147e-08 2.597978e-07 1.8383e-07 6.34376e-08 8.150528e-08 5.003324e-09 9.52582e-08 2.182817e-07 1.843607e-07 1.385019e-08 6.420059e-09 0.3078006 0.542824 0.3078008 2.254646e-07 1.478404e-07 1.590097e-07 3.480245e-07 1.698739e-07 5.310052e-08 4.452434e-08 4.024391e-09 5.150033e-08 2.914598e-08 1.276424e-07 1.826461e-07 3.983406e-07 5.450446e-08 2.266521e-07 1.942694e-07 7.255408e-08 1.167994e-07 1.510635e-07 6.353503e-08 7.878896e-08 3.48065e-08 4.585462e-09
|
||||||
|
1.564492e-07 1.122886e-07 8.888708e-08 1.762499e-07 8.600294e-09 1.938035e-07 2.387625e-07 2.871271e-07 2.21893e-07 1.239546e-07 8.498491e-08 5.176843e-08 1.89771e-07 8.593477e-08 4.070759e-08 7.40208e-08 2.278317e-07 8.807681e-08 9.716467e-08 1.372644e-07 1.501703e-07 2.285501e-08 9.673835e-08 2.987679 3.6553 2.987679 1.266362e-07 1.456038e-08 5.034164e-08 2.59798e-07 1.838302e-07 6.343744e-08 8.150528e-08 5.003168e-09 9.525836e-08 2.182819e-07 1.843609e-07 1.384985e-08 6.420451e-09 0.3078006 0.542824 0.3078008 2.254642e-07 1.478401e-07 1.590095e-07 3.480242e-07 1.698738e-07 5.310068e-08 4.452434e-08 4.024235e-09 5.150017e-08 2.914619e-08 1.276426e-07 1.826464e-07 3.983409e-07 5.450431e-08 2.266521e-07 1.942692e-07 7.255447e-08 1.167991e-07 1.510637e-07 6.353525e-08 7.878911e-08 3.480665e-08 4.585462e-09
|
||||||
|
0.07889044 0.08153418 0.08419043 0.08701882 0.09030894 0.09435995 0.09932118 0.1050815 0.1113084 0.1176306 0.1239187 0.1305645 0.1386389 0.1497532 0.1654913 0.1866337 0.2127252 0.2422527 0.2731474 0.3032434 0.3305583 0.3534314 0.3705729 3.007106 3.643108 3.00695 0.3692689 0.3515675 0.3282436 0.300618 0.2703725 0.239492 0.210109 0.1842086 0.1631676 0.1472882 0.1356852 0.1267589 0.1189501 0.3679682 0.5516769 0.2577798 0.08846281 0.08238595 0.07764257 0.07410715 0.07139895 0.06908786 0.06687377 0.06464756 0.06245382 0.06040037 0.05856909 0.05696621 0.05552716 0.0541646 0.05281365 0.05146411 0.05015175 0.04893123 0.04783951 0.04687591 0.0460047 0.04517756 0.04435825
|
||||||
|
0.3260067 0.3363645 0.3473228 0.3584008 0.3717065 0.3864512 0.401035 0.4184498 0.438931 0.4590358 0.4825051 0.5122178 0.5418503 0.5749596 0.620894 0.6692876 0.7196215 0.7983373 0.8930331 0.9810481 1.1455 1.435858 1.723972 3.06281 3.479569 3.061841 1.720891 1.431183 1.139257 0.9739593 0.8857559 0.7909085 0.7123318 0.6630843 0.6163641 0.5723306 0.5442436 0.5254506 0.5046235 0.6600668 0.5916568 0.1053787 0.324299 0.3175806 0.3153523 0.3077486 0.2989039 0.2901882 0.2814592 0.2735063 0.2657846 0.2581551 0.2513817 0.2449855 0.2385924 0.2328572 0.2275815 0.2222788 0.2174162 0.213032 0.2086438 0.2045162 0.2008394 0.1972071 0.1937068
|
||||||
|
0.4438076 0.4559436 0.4704448 0.4847386 0.5020207 0.5190093 0.5398599 0.5602847 0.5858377 0.6107538 0.6427084 0.6736734 0.7147121 0.7541329 0.8087943 0.8606219 0.9373714 1.008648 1.125829 1.230708 1.440158 1.613244 2.198886 2.788961 2.987679 2.787503 2.195585 1.607394 1.433426 1.222683 1.117479 0.9998917 0.9291879 0.853102 0.8035754 0.7514675 0.7191642 0.6857824 0.6938878 0.7097453 0.6016893 0.4058476 0.4104768 0.4370055 0.4281905 0.4192973 0.4079929 0.3965591 0.3857173 0.3747567 0.3649065 0.3549583 0.346147 0.3372574 0.3294108 0.3215023 0.3145201 0.3074889 0.3012713 0.2950149 0.2894724 0.2838991 0.2789537 0.2739837 0.2695685
|
||||||
|
0.3260067 0.3363644 0.3473228 0.3584008 0.3717064 0.3864512 0.4010351 0.4184499 0.4389309 0.4590358 0.4825051 0.5122178 0.5418502 0.5749596 0.6208939 0.6692876 0.7196215 0.7983373 0.8930331 0.9810481 1.1455 1.435858 1.723972 1.914222 1.97871 1.912684 1.720891 1.431183 1.139257 0.9739592 0.885756 0.7909085 0.7123318 0.6630843 0.6163642 0.5723305 0.5442435 0.5254506 0.5046235 0.4722382 0.4203468 0.3605282 0.3242991 0.3175806 0.3153524 0.3077487 0.2989038 0.2901882 0.2814592 0.2735062 0.2657845 0.2581551 0.2513816 0.2449855 0.2385921 0.2328572 0.2275815 0.2222787 0.2174161 0.213032 0.2086437 0.2045162 0.2008394 0.1972071 0.1937068
|
||||||
|
0.0133671 0.103174 0.161102 0.1321454 0.01981753 0.1171069 0.1886141 0.1606799 0.03154613 0.1368369 0.230862 0.208004 0.05647711 0.1650847 0.3020293 0.298915 0.1252058 0.1979204 0.4401451 0.5311838 0.4472875 0.1536819 0.2990841 0.5738236 0.6613865 0.5702119 0.2925259 0.1586363 0.4468553 0.5274072 0.4351405 0.1946639 0.1232734 0.2943546 0.2986827 0.1671092 0.04655298 0.1966405 0.2236971 0.1398044 0.01435895 0.1384342 0.1696025 0.109466 0.009560954 0.1101514 0.1376043 0.08825757 0.01127213 0.09668601 0.1209162 0.07821064 0.008807627 0.0850097 0.1078746 0.07068323 0.006762736 0.07595089 0.09776526 0.06483996 0.005235632 0.0689388 0.08987227 0.0602516 0.004088645
|
||||||
|
0.3260067 0.3363644 0.3473228 0.3584008 0.3717064 0.3864512 0.4010351 0.4184499 0.4389309 0.4590358 0.4825051 0.5122178 0.5418502 0.5749596 0.6208939 0.6692876 0.7196215 0.7983373 0.8930331 0.9810481 1.1455 1.435858 1.723972 1.914222 1.97871 1.912684 1.720891 1.431183 1.139257 0.9739592 0.885756 0.7909085 0.7123318 0.6630843 0.6163642 0.5723305 0.5442435 0.5254506 0.5046235 0.4722382 0.4203468 0.3605282 0.3242991 0.3175806 0.3153524 0.3077487 0.2989038 0.2901882 0.2814592 0.2735062 0.2657845 0.2581551 0.2513816 0.2449855 0.2385921 0.2328572 0.2275815 0.2222787 0.2174161 0.213032 0.2086437 0.2045162 0.2008394 0.1972071 0.1937068
|
||||||
|
0.4438076 0.4559436 0.4704448 0.4847386 0.5020207 0.5190093 0.5398599 0.5602847 0.5858377 0.6107538 0.6427084 0.6736734 0.7147121 0.7541329 0.8087943 0.8606219 0.9373714 1.008648 1.125829 1.230708 1.440158 1.613244 2.198886 2.788961 2.987679 2.787503 2.195585 1.607394 1.433426 1.222683 1.117479 0.9998917 0.9291879 0.853102 0.8035754 0.7514675 0.7191642 0.6857824 0.6938878 0.7097453 0.6016893 0.4058476 0.4104768 0.4370055 0.4281905 0.4192973 0.4079929 0.3965591 0.3857173 0.3747567 0.3649065 0.3549583 0.346147 0.3372574 0.3294108 0.3215023 0.3145201 0.3074889 0.3012713 0.2950149 0.2894724 0.2838991 0.2789537 0.2739837 0.2695685
|
||||||
|
0.3260067 0.3363645 0.3473228 0.3584008 0.3717065 0.3864512 0.401035 0.4184498 0.438931 0.4590358 0.4825051 0.5122178 0.5418503 0.5749596 0.620894 0.6692876 0.7196215 0.7983373 0.8930331 0.9810481 1.1455 1.435858 1.723972 3.06281 3.479569 3.061841 1.720891 1.431183 1.139257 0.9739593 0.8857559 0.7909085 0.7123318 0.6630843 0.6163641 0.5723306 0.5442436 0.5254506 0.5046235 0.6600668 0.5916568 0.1053787 0.324299 0.3175806 0.3153523 0.3077486 0.2989039 0.2901882 0.2814592 0.2735063 0.2657846 0.2581551 0.2513817 0.2449855 0.2385924 0.2328572 0.2275815 0.2222788 0.2174162 0.213032 0.2086438 0.2045162 0.2008394 0.1972071 0.1937068
|
||||||
|
0.01336695 0.1031739 0.161102 0.1321453 0.01981753 0.1171067 0.1886142 0.1606797 0.03154635 0.1368371 0.2308619 0.2080039 0.05647729 0.1650848 0.3020293 0.298915 0.1252055 0.1979204 0.440145 0.531184 0.4472874 0.1536818 0.299084 3.026 3.630765 3.02569 0.2925259 0.1586363 0.4468552 0.5274073 0.4351404 0.1946639 0.1232734 0.2943546 0.2986827 0.1671094 0.04655316 0.1966404 0.2236971 0.4124265 0.5511931 0.1922355 0.1696027 0.1094658 0.009561111 0.110151 0.1376044 0.08825763 0.01127217 0.09668601 0.1209163 0.07821067 0.008807753 0.08500987 0.1078743 0.07068328 0.00676251 0.07595107 0.0977652 0.06483985 0.005235482 0.06893886 0.0898722 0.06025163 0.004088641
|
||||||
|
0.3260067 0.3363645 0.3473228 0.3584008 0.3717065 0.3864512 0.401035 0.4184498 0.438931 0.4590358 0.4825051 0.5122178 0.5418503 0.5749596 0.620894 0.6692876 0.7196215 0.7983373 0.8930331 0.9810481 1.1455 1.435858 1.723972 3.06281 3.479569 3.061841 1.720891 1.431183 1.139257 0.9739593 0.8857559 0.7909085 0.7123318 0.6630843 0.6163641 0.5723306 0.5442436 0.5254506 0.5046235 0.6600668 0.5916568 0.1053787 0.324299 0.3175806 0.3153523 0.3077486 0.2989039 0.2901882 0.2814592 0.2735063 0.2657846 0.2581551 0.2513817 0.2449855 0.2385924 0.2328572 0.2275815 0.2222788 0.2174162 0.213032 0.2086438 0.2045162 0.2008394 0.1972071 0.1937068
|
||||||
|
0.4438076 0.4559436 0.4704448 0.4847386 0.5020207 0.5190093 0.5398599 0.5602847 0.5858377 0.6107538 0.6427084 0.6736734 0.7147121 0.7541329 0.8087943 0.8606219 0.9373714 1.008648 1.125829 1.230708 1.440158 1.613244 2.198886 2.788961 2.987679 2.787503 2.195585 1.607394 1.433426 1.222683 1.117479 0.9998917 0.9291879 0.853102 0.8035754 0.7514675 0.7191642 0.6857824 0.6938878 0.7097453 0.6016893 0.4058476 0.4104768 0.4370055 0.4281905 0.4192973 0.4079929 0.3965591 0.3857173 0.3747567 0.3649065 0.3549583 0.346147 0.3372574 0.3294108 0.3215023 0.3145201 0.3074889 0.3012713 0.2950149 0.2894724 0.2838991 0.2789537 0.2739837 0.2695685
|
||||||
|
0.3260067 0.3363644 0.3473228 0.3584008 0.3717064 0.3864512 0.4010351 0.4184499 0.4389309 0.4590358 0.4825051 0.5122178 0.5418502 0.5749596 0.6208939 0.6692876 0.7196215 0.7983373 0.8930331 0.9810481 1.1455 1.435858 1.723972 1.914222 1.97871 1.912684 1.720891 1.431183 1.139257 0.9739592 0.885756 0.7909085 0.7123318 0.6630843 0.6163642 0.5723305 0.5442435 0.5254506 0.5046235 0.4722382 0.4203468 0.3605282 0.3242991 0.3175806 0.3153524 0.3077487 0.2989038 0.2901882 0.2814592 0.2735062 0.2657845 0.2581551 0.2513816 0.2449855 0.2385921 0.2328572 0.2275815 0.2222787 0.2174161 0.213032 0.2086437 0.2045162 0.2008394 0.1972071 0.1937068
|
||||||
|
0.07889045 0.08153424 0.08419035 0.08701893 0.09030894 0.09436007 0.09932096 0.1050817 0.1113084 0.1176305 0.1239188 0.1305645 0.1386389 0.1497532 0.1654913 0.1866336 0.2127252 0.2422527 0.2731474 0.3032433 0.3305584 0.3534314 0.3705729 0.3810754 0.3844073 0.3804049 0.3692688 0.3515675 0.3282437 0.3006178 0.2703726 0.239492 0.2101091 0.1842086 0.1631677 0.1472881 0.1356851 0.126759 0.1189501 0.1112676 0.1034146 0.09563871 0.0884626 0.08238604 0.07764256 0.07410739 0.07139879 0.06908784 0.06687377 0.06464756 0.06245377 0.06040035 0.05856907 0.05696608 0.05552753 0.05416456 0.05281367 0.05146397 0.05015182 0.0489313 0.04783952 0.04687586 0.04600478 0.04517753 0.04435825
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
+8
-4
@@ -79,6 +79,10 @@ private val lightScheme = lightColorScheme(
|
|||||||
onSecondary = Color(0xFFFFFFFF),
|
onSecondary = Color(0xFFFFFFFF),
|
||||||
secondaryContainer = Color(0xFFF1E1BB),
|
secondaryContainer = Color(0xFFF1E1BB),
|
||||||
onSecondaryContainer = Color(0xFF221B04),
|
onSecondaryContainer = Color(0xFF221B04),
|
||||||
|
tertiary = Color(0xFF3C6FE0), // AMSAT Active (darker for light theme)
|
||||||
|
onTertiary = Color(0xFFFFFFFF),
|
||||||
|
tertiaryContainer = Color(0xFFE09800), // AMSAT Telemetry (darker)
|
||||||
|
onTertiaryContainer = Color(0xFF000000),
|
||||||
background = Color(0xFFFFF8F0),
|
background = Color(0xFFFFF8F0),
|
||||||
onBackground = Color(0xFF1E1B13),
|
onBackground = Color(0xFF1E1B13),
|
||||||
surface = Color(0xFFFFF8F0),
|
surface = Color(0xFFFFF8F0),
|
||||||
@@ -101,10 +105,10 @@ private val darkScheme = darkColorScheme(
|
|||||||
onSecondary = Color(0xFF000000),
|
onSecondary = Color(0xFF000000),
|
||||||
secondaryContainer = Color(0xFF404040), // navBar indicator,
|
secondaryContainer = Color(0xFF404040), // navBar indicator,
|
||||||
onSecondaryContainer = Color(0xFFE0E0E0), // navBar active icon
|
onSecondaryContainer = Color(0xFFE0E0E0), // navBar active icon
|
||||||
// tertiary = Color(0xFF121212),
|
tertiary = Color(0xFF648FFF), // AMSAT Active (from amsat.org/status)
|
||||||
// onTertiary = Color(0xFF121212),
|
onTertiary = Color(0xFF000000),
|
||||||
// tertiaryContainer = Color(0xFF121212),
|
tertiaryContainer = Color(0xFFFFB000), // AMSAT Telemetry
|
||||||
// onTertiaryContainer = Color(0xFF121212),
|
onTertiaryContainer = Color(0xFF000000),
|
||||||
background = Color(0xFF121212),
|
background = Color(0xFF121212),
|
||||||
onBackground = Color(0xFFE0E0E0),
|
onBackground = Color(0xFFE0E0E0),
|
||||||
surface = Color(0xFF202020), // card background
|
surface = Color(0xFF202020), // card background
|
||||||
|
|||||||
@@ -93,11 +93,11 @@
|
|||||||
\n\nPerbarui database setidaknya seminggu sekali untuk prediksi yang akurat.</string>
|
\n\nPerbarui database setidaknya seminggu sekali untuk prediksi yang akurat.</string>
|
||||||
<string name="pass_whatsnew_title" translatable="false">Apa yang baru di Look4Sat</string>
|
<string name="pass_whatsnew_title" translatable="false">Apa yang baru di Look4Sat</string>
|
||||||
<string name="pass_whatsnew_message" translatable="false">
|
<string name="pass_whatsnew_message" translatable="false">
|
||||||
* BARU: Status satelit AMSAT kini menggunakan API resmi AMSAT (v1 JSON, lebih cepat dan andal)
|
* BARU: Decoder CW kini memakai jaringan saraf DeepCW — jauh lebih baik menafsir Morse sinyal lemah
|
||||||
* Halaman status AMSAT kini mendukung layar penuh (tombol refresh dan waktu selalu dapat diakses)
|
* BARU: Riwayat CW tersimpan permanen (teks tidak lagi hilang)
|
||||||
* WaveLog: pencarian grid lawan QRZ (dialog ⚙ tempel Cookie + uji query)
|
* Waterfall CW: skema warna inferno baru dengan gradasi halus
|
||||||
* WaveLog: tombol perbarui daftar satelit LoTW (112 satelit bawaan)
|
* Dukungan 64-bit (arm64) dipulihkan
|
||||||
* WaveLog: laporan sinyal RST 59/59 untuk QSO (v1 dan v2)
|
* Perbaikan: jeda/lanjut tidak lagi menampilkan galat "gagal menafsir" yang keliru
|
||||||
</string>
|
</string>
|
||||||
<string name="radar_back">Kembali</string>
|
<string name="radar_back">Kembali</string>
|
||||||
<string name="radar_notify">Beri tahu</string>
|
<string name="radar_notify">Beri tahu</string>
|
||||||
@@ -132,6 +132,8 @@
|
|||||||
<string name="radar_cw_start">Mulai</string>
|
<string name="radar_cw_start">Mulai</string>
|
||||||
<string name="radar_cw_stop">Berhenti</string>
|
<string name="radar_cw_stop">Berhenti</string>
|
||||||
<string name="radar_cw_reset">Bersihkan</string>
|
<string name="radar_cw_reset">Bersihkan</string>
|
||||||
|
<string name="radar_cw_tone">Nada %1$d Hz</string>
|
||||||
|
<string name="radar_cw_waiting">Menunggu sinyal…</string>
|
||||||
<string name="map_prev">Sebelumnya</string>
|
<string name="map_prev">Sebelumnya</string>
|
||||||
<string name="map_next">Berikutnya</string>
|
<string name="map_next">Berikutnya</string>
|
||||||
<string name="map_copyright" translatable="false">© Kontributor OpenStreetMap</string>
|
<string name="map_copyright" translatable="false">© Kontributor OpenStreetMap</string>
|
||||||
|
|||||||
@@ -92,11 +92,11 @@
|
|||||||
\n\nPerbarui database setidaknya seminggu sekali untuk prediksi yang akurat.</string>
|
\n\nPerbarui database setidaknya seminggu sekali untuk prediksi yang akurat.</string>
|
||||||
<string name="pass_whatsnew_title" translatable="false">Apa yang baru di Look4Sat</string>
|
<string name="pass_whatsnew_title" translatable="false">Apa yang baru di Look4Sat</string>
|
||||||
<string name="pass_whatsnew_message" translatable="false">
|
<string name="pass_whatsnew_message" translatable="false">
|
||||||
* BARU: Status satelit AMSAT kini menggunakan API resmi AMSAT (v1 JSON, lebih cepat dan andal)
|
* BARU: Decoder CW kini memakai jaringan saraf DeepCW — jauh lebih baik menafsir Morse sinyal lemah
|
||||||
* Halaman status AMSAT kini mendukung layar penuh (tombol refresh dan waktu selalu dapat diakses)
|
* BARU: Riwayat CW tersimpan permanen (teks tidak lagi hilang)
|
||||||
* WaveLog: pencarian grid lawan QRZ (dialog ⚙ tempel Cookie + uji query)
|
* Waterfall CW: skema warna inferno baru dengan gradasi halus
|
||||||
* WaveLog: tombol perbarui daftar satelit LoTW (112 satelit bawaan)
|
* Dukungan 64-bit (arm64) dipulihkan
|
||||||
* WaveLog: laporan sinyal RST 59/59 untuk QSO (v1 dan v2)
|
* Perbaikan: jeda/lanjut tidak lagi menampilkan galat "gagal menafsir" yang keliru
|
||||||
</string>
|
</string>
|
||||||
<string name="radar_back">Kembali</string>
|
<string name="radar_back">Kembali</string>
|
||||||
<string name="radar_notify">Beri tahu</string>
|
<string name="radar_notify">Beri tahu</string>
|
||||||
@@ -131,6 +131,8 @@
|
|||||||
<string name="radar_cw_start">Mulai</string>
|
<string name="radar_cw_start">Mulai</string>
|
||||||
<string name="radar_cw_stop">Berhenti</string>
|
<string name="radar_cw_stop">Berhenti</string>
|
||||||
<string name="radar_cw_reset">Bersihkan</string>
|
<string name="radar_cw_reset">Bersihkan</string>
|
||||||
|
<string name="radar_cw_tone">Nada %1$d Hz</string>
|
||||||
|
<string name="radar_cw_waiting">Menunggu sinyal…</string>
|
||||||
<string name="map_prev">Sebelumnya</string>
|
<string name="map_prev">Sebelumnya</string>
|
||||||
<string name="map_next">Berikutnya</string>
|
<string name="map_next">Berikutnya</string>
|
||||||
<string name="map_copyright" translatable="false">© Kontributor OpenStreetMap</string>
|
<string name="map_copyright" translatable="false">© Kontributor OpenStreetMap</string>
|
||||||
|
|||||||
@@ -97,11 +97,11 @@
|
|||||||
\n\nDoğru tahminler alabilmek için veritabanını en az haftada bir güncelleyin.</string>
|
\n\nDoğru tahminler alabilmek için veritabanını en az haftada bir güncelleyin.</string>
|
||||||
<string name="pass_whatsnew_title" translatable="false">What\'s new in Look4Sat</string>
|
<string name="pass_whatsnew_title" translatable="false">What\'s new in Look4Sat</string>
|
||||||
<string name="pass_whatsnew_message" translatable="false">
|
<string name="pass_whatsnew_message" translatable="false">
|
||||||
* YENİ: AMSAT uydu durumu resmi AMSAT API kullanıyor (v1 JSON, daha hızlı ve güvenilir)
|
* YENİ: CW çözücü artık DeepCW sinir ağını kullanıyor — zayıf sinyal Morse çözümü çok daha iyi
|
||||||
* AMSAT durum sayfası tam ekran cihazlara uyumlu (yenileme düğmesi ve saat her zaman erişilebilir)
|
* YENİ: CW çözüm geçmişi kalıcı olarak saklanır (metin artık kaybolmaz)
|
||||||
* WaveLog: QRZ karşı grid sorgulama (⚙ diyalogda Cookie yapıştır + test sorgusu)
|
* CW şelale: yeni inferno renk şeması ve yumuşak geçişler
|
||||||
* WaveLog: LoTW uydu listesi güncelleme düğmesi (112 uydu yerleşik)
|
* 64-bit (arm64) desteği geri geldi
|
||||||
* WaveLog: RST 59/59 sinyal raporu (v1 ve v2)
|
* Düzeltme: duraklat/devam artık hatalı "çözümleme başarısız" mesajı göstermiyor
|
||||||
</string>
|
</string>
|
||||||
|
|
||||||
<!-- Radar screen -->
|
<!-- Radar screen -->
|
||||||
@@ -296,5 +296,7 @@
|
|||||||
\n• BA7OPF (pass matching feature)
|
\n• BA7OPF (pass matching feature)
|
||||||
\n• BG7NTA</string>
|
\n• BG7NTA</string>
|
||||||
<string name="prefs_outro_license">Bu uygulama herhangi bir garanti sunmaz</string>
|
<string name="prefs_outro_license">Bu uygulama herhangi bir garanti sunmaz</string>
|
||||||
|
<string name="radar_cw_tone">Ton %1$d Hz</string>
|
||||||
|
<string name="radar_cw_waiting">Sinyal bekleniyor…</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -87,11 +87,11 @@
|
|||||||
<string name="pass_welcome_message">请务必在设置中通过GPS、经纬度或QTH定位您的位置\n建议至少每周更新一次数据库,以确保预测结果的准确性</string>
|
<string name="pass_welcome_message">请务必在设置中通过GPS、经纬度或QTH定位您的位置\n建议至少每周更新一次数据库,以确保预测结果的准确性</string>
|
||||||
<string name="pass_whatsnew_title">Look4Sat Pro 更新内容</string>
|
<string name="pass_whatsnew_title">Look4Sat Pro 更新内容</string>
|
||||||
<string name="pass_whatsnew_message" translatable="false">
|
<string name="pass_whatsnew_message" translatable="false">
|
||||||
* 新: AMSAT 卫星状态改用官方 AMSAT API(v1 JSON,更快更稳)
|
* 新: CW 解码改用 DeepCW 神经网络,弱信号摩尔斯码解码率大幅提升
|
||||||
* AMSAT 状态页适配全面屏(刷新按钮和更新时间始终可点)
|
* 新: CW 解码历史永久保留(文字不再消失)
|
||||||
* WaveLog: QRZ 对方网格查询(⚙ 弹窗粘贴 Cookie + 测试查询)
|
* CW 瀑布图: 全新 inferno 配色与平滑渐变
|
||||||
* WaveLog: LoTW 卫星列表更新按钮(内置 112 颗卫星)
|
* 恢复 64 位(arm64)支持
|
||||||
* WaveLog: 信号报告 59/59(v1 和 v2)
|
* 修复: 暂停/恢复不再误报"解码失败"
|
||||||
</string>
|
</string>
|
||||||
|
|
||||||
<!-- Radar screen -->
|
<!-- Radar screen -->
|
||||||
@@ -123,6 +123,8 @@
|
|||||||
<string name="radar_cw_start">开始</string>
|
<string name="radar_cw_start">开始</string>
|
||||||
<string name="radar_cw_stop">停止</string>
|
<string name="radar_cw_stop">停止</string>
|
||||||
<string name="radar_cw_reset">清空</string>
|
<string name="radar_cw_reset">清空</string>
|
||||||
|
<string name="radar_cw_tone">音调 %1$d Hz</string>
|
||||||
|
<string name="radar_cw_waiting">等待信号…</string>
|
||||||
|
|
||||||
<!-- Map screen -->
|
<!-- Map screen -->
|
||||||
<string name="map_prev">上一个</string>
|
<string name="map_prev">上一个</string>
|
||||||
|
|||||||
@@ -99,11 +99,11 @@
|
|||||||
\n\nPlease update the database at least weekly to get accurate predictions.</string>
|
\n\nPlease update the database at least weekly to get accurate predictions.</string>
|
||||||
<string name="pass_whatsnew_title" translatable="false">What\'s new in Look4Sat</string>
|
<string name="pass_whatsnew_title" translatable="false">What\'s new in Look4Sat</string>
|
||||||
<string name="pass_whatsnew_message" translatable="false">
|
<string name="pass_whatsnew_message" translatable="false">
|
||||||
* NEW: AMSAT satellite status now uses the official AMSAT API (v1 JSON, faster and more reliable)
|
* NEW: CW decoder now uses the DeepCW neural network — far better weak-signal Morse decoding
|
||||||
* AMSAT status page fits edge-to-edge screens (refresh button and update time always reachable)
|
* NEW: CW decode history is kept permanently (text no longer disappears)
|
||||||
* WaveLog: QRZ counterpart grid lookup (gear dialog > paste QRZ cookies > test query)
|
* CW waterfall: new inferno colour scheme with smooth gradients
|
||||||
* WaveLog: LoTW satellite list refresh button (112 satellites built in)
|
* Restored 64-bit (arm64) support
|
||||||
* WaveLog: RST 59/59 signal reports for QSOs (v1 and v2)
|
* Fixed: pause/resume no longer shows a spurious "decode failed" error
|
||||||
</string>
|
</string>
|
||||||
|
|
||||||
<!-- Radar screen -->
|
<!-- Radar screen -->
|
||||||
@@ -141,6 +141,8 @@
|
|||||||
<string name="radar_cw_start">Start</string>
|
<string name="radar_cw_start">Start</string>
|
||||||
<string name="radar_cw_stop">Stop</string>
|
<string name="radar_cw_stop">Stop</string>
|
||||||
<string name="radar_cw_reset">Clear</string>
|
<string name="radar_cw_reset">Clear</string>
|
||||||
|
<string name="radar_cw_tone">Tone %1$d Hz</string>
|
||||||
|
<string name="radar_cw_waiting">Waiting for signal…</string>
|
||||||
|
|
||||||
<!-- Map screen -->
|
<!-- Map screen -->
|
||||||
<string name="map_prev">Prev</string>
|
<string name="map_prev">Prev</string>
|
||||||
@@ -312,5 +314,6 @@
|
|||||||
\n* BG7NTA
|
\n* BG7NTA
|
||||||
</string>
|
</string>
|
||||||
<string name="prefs_outro_license">The app comes with no warranty</string>
|
<string name="prefs_outro_license">The app comes with no warranty</string>
|
||||||
|
<string name="prefs_outro_deepcw" translatable="false">CW decoding uses the DeepCW model by e04, licensed under AGPL-3.0-only.\nhttps://github.com/e04/deepcw-engine</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
# DeepCW Decoder — module documentation
|
||||||
|
|
||||||
|
The `feature:cw` module decodes Morse code (CW) with the DeepCW neural model
|
||||||
|
instead of a classical DSP detector. This document records the architecture,
|
||||||
|
the model provenance, the pitfalls we hit on real devices, and the verification
|
||||||
|
results. It exists so the next person does not have to re-derive any of it.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
microphone (44.1 kHz PCM float)
|
||||||
|
│ AudioCapture.audioFlow() → ~100 ms chunks
|
||||||
|
▼
|
||||||
|
CwDecodeScreen (feature:cw, pure Compose)
|
||||||
|
│ decoder.processBuffer(chunk) waterfall.pushSamples(chunk)
|
||||||
|
▼ ▼
|
||||||
|
CwDeepDecoder (core:data) CwWaterfallState (feature:cw)
|
||||||
|
resample → 3200 Hz resample → 3200 Hz
|
||||||
|
CwDeepBuffer (20 s rolling) CwDeepSpectrogram.compute
|
||||||
|
CwDeepSpectrogram.compute → rolling spectrogram history
|
||||||
|
ONNX Runtime session.run → Canvas waterfall
|
||||||
|
CwCtcDecoder.greedy (400–1200 Hz band, same magnitudes
|
||||||
|
→ decodedText StateFlow the model sees)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why the split.** `core:domain` must stay pure Kotlin/JVM (KMP migration
|
||||||
|
headroom, per `AGENTS.md`), so the ONNX Runtime dependency — which ships native
|
||||||
|
libraries — lives in `core:data` behind the `ICwDecoder` interface. The
|
||||||
|
spectrogram front-end (`CwDeepSpectrogram`) and CTC collapse (`CwCtcDecoder`)
|
||||||
|
are pure Kotlin and live in `core:domain`, where they are unit-tested against
|
||||||
|
the Python reference with golden vectors.
|
||||||
|
|
||||||
|
**Streaming model, not incremental.** DeepCW is a whole-utterance CTC model. It
|
||||||
|
rewrites earlier characters as more context arrives, so incremental appending is
|
||||||
|
wrong. The decoder keeps a rolling buffer capped at 20 s and re-runs the whole
|
||||||
|
window every 1.5 s, replacing the displayed text outright. 20 s is the smallest
|
||||||
|
window that hits 0 % CER on the reference clip *and* the largest that keeps
|
||||||
|
inference comfortably faster than real time (measured ~14× headroom on a server
|
||||||
|
CPU; timed per-window on device and reported via `lastInferenceMs`).
|
||||||
|
|
||||||
|
## Model
|
||||||
|
|
||||||
|
| | fp32 (original) | int8 (shipped) |
|
||||||
|
|---|---|---|
|
||||||
|
| Size | 15,139,839 bytes | 4,248,808 bytes |
|
||||||
|
| Derivation | — | `quantize_dynamic` (weights → QUInt8, activations float32) |
|
||||||
|
| Input | `spectrogram` [1,1,T,65] float32 | unchanged |
|
||||||
|
| Output | `log_probs` [1,T,42] float32 | unchanged |
|
||||||
|
| In APK | no (available as a release asset) | yes (`assets/deepcw/model.onnx`) |
|
||||||
|
|
||||||
|
The int8 model ships inside the APK: it is ~4× smaller and measurably identical
|
||||||
|
to fp32 on synthetic CW at SNR ≥ −4 dB (both degrade together below that). The
|
||||||
|
fp32 model is published as a separate release asset for anyone who wants the
|
||||||
|
highest-fidelity reference. Both are AGPL-3.0-only — see
|
||||||
|
[`licenses/NOTICE.md`](licenses/NOTICE.md) for provenance, commit SHA and hashes.
|
||||||
|
|
||||||
|
Audio must be packaged **uncompressed** (`noCompress += "onnx"` in the app
|
||||||
|
module): ONNX Runtime mmap's assets and refuses compressed ones.
|
||||||
|
|
||||||
|
## Pitfalls fixed on real devices (release-only)
|
||||||
|
|
||||||
|
1. **R8 minification breaks the JNI binding.** `isMinifyEnabled = true` in the
|
||||||
|
convention plugin, but `onnxruntime-android` ships **no** consumer ProGuard
|
||||||
|
rules, so `ai.onnxruntime.*` got renamed and native code crashed with no Java
|
||||||
|
stack trace. Fixed with
|
||||||
|
`-keep class ai.onnxruntime.** { *; }` in `app/proguard-rules.pro`.
|
||||||
|
Symptom was the exact "flash to home screen, empty crash log" we chased.
|
||||||
|
2. **Model load in `init{}`** took down the whole composable on failure; moved
|
||||||
|
to lazy `ensureLoaded()` with an `errorMessage` path.
|
||||||
|
3. **Default thread count** saturated all cores and starved audio capture/UI;
|
||||||
|
capped `setIntraOpNumThreads` to `cores−1` (max 4).
|
||||||
|
4. **Compose snapshot state written from the audio thread** crashes at runtime
|
||||||
|
with no log from business-class instrumentation; the waterfall now uses a
|
||||||
|
`MutableStateFlow` revision counter, and both the row deque and the pending
|
||||||
|
buffer are lock-guarded.
|
||||||
|
5. **`LaunchedEffect { launch { collect() } }`** leaked a second `AudioRecord`
|
||||||
|
when toggling; collection now runs directly in the effect body keyed on
|
||||||
|
`(isListening, permissionGranted)`.
|
||||||
|
|
||||||
|
Diagnostics (no-adb): `MainApplication` already writes uncaught exceptions to
|
||||||
|
`files/crash_log.txt`. The decoder additionally persists load/inference failures
|
||||||
|
to `files/deepcw_load_error.txt` / `files/deepcw_infer_error.txt` and step
|
||||||
|
markers to `files/probe_cw.txt` (`load_begin → load_session_ok → infer_begin →
|
||||||
|
infer_done`).
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- **Golden vectors**: `CwDeepSpectrogramTest` + `CwDeepGoldenVectorTest` pin the
|
||||||
|
Kotlin spectrogram to the Python reference across 5005 values (tolerance
|
||||||
|
1e-4). 79 domain tests pass.
|
||||||
|
- **End-to-end on server**: real 20 s noisy CW audio → resample → spectrogram →
|
||||||
|
ONNX → greedy CTC reproduces `CQ CQ DE BG7NTA BG7NTA K 5NN TU 73`
|
||||||
|
character-for-character (~1 s inference, fp32).
|
||||||
|
- **On device (release APK)**: decodes 40 WPM CW at high accuracy. Some UI
|
||||||
|
latency is expected on the first decode cycle (model load + first inference);
|
||||||
|
sustained decoding stays real-time.
|
||||||
@@ -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 {
|
||||||
@@ -7,19 +6,8 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.rtbishop.look4sat.feature.cw"
|
namespace = "com.rtbishop.look4sat.feature.cw"
|
||||||
compileOptions {
|
|
||||||
encoding = "UTF-8"
|
|
||||||
}
|
|
||||||
// 照搬 Morse Expert 1.15: native 解码器仅 armeabi-v7a
|
|
||||||
defaultConfig {
|
|
||||||
ndk {
|
|
||||||
abiFilters += listOf("armeabi-v7a")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CW 类保持规则(JNI 按类名注册 + 照搬混淆类保逻辑), 由 app 的 R8 消费
|
|
||||||
// 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 +17,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"
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,661 @@
|
|||||||
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU Affero General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works, specifically designed to ensure
|
||||||
|
cooperation with the community in the case of network server software.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
our General Public Licenses are intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
Developers that use our General Public Licenses protect your rights
|
||||||
|
with two steps: (1) assert copyright on the software, and (2) offer
|
||||||
|
you this License which gives you legal permission to copy, distribute
|
||||||
|
and/or modify the software.
|
||||||
|
|
||||||
|
A secondary benefit of defending all users' freedom is that
|
||||||
|
improvements made in alternate versions of the program, if they
|
||||||
|
receive widespread use, become available for other developers to
|
||||||
|
incorporate. Many developers of free software are heartened and
|
||||||
|
encouraged by the resulting cooperation. However, in the case of
|
||||||
|
software used on network servers, this result may fail to come about.
|
||||||
|
The GNU General Public License permits making a modified version and
|
||||||
|
letting the public access it on a server without ever releasing its
|
||||||
|
source code to the public.
|
||||||
|
|
||||||
|
The GNU Affero General Public License is designed specifically to
|
||||||
|
ensure that, in such cases, the modified source code becomes available
|
||||||
|
to the community. It requires the operator of a network server to
|
||||||
|
provide the source code of the modified version running there to the
|
||||||
|
users of that server. Therefore, public use of a modified version, on
|
||||||
|
a publicly accessible server, gives the public access to the source
|
||||||
|
code of the modified version.
|
||||||
|
|
||||||
|
An older license, called the Affero General Public License and
|
||||||
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
|
this license.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, if you modify the
|
||||||
|
Program, your modified version must prominently offer all users
|
||||||
|
interacting with it remotely through a computer network (if your version
|
||||||
|
supports such interaction) an opportunity to receive the Corresponding
|
||||||
|
Source of your version by providing access to the Corresponding Source
|
||||||
|
from a network server at no charge, through some standard or customary
|
||||||
|
means of facilitating copying of software. This Corresponding Source
|
||||||
|
shall include the Corresponding Source for any work covered by version 3
|
||||||
|
of the GNU General Public License that is incorporated pursuant to the
|
||||||
|
following paragraph.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the work with which it is combined will remain governed by version
|
||||||
|
3 of the GNU General Public License.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU Affero General Public License from time to time. Such new versions
|
||||||
|
will be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU Affero General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU Affero General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU Affero General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If your software can interact with users remotely through a computer
|
||||||
|
network, you should also make sure that it provides a way for users to
|
||||||
|
get its source. For example, if your program is a web application, its
|
||||||
|
interface could display a "Source" link that leads users to an archive
|
||||||
|
of the code. There are many ways you could offer source, and different
|
||||||
|
solutions will be better for different programs; see section 13 for the
|
||||||
|
specific requirements.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
@@ -0,0 +1,674 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
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/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# Third-Party Notices — CW Decode Module
|
||||||
|
|
||||||
|
## DeepCW neural CW decoding model
|
||||||
|
|
||||||
|
The CW (Morse code) decoder in this module performs inference with a neural
|
||||||
|
network model obtained from the DeepCW project.
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| **Component** | `src/main/assets/deepcw/model.onnx` and `model.onnx.json` |
|
||||||
|
| **Upstream project** | DeepCW / deepcw-engine |
|
||||||
|
| **Source repository** | https://github.com/e04/deepcw-engine |
|
||||||
|
| **Author / copyright** | e04 |
|
||||||
|
| **License** | GNU Affero General Public License v3.0 only (AGPL-3.0-only) |
|
||||||
|
| **License text** | [`DeepCW-AGPL-3.0.txt`](DeepCW-AGPL-3.0.txt) |
|
||||||
|
| **Obtained at commit** | `8e264d243bbd4467bd19f3f28292219405b47e0e` |
|
||||||
|
| **Original file size** | 15,139,839 bytes |
|
||||||
|
| **Original SHA-256** | `ef120799457bca042d4690944f0faf93268eb4654e7f50f28784ad63bdc1fe02` |
|
||||||
|
| **Derived file size** | 4,248,808 bytes |
|
||||||
|
| **Derived SHA-256** | `cd48259be0ea8c30ecbfff4a718644f361cb27b9228b030771b0c94756dcab98` |
|
||||||
|
| **Derivation** | Dynamic int8 quantization (weights → QUInt8, activations stay float32) via `onnxruntime.quantization.quantize_dynamic`. Input/output names, shapes and dtypes are unchanged. Measured CER on synthetic CW audio is identical to the fp32 model at SNR >= -4 dB; at -6/-8 dB both models degrade similarly. |
|
||||||
|
|
||||||
|
Related upstream repositories by the same author (not vendored here):
|
||||||
|
|
||||||
|
- https://github.com/e04/web-deep-cw-decoder — reference web application
|
||||||
|
- https://github.com/e04/HamNoise — neural noise reduction (not used)
|
||||||
|
|
||||||
|
### License compatibility
|
||||||
|
|
||||||
|
Look4Sat is licensed under the GNU General Public License v3.0 or later
|
||||||
|
(GPL-3.0-or-later). The DeepCW model is licensed under AGPL-3.0-only.
|
||||||
|
|
||||||
|
Section 13 of the GPL version 3 expressly permits combining GPL-3.0 covered
|
||||||
|
work with AGPL-3.0 covered work; the resulting combination may be conveyed,
|
||||||
|
with the AGPL's additional network-interaction requirement applying to the
|
||||||
|
AGPL-covered portion. Accordingly:
|
||||||
|
|
||||||
|
- The Look4Sat source code remains under GPL-3.0-or-later.
|
||||||
|
- The DeepCW model remains under AGPL-3.0-only.
|
||||||
|
- Distributions of the combined application are accompanied by complete
|
||||||
|
corresponding source, satisfying both licenses.
|
||||||
|
|
||||||
|
### AGPL section 13 (network interaction)
|
||||||
|
|
||||||
|
Inference runs entirely on the local device via ONNX Runtime. The application
|
||||||
|
does not offer the model's functionality to users interacting with it remotely
|
||||||
|
over a network, so the additional network-source-offer requirement of AGPL-3.0
|
||||||
|
section 13 is not triggered by this usage. The complete corresponding source
|
||||||
|
for both the application and the vendored model remains publicly available at
|
||||||
|
the repository hosting this file.
|
||||||
|
|
||||||
|
### Reproducing the vendored files
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1) Fetch the original fp32 model
|
||||||
|
SHA=8e264d243bbd4467bd19f3f28292219405b47e0e
|
||||||
|
curl -sLO https://raw.githubusercontent.com/e04/deepcw-engine/$SHA/model.onnx
|
||||||
|
curl -sLO https://raw.githubusercontent.com/e04/deepcw-engine/$SHA/model.onnx.json
|
||||||
|
curl -sL -o DeepCW-AGPL-3.0.txt \
|
||||||
|
https://raw.githubusercontent.com/e04/deepcw-engine/$SHA/LICENSE
|
||||||
|
sha256sum model.onnx
|
||||||
|
# expected: ef120799457bca042d4690944f0faf93268eb4654e7f50f28784ad63bdc1fe02
|
||||||
|
|
||||||
|
# 2) Reproduce the int8 quantization this repository ships
|
||||||
|
python - <<'PY'
|
||||||
|
from onnxruntime.quantization import quantize_dynamic, QuantType
|
||||||
|
quantize_dynamic("model.onnx", "model_int8.onnx", weight_type=QuantType.QUInt8)
|
||||||
|
PY
|
||||||
|
sha256sum model_int8.onnx
|
||||||
|
# expected: cd48259be0ea8c30ecbfff4a718644f361cb27b9228b030771b0c94756dcab98
|
||||||
|
# then copy model_int8.onnx over assets/deepcw/model.onnx
|
||||||
|
```
|
||||||
|
|
||||||
|
## ONNX Runtime
|
||||||
|
|
||||||
|
Inference engine: `com.microsoft.onnxruntime:onnxruntime-android`, licensed
|
||||||
|
under the MIT License. Consumed as a published Maven artifact; not vendored in
|
||||||
|
this repository.
|
||||||
Vendored
+2
-31
@@ -1,31 +1,2 @@
|
|||||||
# Look4Sat Pro — CW 解码模块(照搬 Morse Expert 1.15)混淆规则
|
# CW 模块无 native 代码、无按类名注册的 JNI,因此不需要任何 keep 规则。
|
||||||
# 所有 CW 类保持原名: pas.* 通过 RegisterNatives 按类名注册 JNI(混淆即崩);
|
# DeepCW 走 ONNX Runtime(自带 consumer proguard 规则),前后处理为普通 Kotlin。
|
||||||
# 其余为照搬的混淆名类(命名已无混淆意义, 保持以保逻辑完整)。
|
|
||||||
|
|
||||||
# JNI 引擎(RegisterNatives 按类名/方法名查表)
|
|
||||||
-keep class pas.** { *; }
|
|
||||||
|
|
||||||
# 照搬的解码/UI 类(全部保持)
|
|
||||||
-keep class com.ve3nea.morse_expert.** { *; }
|
|
||||||
-keep class H2.** { *; }
|
|
||||||
-keep class I2.b { *; }
|
|
||||||
-keep class J2.** { *; }
|
|
||||||
-keep class k3.** { *; }
|
|
||||||
-keep class i3.** { *; }
|
|
||||||
-keep class g3.** { *; }
|
|
||||||
-keep class j3.** { *; }
|
|
||||||
-keep class s.** { *; }
|
|
||||||
-keep class d1.AbstractC1518b { *; }
|
|
||||||
-keep class B0.b { *; }
|
|
||||||
-keep class B.RunnableC0001b { *; }
|
|
||||||
-keep class D.n { *; }
|
|
||||||
-keep class E2.g { *; }
|
|
||||||
-keep class F2.a { *; }
|
|
||||||
-keep class I0.d { *; }
|
|
||||||
-keep class K1.a { *; }
|
|
||||||
-keep class j1.C1646n { *; }
|
|
||||||
|
|
||||||
# sun.misc stub(Android 无此类, 仅编译期; 防止 R8 异常处理)
|
|
||||||
-dontwarn sun.misc.**
|
|
||||||
-keep class sun.misc.Unsafe { *; }
|
|
||||||
-keep class sun.misc.Cleaner { *; }
|
|
||||||
Binary file not shown.
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"chars": [
|
||||||
|
",",
|
||||||
|
".",
|
||||||
|
"/",
|
||||||
|
"0",
|
||||||
|
"1",
|
||||||
|
"2",
|
||||||
|
"3",
|
||||||
|
"4",
|
||||||
|
"5",
|
||||||
|
"6",
|
||||||
|
"7",
|
||||||
|
"8",
|
||||||
|
"9",
|
||||||
|
"?",
|
||||||
|
"A",
|
||||||
|
"B",
|
||||||
|
"C",
|
||||||
|
"D",
|
||||||
|
"E",
|
||||||
|
"F",
|
||||||
|
"G",
|
||||||
|
"H",
|
||||||
|
"I",
|
||||||
|
"J",
|
||||||
|
"K",
|
||||||
|
"L",
|
||||||
|
"M",
|
||||||
|
"N",
|
||||||
|
"O",
|
||||||
|
"P",
|
||||||
|
"Q",
|
||||||
|
"R",
|
||||||
|
"S",
|
||||||
|
"T",
|
||||||
|
"U",
|
||||||
|
"V",
|
||||||
|
"W",
|
||||||
|
"X",
|
||||||
|
"Y",
|
||||||
|
"Z",
|
||||||
|
" "
|
||||||
|
],
|
||||||
|
"blank_index": 41,
|
||||||
|
"sample_rate": 3200,
|
||||||
|
"fft_length": 256,
|
||||||
|
"hop_length": 48,
|
||||||
|
"spectrogram_min_freq_hz": 400.0,
|
||||||
|
"spectrogram_max_freq_hz": 1200.0,
|
||||||
|
"spectrogram_frequency_bins": 65,
|
||||||
|
"normalization": "log1p",
|
||||||
|
"onnx_input_name": "spectrogram",
|
||||||
|
"onnx_output_name": "log_probs",
|
||||||
|
"onnx_input_layout": [
|
||||||
|
"batch",
|
||||||
|
"channel",
|
||||||
|
"time",
|
||||||
|
"frequency"
|
||||||
|
],
|
||||||
|
"onnx_output_layout": [
|
||||||
|
"batch",
|
||||||
|
"time",
|
||||||
|
"class"
|
||||||
|
],
|
||||||
|
"channel_count": 1,
|
||||||
|
"num_classes": 42,
|
||||||
|
"onnx_input_dtype": "float32",
|
||||||
|
"onnx_output_dtype": "float32"
|
||||||
|
}
|
||||||
@@ -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,29 +1,28 @@
|
|||||||
/*
|
/*
|
||||||
* 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.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.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.background
|
import androidx.compose.foundation.background
|
||||||
@@ -36,169 +35,233 @@ 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.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.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.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.graphics.Color
|
import androidx.compose.ui.draw.clip
|
||||||
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.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 androidx.core.content.ContextCompat
|
||||||
import com.ve3nea.morse_expert.MainActivity
|
import com.rtbishop.look4sat.core.domain.repository.IContainerProvider
|
||||||
|
import com.rtbishop.look4sat.core.presentation.R as CoreR
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Full-page CW decoder backed by DeepCW.
|
||||||
|
*
|
||||||
|
* Layout follows the DeepCW reference app: waterfall on top, the live line
|
||||||
|
* under it, then the scrolling history. Pause/clear controls and the microphone
|
||||||
|
* permission flow carry over from the previous page.
|
||||||
|
*
|
||||||
|
* There is no settings dialog any more: the model analyses a fixed
|
||||||
|
* 400-1200 Hz window and tracks speed on its own, so there is nothing to tune.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun CwDecodeScreen(navigateUp: () -> Unit = {}) {
|
fun CwDecodeScreen(navigateUp: () -> Unit = {}) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val activity = remember(context) {
|
val container = remember { (context.applicationContext as IContainerProvider).getMainContainer() }
|
||||||
context as? Activity ?: error("CwDecodeScreen must be hosted in an Activity")
|
val decoder = remember { container.provideCwDecoder() }
|
||||||
}
|
val audioCapture = remember { container.provideAudioCapture() }
|
||||||
// Ported controller (plain class, not an Activity); new instance per page entry
|
val waterfall = remember { CwWaterfallState() }
|
||||||
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(
|
||||||
context.checkSelfPermission(Manifest.permission.RECORD_AUDIO) ==
|
ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) ==
|
||||||
PackageManager.PERMISSION_GRANTED
|
PackageManager.PERMISSION_GRANTED
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
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) }
|
|
||||||
|
|
||||||
// Lifecycle: onCreate before onResume; onDispose runs onPause then onDestroy
|
val decodedText by decoder.decodedText.collectAsState()
|
||||||
DisposableEffect(Unit) {
|
val historyText by decoder.historyText.collectAsState()
|
||||||
controller.onCreate(activity, rootView)
|
val estimatedPitch by decoder.estimatedPitch.collectAsState()
|
||||||
controller.onResume()
|
val signalStrength by decoder.signalStrength.collectAsState()
|
||||||
onDispose {
|
val inferenceMs by decoder.lastInferenceMs.collectAsState()
|
||||||
controller.onPause()
|
val errorMessage by decoder.errorMessage.collectAsState()
|
||||||
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 &&
|
val activity = context as? Activity
|
||||||
|
permanentlyDenied = !granted && activity != null &&
|
||||||
!activity.shouldShowRequestPermissionRationale(Manifest.permission.RECORD_AUDIO)
|
!activity.shouldShowRequestPermissionRationale(Manifest.permission.RECORD_AUDIO)
|
||||||
|
if (granted) isListening = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Original tap_back_again_to_close: single Back shows a hint; press again within 2 s to exit
|
// Capture runs only while listening. Keyed on both listening state and
|
||||||
BackHandler {
|
// permission so granting the permission mid-flow restarts collection
|
||||||
if (!controller.handleBackPress()) navigateUp()
|
// (isListening may already be true when the launcher returns).
|
||||||
|
LaunchedEffect(isListening, permissionGranted) {
|
||||||
|
if (!isListening) return@LaunchedEffect
|
||||||
|
if (!permissionGranted) {
|
||||||
|
permissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
|
||||||
|
return@LaunchedEffect
|
||||||
|
}
|
||||||
|
audioCapture.audioFlow().collect { chunk ->
|
||||||
|
decoder.processBuffer(chunk, audioCapture.sampleRate)
|
||||||
|
waterfall.pushSamples(chunk, audioCapture.sampleRate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showSettings) {
|
DisposableEffect(Unit) {
|
||||||
CwSettingsDialog(controller = controller, onDismiss = { showSettings = false })
|
onDispose {
|
||||||
|
// OrtSession holds native memory and must be released explicitly.
|
||||||
|
decoder.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.systemBarsPadding()
|
|
||||||
) {
|
|
||||||
// Original options_menu: Pause/Clear/Save (original icons) + Record/Settings (text buttons)
|
|
||||||
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 = { isListening = false; navigateUp() }) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_baseline_pause_24),
|
painter = painterResource(CoreR.drawable.ic_back),
|
||||||
contentDescription = stringResource(R.string.pause)
|
contentDescription = stringResource(R.string.cw_back)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
IconButton(onClick = { controller.clearDecoded() }) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Icon(
|
Text(
|
||||||
painter = painterResource(R.drawable.ic_baseline_delete_24),
|
text = stringResource(CoreR.string.nav_cw),
|
||||||
contentDescription = stringResource(R.string.clear)
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = if (estimatedPitch != null) {
|
||||||
|
stringResource(R.string.cw_status_tone, estimatedPitch!!.toInt(), inferenceMs)
|
||||||
|
} else {
|
||||||
|
stringResource(R.string.cw_status_listening)
|
||||||
|
},
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
IconButton(onClick = { controller.saveText() }) {
|
IconButton(onClick = { isListening = !isListening }) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_baseline_save_24),
|
painter = painterResource(
|
||||||
contentDescription = stringResource(R.string.save_text)
|
if (isListening) CoreR.drawable.ic_pause else CoreR.drawable.ic_play
|
||||||
|
),
|
||||||
|
contentDescription = stringResource(
|
||||||
|
if (isListening) R.string.cw_pause else R.string.cw_resume
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// recordSignals needs the decode core (f11037F); disabled until initialized
|
IconButton(onClick = { decoder.reset(); waterfall.clear() }) {
|
||||||
TextButton(
|
Icon(
|
||||||
onClick = { if (initialized) controller.recordSignals() },
|
painter = painterResource(CoreR.drawable.ic_delete),
|
||||||
enabled = initialized
|
contentDescription = stringResource(R.string.cw_clear)
|
||||||
) {
|
)
|
||||||
Text(stringResource(R.string.record_signals))
|
|
||||||
}
|
|
||||||
TextButton(onClick = { showSettings = true }) {
|
|
||||||
Text(stringResource(R.string.settings))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Box(modifier = Modifier.weight(1f).fillMaxWidth()) {
|
Box(
|
||||||
AndroidView(
|
modifier = Modifier
|
||||||
factory = { rootView },
|
.fillMaxWidth()
|
||||||
modifier = Modifier.fillMaxSize()
|
.height(150.dp)
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
) {
|
||||||
|
CwWaterfallView(state = waterfall, signalStrength = signalStrength)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = decodedText.takeLast(64).ifEmpty { "…" },
|
||||||
|
fontSize = 20.sp,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
maxLines = 1,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 12.dp, vertical = 6.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.4f))
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = (historyText + decodedText).ifEmpty { "…" },
|
||||||
|
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
|
errorMessage?.let { message ->
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(Color(0x99000000)),
|
.background(MaterialTheme.colorScheme.errorContainer.copy(alpha = 0.9f)),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
Text(
|
||||||
|
text = message,
|
||||||
|
color = MaterialTheme.colorScheme.onErrorContainer,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!permissionGranted) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(MaterialTheme.colorScheme.scrim.copy(alpha = 0.6f)),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.cw_mic_permission),
|
text = stringResource(R.string.cw_mic_permission),
|
||||||
color = Color.White,
|
color = MaterialTheme.colorScheme.inverseOnSurface,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier.padding(horizontal = 24.dp)
|
modifier = Modifier.padding(horizontal = 24.dp)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
Button(onClick = {
|
Button(onClick = {
|
||||||
permissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
|
permissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
|
||||||
}) {
|
}) {
|
||||||
Text(stringResource(R.string.cw_grant_permission))
|
Text(stringResource(R.string.cw_grant_permission))
|
||||||
}
|
}
|
||||||
if (permanentlyDenied) {
|
if (permanentlyDenied) {
|
||||||
// "Never ask again" checked: guide user to system settings
|
|
||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
activity.startActivity(
|
context.startActivity(
|
||||||
Intent(
|
Intent(
|
||||||
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
|
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
|
||||||
Uri.parse("package:${activity.packageName}")
|
Uri.fromParts("package", context.packageName, null)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}) {
|
}) {
|
||||||
@@ -209,5 +272,7 @@ fun CwDecodeScreen(navigateUp: () -> Unit = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
package com.rtbishop.look4sat.feature.cw
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material3.AlertDialog
|
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.RadioButton
|
|
||||||
import androidx.compose.material3.Slider
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.ve3nea.morse_expert.MainActivity
|
|
||||||
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).
|
|
||||||
* 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).
|
|
||||||
* Storage: SharedPreferences(getPackageName() + "_preferences").
|
|
||||||
* OK saves and calls controller.onResume() to take effect at once; Cancel/outside-tap only closes without saving.
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun CwSettingsDialog(controller: MainActivity, onDismiss: () -> Unit) {
|
|
||||||
val activity = controller.mActivity ?: return
|
|
||||||
val prefs = remember(activity) {
|
|
||||||
activity.getSharedPreferences(activity.packageName + "_preferences", Context.MODE_PRIVATE)
|
|
||||||
}
|
|
||||||
|
|
||||||
var messageType by remember {
|
|
||||||
mutableStateOf(prefs.getString(KEY_MESSAGE_TYPE, DEFAULT_MESSAGE_TYPE) ?: DEFAULT_MESSAGE_TYPE)
|
|
||||||
}
|
|
||||||
var fontSize by remember {
|
|
||||||
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(
|
|
||||||
onDismissRequest = onDismiss,
|
|
||||||
title = { Text("CW Settings") },
|
|
||||||
text = {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
.padding(top = 4.dp)
|
|
||||||
) {
|
|
||||||
Text("Message type", 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) {
|
|
||||||
Slider(
|
|
||||||
value = fontSize,
|
|
||||||
onValueChange = { fontSize = it },
|
|
||||||
valueRange = MIN_FONT_SIZE.toFloat()..MAX_FONT_SIZE.toFloat(),
|
|
||||||
steps = MAX_FONT_SIZE - MIN_FONT_SIZE - 1,
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = "${fontSize.roundToInt()}",
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
textAlign = TextAlign.End,
|
|
||||||
modifier = Modifier.width(44.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
HorizontalDivider(Modifier.padding(vertical = 8.dp))
|
|
||||||
|
|
||||||
Text("Colors", style = MaterialTheme.typography.titleSmall)
|
|
||||||
for (i in 0 until 9) {
|
|
||||||
ColorSettingRow(
|
|
||||||
title = I2.b.c[i],
|
|
||||||
value = colorValues[i],
|
|
||||||
onSelect = { selected ->
|
|
||||||
colorValues = colorValues.copyOf().also { it[i] = selected }
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = {
|
|
||||||
val editor = prefs.edit()
|
|
||||||
editor.putString(KEY_MESSAGE_TYPE, messageType)
|
|
||||||
editor.putInt(KEY_TEXT_FONT_SIZE, fontSize.roundToInt())
|
|
||||||
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()
|
|
||||||
}) { Text("OK") }
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = onDismiss) { Text("Cancel") }
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@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) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
/*
|
||||||
|
* 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.feature.cw
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Canvas
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.geometry.Size
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import com.rtbishop.look4sat.core.domain.cw.CwDeepSpectrogram
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rolling spectrogram history for the waterfall display.
|
||||||
|
*
|
||||||
|
* Reuses [CwDeepSpectrogram] so the picture shows exactly the 400-1200 Hz band
|
||||||
|
* and the same magnitudes the model sees — what you look at is what it decodes.
|
||||||
|
*/
|
||||||
|
class CwWaterfallState(private val historyRows: Int = 96) {
|
||||||
|
|
||||||
|
// pushSamples runs on the audio capture thread while snapshot() runs on the
|
||||||
|
// Compose draw thread, so every touch of these two collections is guarded.
|
||||||
|
// ArrayDeque is not thread-safe: concurrent removeFirst()/toList() throws.
|
||||||
|
private val lock = Any()
|
||||||
|
private val rows = ArrayDeque<FloatArray>(historyRows)
|
||||||
|
private val pending = ArrayList<Float>(CwDeepSpectrogram.SAMPLE_RATE)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bumped on every change so Compose knows to redraw.
|
||||||
|
*
|
||||||
|
* A StateFlow, not `mutableIntStateOf`: this is written from the audio
|
||||||
|
* capture thread, and Compose snapshot state must only be mutated from the
|
||||||
|
* composition thread — doing otherwise crashes at runtime.
|
||||||
|
*/
|
||||||
|
private val _revision = MutableStateFlow(0)
|
||||||
|
val revision: StateFlow<Int> = _revision.asStateFlow()
|
||||||
|
|
||||||
|
/** Snapshot for drawing, oldest row first. */
|
||||||
|
fun snapshot(): List<FloatArray> = synchronized(lock) { rows.toList() }
|
||||||
|
|
||||||
|
fun pushSamples(chunk: FloatArray, sampleRate: Int) {
|
||||||
|
if (chunk.isEmpty()) return
|
||||||
|
val resampled = CwDeepSpectrogram.resampleLinear(
|
||||||
|
chunk, sampleRate, CwDeepSpectrogram.SAMPLE_RATE
|
||||||
|
)
|
||||||
|
val audio: FloatArray
|
||||||
|
synchronized(lock) {
|
||||||
|
pending.ensureCapacity(pending.size + resampled.size)
|
||||||
|
for (sample in resampled) pending.add(sample)
|
||||||
|
// Need at least one FFT window before a row can be produced.
|
||||||
|
if (pending.size < CwDeepSpectrogram.FFT_LENGTH) return
|
||||||
|
audio = FloatArray(pending.size) { pending[it] }
|
||||||
|
pending.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FFT outside the lock; only the append below needs exclusivity.
|
||||||
|
val computed = CwDeepSpectrogram.compute(audio)
|
||||||
|
synchronized(lock) {
|
||||||
|
for (row in computed) {
|
||||||
|
if (rows.size >= historyRows) rows.removeFirst()
|
||||||
|
rows.addLast(row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_revision.value += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
synchronized(lock) {
|
||||||
|
rows.clear()
|
||||||
|
pending.clear()
|
||||||
|
}
|
||||||
|
_revision.value += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the waterfall newest-row-last, one pixel column per frequency bin.
|
||||||
|
* Colour ramp is the inferno palette (black -> purple -> orange -> yellow).
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
internal fun CwWaterfallView(
|
||||||
|
state: CwWaterfallState,
|
||||||
|
signalStrength: Float,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val revision by state.revision.collectAsState()
|
||||||
|
|
||||||
|
Canvas(modifier = modifier.fillMaxSize()) {
|
||||||
|
// Touch the revision inside the draw scope so a new spectrum triggers a
|
||||||
|
// redraw; without this read the canvas would only ever render once.
|
||||||
|
@Suppress("UNUSED_EXPRESSION") revision
|
||||||
|
|
||||||
|
drawRect(color = Color(0xFF00060F), size = size)
|
||||||
|
|
||||||
|
val rows = state.snapshot()
|
||||||
|
if (rows.isEmpty()) return@Canvas
|
||||||
|
|
||||||
|
// Scale to the loudest value on screen so quiet signals stay visible.
|
||||||
|
var peak = 0f
|
||||||
|
for (row in rows) for (v in row) if (v > peak) peak = v
|
||||||
|
if (peak <= 0f) return@Canvas
|
||||||
|
|
||||||
|
val rowHeight = size.height / rows.size
|
||||||
|
val binWidth = size.width / CwDeepSpectrogram.FREQUENCY_BINS
|
||||||
|
|
||||||
|
for ((index, row) in rows.withIndex()) {
|
||||||
|
val y = index * rowHeight
|
||||||
|
// Linear interpolation between adjacent bins via a horizontal
|
||||||
|
// gradient removes the blocky "pixel" look of 65 discrete columns.
|
||||||
|
for (bin in 0 until row.size - 1) {
|
||||||
|
val m0 = (row[bin] / peak).coerceIn(0f, 1f)
|
||||||
|
val m1 = (row[bin + 1] / peak).coerceIn(0f, 1f)
|
||||||
|
if (m0 < 0.06f && m1 < 0.06f) continue
|
||||||
|
drawRect(
|
||||||
|
brush = Brush.horizontalGradient(listOf(inferno(m0), inferno(m1))),
|
||||||
|
topLeft = Offset(bin * binWidth, y),
|
||||||
|
size = Size(binWidth + 1f, rowHeight + 1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signalStrength > 0f) {
|
||||||
|
drawRect(
|
||||||
|
color = Color(0xFF4CD964).copy(alpha = 0.8f),
|
||||||
|
topLeft = Offset(0f, size.height - 3f),
|
||||||
|
size = Size(size.width * signalStrength.coerceIn(0f, 1f), 3f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* matplotlib "inferno" colour map, approximated with piecewise-linear stops
|
||||||
|
* (black -> purple -> magenta-red -> orange -> pale yellow). The same palette
|
||||||
|
* used for the static spectrogram illustration, kept for visual consistency.
|
||||||
|
*/
|
||||||
|
private val INFERNO_STOPS = arrayOf(
|
||||||
|
floatArrayOf(0.00f, 0.000f, 0.000f, 0.016f), // black
|
||||||
|
floatArrayOf(0.25f, 0.231f, 0.059f, 0.439f), // deep purple
|
||||||
|
floatArrayOf(0.50f, 0.549f, 0.161f, 0.506f), // magenta
|
||||||
|
floatArrayOf(0.75f, 0.871f, 0.286f, 0.408f), // red-orange
|
||||||
|
floatArrayOf(1.00f, 0.988f, 1.000f, 0.643f) // pale yellow
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun inferno(t: Float): Color {
|
||||||
|
val x = t.coerceIn(0f, 1f)
|
||||||
|
for (i in 0 until INFERNO_STOPS.size - 1) {
|
||||||
|
val a = INFERNO_STOPS[i]
|
||||||
|
val b = INFERNO_STOPS[i + 1]
|
||||||
|
if (x <= b[0]) {
|
||||||
|
val f = (x - a[0]) / (b[0] - a[0])
|
||||||
|
return Color(
|
||||||
|
red = a[1] + (b[1] - a[1]) * f,
|
||||||
|
green = a[2] + (b[2] - a[2]) * f,
|
||||||
|
blue = a[3] + (b[3] - a[3]) * f
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Color(0.988f, 1.0f, 0.643f)
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loaded 100 of 120 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user