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