Initial commit of SCDC compression
This commit is contained in:
parent
ec4df58787
commit
188d775b7f
@ -305,6 +305,7 @@ set (wsjtx_CXXSRCS
|
|||||||
messageaveraging.cpp
|
messageaveraging.cpp
|
||||||
WsprTxScheduler.cpp
|
WsprTxScheduler.cpp
|
||||||
varicode.cpp
|
varicode.cpp
|
||||||
|
jsc.cpp
|
||||||
SelfDestructMessageBox.cpp
|
SelfDestructMessageBox.cpp
|
||||||
messagereplydialog.cpp
|
messagereplydialog.cpp
|
||||||
keyeater.cpp
|
keyeater.cpp
|
||||||
|
174
jsc.cpp
Normal file
174
jsc.cpp
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of FT8Call.
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*
|
||||||
|
* (C) 2018 Jordan Sherer <kn4crd@gmail.com> - All Rights Reserved
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
|
||||||
|
#include "jsc.h"
|
||||||
|
#include "varicode.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
|
||||||
|
Codeword codeword(quint32 index, bool separate, quint32 bytesize, quint32 s, quint32 c){
|
||||||
|
QList<Codeword> out;
|
||||||
|
|
||||||
|
quint32 v = ((index % s) << 1) + (int)separate;
|
||||||
|
out.prepend(Varicode::intToBits(v, bytesize + 1));
|
||||||
|
|
||||||
|
quint32 x = index / s;
|
||||||
|
while(x > 0){
|
||||||
|
x -= 1;
|
||||||
|
out.prepend(Varicode::intToBits((x % c) + s, bytesize));
|
||||||
|
x /= c;
|
||||||
|
}
|
||||||
|
|
||||||
|
Codeword word;
|
||||||
|
foreach(auto w, out){
|
||||||
|
word.append(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
return word;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPair<CompressionMap, CompressionList> JSC::loadCompressionTable(QTextStream &stream){
|
||||||
|
CompressionMap out;
|
||||||
|
CompressionList words;
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
while(!stream.atEnd()){
|
||||||
|
// assume that this is in sorted order, that each word is already upper case.
|
||||||
|
auto word = stream.readLine().trimmed();
|
||||||
|
if(out.contains(word)){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
out[word] = index;
|
||||||
|
words.append(word);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 1
|
||||||
|
qStableSort(words.begin(), words.end(), [out](QString const &left, QString const &right){
|
||||||
|
return out[left] < out[right];
|
||||||
|
});
|
||||||
|
qStableSort(words.begin(), words.end(), [](QString const &left, QString const &right){
|
||||||
|
if(left.length() == right.length()){
|
||||||
|
return left < right;
|
||||||
|
}
|
||||||
|
return left.length() < right.length();
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return {out, words};
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<CodewordPair> JSC::compress(CompressionTable table, QString text){
|
||||||
|
QList<CodewordPair> out;
|
||||||
|
|
||||||
|
const quint32 b = 4;
|
||||||
|
const quint32 s = 7;
|
||||||
|
const quint32 c = pow(2, 4) - s;
|
||||||
|
|
||||||
|
auto map = table.first;
|
||||||
|
auto list = table.second;
|
||||||
|
|
||||||
|
foreach(QString w, text.split(" ", QString::SkipEmptyParts)){
|
||||||
|
if(map.contains(w)){
|
||||||
|
auto index = map[w];
|
||||||
|
out.append({ codeword(index, true, b, s, c), w.length() });
|
||||||
|
} else {
|
||||||
|
// prefix match?
|
||||||
|
while(!w.isEmpty()){
|
||||||
|
bool hasPrefix = false;
|
||||||
|
|
||||||
|
foreach(QString word, list){
|
||||||
|
if(w.startsWith(word)){
|
||||||
|
w = QString(w.mid(word.length()));
|
||||||
|
out.append({ codeword(map[word], w.isEmpty(), b, s, c), word.length()});
|
||||||
|
hasPrefix = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!hasPrefix){
|
||||||
|
// no match...SOL
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString JSC::decompress(CompressionTable table, Codeword const& bitvec){
|
||||||
|
const quint32 b = 4;
|
||||||
|
const quint32 s = 7;
|
||||||
|
const quint32 c = pow(2, b) - s;
|
||||||
|
|
||||||
|
QStringList out;
|
||||||
|
|
||||||
|
quint32 base[8];
|
||||||
|
base[0] = 0;
|
||||||
|
base[1] = s;
|
||||||
|
base[2] = base[1] + s*c;
|
||||||
|
base[3] = base[2] + s*c*c;
|
||||||
|
base[4] = base[3] + s*c*c*c;
|
||||||
|
base[5] = base[4] + s*c*c*c*c;
|
||||||
|
base[6] = base[5] + s*c*c*c*c*c;
|
||||||
|
base[7] = base[6] + s*c*c*c*c*c*c;
|
||||||
|
|
||||||
|
QList<quint64> bytes;
|
||||||
|
QList<int> separators;
|
||||||
|
auto iter = bitvec.begin();
|
||||||
|
while(iter != bitvec.end()){
|
||||||
|
quint64 byte = Varicode::bitsToInt(iter, 4);
|
||||||
|
iter += 4;
|
||||||
|
bytes.append(byte);
|
||||||
|
|
||||||
|
if(byte < s){
|
||||||
|
if(*iter){
|
||||||
|
separators.append(bytes.length()-1);
|
||||||
|
}
|
||||||
|
iter += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int start = 0;
|
||||||
|
while(start < bytes.length()){
|
||||||
|
int k = 0;
|
||||||
|
int j = 0;
|
||||||
|
|
||||||
|
while(start + k < bytes.length() && bytes[start + k] >= s){
|
||||||
|
j = j*c + (bytes[start + k] - s);
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
|
||||||
|
j = j*s + bytes[start + k] + base[k];
|
||||||
|
|
||||||
|
out.append(table.first.key(j));
|
||||||
|
if(separators.first() == start + k){
|
||||||
|
out.append(" ");
|
||||||
|
separators.removeFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
start = start + (k + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.join("");
|
||||||
|
}
|
28
jsc.h
Normal file
28
jsc.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#ifndef JSC_H
|
||||||
|
#define JSC_H
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (C) 2018 Jordan Sherer <kn4crd@gmail.com> - All Rights Reserved
|
||||||
|
**/
|
||||||
|
|
||||||
|
#include <QTextStream>
|
||||||
|
#include <QList>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QPair>
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
|
typedef QMap<QString, int> CompressionMap; // Map(Word, I) where I is the word index sorted by frequency
|
||||||
|
typedef QList<QString> CompressionList; // List(Word) where list is sorted
|
||||||
|
typedef QPair<CompressionMap, CompressionList> CompressionTable; // Tuple(Map, List)
|
||||||
|
typedef QPair<QVector<bool>, int> CodewordPair; // Tuple(Codeword, N) where N = number of characters
|
||||||
|
typedef QVector<bool> Codeword;
|
||||||
|
|
||||||
|
class JSC
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static CompressionTable loadCompressionTable(QTextStream &stream);
|
||||||
|
static QList<CodewordPair> compress(CompressionTable table, QString text);
|
||||||
|
static QString decompress(CompressionTable table, Codeword const& bits);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // JSC_H
|
@ -1,4 +1,4 @@
|
|||||||
//---------------------------------------------------------- MainWindow
|
//---------------------------------------------------------- MainWindow
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
@ -64,6 +64,7 @@
|
|||||||
#include "SelfDestructMessageBox.h"
|
#include "SelfDestructMessageBox.h"
|
||||||
#include "messagereplydialog.h"
|
#include "messagereplydialog.h"
|
||||||
#include "DriftingDateTime.h"
|
#include "DriftingDateTime.h"
|
||||||
|
#include "jsc.h"
|
||||||
|
|
||||||
#include "ui_mainwindow.h"
|
#include "ui_mainwindow.h"
|
||||||
#include "moc_mainwindow.cpp"
|
#include "moc_mainwindow.cpp"
|
||||||
@ -905,10 +906,6 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
|
|||||||
beaconTimer.setSingleShot(false);
|
beaconTimer.setSingleShot(false);
|
||||||
connect(&beaconTimer, &QTimer::timeout, this, &MainWindow::checkBeacon);
|
connect(&beaconTimer, &QTimer::timeout, this, &MainWindow::checkBeacon);
|
||||||
|
|
||||||
selectedCallTimer.setSingleShot(true);
|
|
||||||
selectedCallTimer.setInterval(1000*60*60);
|
|
||||||
connect(&selectedCallTimer, &QTimer::timeout, this, &MainWindow::clearCallsignSelected);
|
|
||||||
|
|
||||||
connect(m_wideGraph.data (), SIGNAL(setFreq3(int,int)),this,
|
connect(m_wideGraph.data (), SIGNAL(setFreq3(int,int)),this,
|
||||||
SLOT(setFreq4(int,int)));
|
SLOT(setFreq4(int,int)));
|
||||||
|
|
||||||
@ -1205,19 +1202,12 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
|
|||||||
connect(ui->extFreeTextMsgEdit, &QTableWidget::customContextMenuRequested, this, [this, clearAction2, clearActionAll, restoreAction](QPoint const &point){
|
connect(ui->extFreeTextMsgEdit, &QTableWidget::customContextMenuRequested, this, [this, clearAction2, clearActionAll, restoreAction](QPoint const &point){
|
||||||
QMenu * menu = new QMenu(ui->extFreeTextMsgEdit);
|
QMenu * menu = new QMenu(ui->extFreeTextMsgEdit);
|
||||||
|
|
||||||
QString selectedCall = callsignSelected();
|
|
||||||
bool missingCallsign = selectedCall.isEmpty();
|
|
||||||
|
|
||||||
restoreAction->setDisabled(m_lastTxMessage.isEmpty());
|
restoreAction->setDisabled(m_lastTxMessage.isEmpty());
|
||||||
menu->addAction(restoreAction);
|
menu->addAction(restoreAction);
|
||||||
|
|
||||||
auto savedMenu = menu->addMenu("Saved messages...");
|
auto savedMenu = menu->addMenu("Saved messages...");
|
||||||
buildSavedMessagesMenu(savedMenu);
|
buildSavedMessagesMenu(savedMenu);
|
||||||
|
|
||||||
auto directedMenu = menu->addMenu(QString("Directed to %1...").arg(selectedCall));
|
|
||||||
directedMenu->setDisabled(missingCallsign);
|
|
||||||
buildQueryMenu(directedMenu, selectedCall);
|
|
||||||
|
|
||||||
auto relayMenu = menu->addMenu("Relay via...");
|
auto relayMenu = menu->addMenu("Relay via...");
|
||||||
relayMenu->setDisabled(ui->extFreeTextMsgEdit->toPlainText().isEmpty() || m_callActivity.isEmpty());
|
relayMenu->setDisabled(ui->extFreeTextMsgEdit->toPlainText().isEmpty() || m_callActivity.isEmpty());
|
||||||
buildRelayMenu(relayMenu);
|
buildRelayMenu(relayMenu);
|
||||||
@ -1408,9 +1398,6 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
|
|||||||
p.setColor(QPalette::Inactive, QPalette::Highlight, p.color(QPalette::Active, QPalette::Highlight));
|
p.setColor(QPalette::Inactive, QPalette::Highlight, p.color(QPalette::Active, QPalette::Highlight));
|
||||||
ui->tableWidgetCalls->setPalette(p);
|
ui->tableWidgetCalls->setPalette(p);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Don't block beacon's first run...
|
// Don't block beacon's first run...
|
||||||
m_lastTxTime = DriftingDateTime::currentDateTimeUtc().addSecs(-300);
|
m_lastTxTime = DriftingDateTime::currentDateTimeUtc().addSecs(-300);
|
||||||
|
|
||||||
@ -1463,6 +1450,26 @@ void MainWindow::not_GA_warning_message ()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::initializeDummyData(){
|
void MainWindow::initializeDummyData(){
|
||||||
|
|
||||||
|
QFile file("jsc.dat");
|
||||||
|
if (file.open(QIODevice::ReadOnly | QIODevice::Text)){
|
||||||
|
QTextStream s(&file);
|
||||||
|
|
||||||
|
auto table = JSC::loadCompressionTable(s);
|
||||||
|
|
||||||
|
Codeword bits;
|
||||||
|
auto compressed = JSC::compress(table, "E A T EAT TEA ATE EATTET");
|
||||||
|
foreach(auto pair, compressed){
|
||||||
|
qDebug() << "compressed" << Varicode::bitsToStr(pair.first);
|
||||||
|
bits.append(pair.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "decomressed" << JSC::decompress(table, bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if(!QApplication::applicationName().contains("dummy")){
|
if(!QApplication::applicationName().contains("dummy")){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -7976,15 +7983,12 @@ void MainWindow::on_tableWidgetRXAll_cellDoubleClicked(int row, int col){
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_tableWidgetRXAll_selectionChanged(const QItemSelection &/*selected*/, const QItemSelection &/*deselected*/){
|
void MainWindow::on_tableWidgetRXAll_selectionChanged(const QItemSelection &/*selected*/, const QItemSelection &/*deselected*/){
|
||||||
selectedCallTimer.stop();
|
|
||||||
|
|
||||||
on_extFreeTextMsgEdit_currentTextChanged(ui->extFreeTextMsgEdit->toPlainText());
|
on_extFreeTextMsgEdit_currentTextChanged(ui->extFreeTextMsgEdit->toPlainText());
|
||||||
|
|
||||||
auto placeholderText = QString("Type your outgoing messages here.");
|
auto placeholderText = QString("Type your outgoing messages here.");
|
||||||
auto selectedCall = callsignSelected();
|
auto selectedCall = callsignSelected();
|
||||||
if(!selectedCall.isEmpty()){
|
if(!selectedCall.isEmpty()){
|
||||||
placeholderText = QString("Type your outgoing directed message to %1 here.").arg(selectedCall);
|
placeholderText = QString("Type your outgoing directed message to %1 here.").arg(selectedCall);
|
||||||
selectedCallTimer.start();
|
|
||||||
}
|
}
|
||||||
ui->extFreeTextMsgEdit->setPlaceholderText(placeholderText);
|
ui->extFreeTextMsgEdit->setPlaceholderText(placeholderText);
|
||||||
|
|
||||||
@ -8110,7 +8114,6 @@ void MainWindow::on_tuneButton_clicked (bool checked)
|
|||||||
}
|
}
|
||||||
else { // we're turning off so remember our Tune pwr setting and reset to Tx pwr
|
else { // we're turning off so remember our Tune pwr setting and reset to Tx pwr
|
||||||
if (m_config.pwrBandTuneMemory() || m_config.pwrBandTxMemory()) {
|
if (m_config.pwrBandTuneMemory() || m_config.pwrBandTxMemory()) {
|
||||||
stopTx();
|
|
||||||
m_pwrBandTuneMemory[curBand] = ui->outAttenuation->value(); // remember our Tune pwr
|
m_pwrBandTuneMemory[curBand] = ui->outAttenuation->value(); // remember our Tune pwr
|
||||||
m_PwrBandSetOK = false;
|
m_PwrBandSetOK = false;
|
||||||
ui->outAttenuation->setValue(m_pwrBandTxMemory[curBand].toInt()); // set to Tx pwr
|
ui->outAttenuation->setValue(m_pwrBandTxMemory[curBand].toInt()); // set to Tx pwr
|
||||||
|
@ -75,7 +75,8 @@ SOURCES += \
|
|||||||
APRSISClient.cpp \
|
APRSISClient.cpp \
|
||||||
messagereplydialog.cpp \
|
messagereplydialog.cpp \
|
||||||
keyeater.cpp \
|
keyeater.cpp \
|
||||||
DriftingDateTime.cpp
|
DriftingDateTime.cpp \
|
||||||
|
jsc.cpp
|
||||||
|
|
||||||
HEADERS += qt_helpers.hpp \
|
HEADERS += qt_helpers.hpp \
|
||||||
pimpl_h.hpp pimpl_impl.hpp \
|
pimpl_h.hpp pimpl_impl.hpp \
|
||||||
@ -102,7 +103,8 @@ HEADERS += qt_helpers.hpp \
|
|||||||
APRSISClient.h \
|
APRSISClient.h \
|
||||||
messagereplydialog.h \
|
messagereplydialog.h \
|
||||||
keyeater.h \
|
keyeater.h \
|
||||||
DriftingDateTime.h
|
DriftingDateTime.h \
|
||||||
|
jsc.h
|
||||||
|
|
||||||
|
|
||||||
INCLUDEPATH += qmake_only
|
INCLUDEPATH += qmake_only
|
||||||
|
Loading…
Reference in New Issue
Block a user