Initial commit of SCDC compression

This commit is contained in:
Jordan Sherer 2018-09-30 17:17:47 -04:00
parent ec4df58787
commit 188d775b7f
6 changed files with 234325 additions and 21 deletions

View File

@ -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
View 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("");
}

234096
jsc.dat Normal file

File diff suppressed because it is too large Load Diff

28
jsc.h Normal file
View 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

View File

@ -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

View File

@ -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