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
	 Jordan Sherer
						Jordan Sherer