Initial commit of SCDC compression
This commit is contained in:
parent
ec4df58787
commit
188d775b7f
@ -305,6 +305,7 @@ set (wsjtx_CXXSRCS
|
||||
messageaveraging.cpp
|
||||
WsprTxScheduler.cpp
|
||||
varicode.cpp
|
||||
jsc.cpp
|
||||
SelfDestructMessageBox.cpp
|
||||
messagereplydialog.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 <cmath>
|
||||
#include <cinttypes>
|
||||
@ -64,6 +64,7 @@
|
||||
#include "SelfDestructMessageBox.h"
|
||||
#include "messagereplydialog.h"
|
||||
#include "DriftingDateTime.h"
|
||||
#include "jsc.h"
|
||||
|
||||
#include "ui_mainwindow.h"
|
||||
#include "moc_mainwindow.cpp"
|
||||
@ -905,10 +906,6 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
|
||||
beaconTimer.setSingleShot(false);
|
||||
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,
|
||||
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){
|
||||
QMenu * menu = new QMenu(ui->extFreeTextMsgEdit);
|
||||
|
||||
QString selectedCall = callsignSelected();
|
||||
bool missingCallsign = selectedCall.isEmpty();
|
||||
|
||||
restoreAction->setDisabled(m_lastTxMessage.isEmpty());
|
||||
menu->addAction(restoreAction);
|
||||
|
||||
auto savedMenu = menu->addMenu("Saved messages...");
|
||||
buildSavedMessagesMenu(savedMenu);
|
||||
|
||||
auto directedMenu = menu->addMenu(QString("Directed to %1...").arg(selectedCall));
|
||||
directedMenu->setDisabled(missingCallsign);
|
||||
buildQueryMenu(directedMenu, selectedCall);
|
||||
|
||||
auto relayMenu = menu->addMenu("Relay via...");
|
||||
relayMenu->setDisabled(ui->extFreeTextMsgEdit->toPlainText().isEmpty() || m_callActivity.isEmpty());
|
||||
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));
|
||||
ui->tableWidgetCalls->setPalette(p);
|
||||
|
||||
|
||||
|
||||
|
||||
// Don't block beacon's first run...
|
||||
m_lastTxTime = DriftingDateTime::currentDateTimeUtc().addSecs(-300);
|
||||
|
||||
@ -1463,6 +1450,26 @@ void MainWindow::not_GA_warning_message ()
|
||||
}
|
||||
|
||||
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")){
|
||||
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*/){
|
||||
selectedCallTimer.stop();
|
||||
|
||||
on_extFreeTextMsgEdit_currentTextChanged(ui->extFreeTextMsgEdit->toPlainText());
|
||||
|
||||
auto placeholderText = QString("Type your outgoing messages here.");
|
||||
auto selectedCall = callsignSelected();
|
||||
if(!selectedCall.isEmpty()){
|
||||
placeholderText = QString("Type your outgoing directed message to %1 here.").arg(selectedCall);
|
||||
selectedCallTimer.start();
|
||||
}
|
||||
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
|
||||
if (m_config.pwrBandTuneMemory() || m_config.pwrBandTxMemory()) {
|
||||
stopTx();
|
||||
m_pwrBandTuneMemory[curBand] = ui->outAttenuation->value(); // remember our Tune pwr
|
||||
m_PwrBandSetOK = false;
|
||||
ui->outAttenuation->setValue(m_pwrBandTxMemory[curBand].toInt()); // set to Tx pwr
|
||||
|
@ -75,7 +75,8 @@ SOURCES += \
|
||||
APRSISClient.cpp \
|
||||
messagereplydialog.cpp \
|
||||
keyeater.cpp \
|
||||
DriftingDateTime.cpp
|
||||
DriftingDateTime.cpp \
|
||||
jsc.cpp
|
||||
|
||||
HEADERS += qt_helpers.hpp \
|
||||
pimpl_h.hpp pimpl_impl.hpp \
|
||||
@ -102,7 +103,8 @@ HEADERS += qt_helpers.hpp \
|
||||
APRSISClient.h \
|
||||
messagereplydialog.h \
|
||||
keyeater.h \
|
||||
DriftingDateTime.h
|
||||
DriftingDateTime.h \
|
||||
jsc.h
|
||||
|
||||
|
||||
INCLUDEPATH += qmake_only
|
||||
|
Loading…
Reference in New Issue
Block a user