diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2aa5ab9..06f9796 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -253,6 +253,8 @@ set (wsjtx_CXXSRCS
   WaveFile.cpp
   AudioDecoder.cpp
   NotificationAudio.cpp
+  ProcessThread.cpp
+  Decoder.cpp
   )
 
 set (wsjt_CXXSRCS
diff --git a/Decoder.cpp b/Decoder.cpp
new file mode 100644
index 0000000..891501a
--- /dev/null
+++ b/Decoder.cpp
@@ -0,0 +1,163 @@
+/**
+ * This file is part of JS8Call.
+ *
+ * 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 .
+ *
+ * (C) 2019 Jordan Sherer  - All Rights Reserved
+ *
+ **/
+
+#include "Decoder.h"
+
+#include "commons.h"
+
+#include 
+
+
+Decoder::Decoder(QObject *parent):
+    QObject(parent)
+{
+}
+
+Decoder::~Decoder(){
+}
+
+//
+void Decoder::lock(){
+    // NOOP
+}
+
+//
+void Decoder::unlock(){
+    // NOOP
+}
+
+//
+Worker* Decoder::createWorker(){
+    auto worker = new Worker();
+    worker->moveToThread(&m_thread);
+    connect(&m_thread, &QThread::finished, worker, &QObject::deleteLater);
+    connect(this, &Decoder::startWorker, worker, &Worker::start);
+    connect(this, &Decoder::quitWorker, worker, &Worker::quit);
+    connect(worker, &Worker::ready, this, &Decoder::ready);
+    return worker;
+}
+
+//
+void Decoder::start(QThread::Priority priority){
+    m_thread.start(priority);
+}
+
+//
+void Decoder::quit(){
+    m_thread.quit();
+}
+
+//
+bool Decoder::wait(){
+    return m_thread.wait();
+}
+
+//
+void Decoder::processStart(QString path, QStringList args){
+    if(m_worker.isNull()){
+        m_worker = createWorker();
+    }
+
+    emit startWorker(path, args);
+}
+
+//
+void Decoder::processReady(QByteArray t){
+    emit ready(t);
+}
+
+//
+void Decoder::processQuit(){
+    emit quitWorker();
+}
+
+////////////////////////////////////////
+//////////////// WORKER ////////////////
+////////////////////////////////////////
+
+//
+Worker::~Worker(){
+}
+
+//
+void Worker::setProcess(QProcess *proc, int msecs){
+    if(!m_proc.isNull()){
+        bool b = m_proc->waitForFinished(msecs);
+        if(!b) m_proc->close();
+        m_proc.reset();
+    }
+
+    if(proc){
+        m_proc.reset(proc);
+    }
+}
+
+//
+void Worker::start(QString path, QStringList args){
+    if(JS8_DEBUG_DECODE) qDebug() << "decoder process starting...";
+
+    auto proc = new QProcess(this);
+
+    connect(proc, &QProcess::readyReadStandardOutput,
+            [this, proc](){
+                while(proc->canReadLine()){
+                    emit ready(proc->readLine());
+                }
+            });
+
+    connect(proc, static_cast (&QProcess::error),
+            [this, proc] (QProcess::ProcessError e) {
+              emit error();
+            });
+
+    connect(proc, static_cast (&QProcess::finished),
+            [this, proc] (int exitCode, QProcess::ExitStatus status) {
+              emit finished();
+            });
+
+
+    auto watcher = new QTimer(this);
+
+    connect(proc, static_cast (&QProcess::finished),
+            [this, watcher] (int /*exitCode*/, QProcess::ExitStatus /*status*/) {
+                watcher->stop();
+            });
+
+    connect(watcher, &QTimer::timeout,
+            [this, proc](){
+                if(JS8_DEBUG_DECODE) qDebug() << "decode process" << proc->state() << "can readline?" << proc->canReadLine();
+            });
+
+    watcher->setInterval(500);
+    watcher->start();
+
+
+    QProcessEnvironment env {QProcessEnvironment::systemEnvironment ()};
+    env.insert ("OMP_STACKSIZE", "4M");
+    proc->setProcessEnvironment (env);
+    proc->start(path, args, QIODevice::ReadWrite | QIODevice::Unbuffered);
+
+    setProcess(proc);
+}
+
+//
+void Worker::quit(){
+    setProcess(nullptr);
+}
diff --git a/Decoder.h b/Decoder.h
new file mode 100644
index 0000000..6dd2756
--- /dev/null
+++ b/Decoder.h
@@ -0,0 +1,71 @@
+#ifndef DECODER_H
+#define DECODER_H
+
+/**
+ * (C) 2019 Jordan Sherer  - All Rights Reserved
+ **/
+
+#include "ProcessThread.h"
+
+#include 
+#include 
+#include 
+#include 
+
+
+class Worker : public QObject{
+    Q_OBJECT
+public:
+    ~Worker();
+public slots:
+    void start(QString path, QStringList args);
+    void quit();
+
+private:
+    void setProcess(QProcess *proc, int msecs=1000);
+
+signals:
+    void ready(QByteArray t);
+    void error();
+    void finished();
+
+private:
+    QScopedPointer m_proc;
+};
+
+
+class Decoder: public QObject
+{
+    Q_OBJECT
+public:
+    Decoder(QObject *parent=nullptr);
+    ~Decoder();
+
+    void lock();
+    void unlock();
+
+private:
+    Worker* createWorker();
+
+public slots:
+    void start(QThread::Priority priority);
+    void quit();
+    bool wait();
+
+    void processStart(QString path, QStringList args);
+    void processReady(QByteArray t);
+    void processQuit();
+
+signals:
+    void startWorker(QString path, QStringList args);
+    void quitWorker();
+
+    void ready(QByteArray t);
+
+private:
+    QPointer m_worker;
+    QThread m_thread;
+};
+
+
+#endif // DECODER_H
diff --git a/ProcessThread.cpp b/ProcessThread.cpp
new file mode 100644
index 0000000..3050c7f
--- /dev/null
+++ b/ProcessThread.cpp
@@ -0,0 +1,46 @@
+/**
+ * This file is part of JS8Call.
+ *
+ * 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 .
+ *
+ * (C) 2019 Jordan Sherer  - All Rights Reserved
+ *
+ **/
+#include "ProcessThread.h"
+
+ProcessThread::ProcessThread(QObject *parent):
+    QThread(parent)
+{
+}
+
+ProcessThread::~ProcessThread(){
+    setProcess(nullptr);
+}
+
+/**
+ * @brief ProcessThread::setProcess
+ * @param proc - process to move to this thread and take ownership
+ */
+void ProcessThread::setProcess(QProcess *proc, int msecs){
+    if(!m_proc.isNull()){
+        bool b = m_proc->waitForFinished(msecs);
+        if(!b) m_proc->close();
+        m_proc.reset();
+    }
+
+    if(proc){
+        proc->moveToThread(this);
+        m_proc.reset(proc);
+    }
+}
diff --git a/ProcessThread.h b/ProcessThread.h
new file mode 100644
index 0000000..e0c578c
--- /dev/null
+++ b/ProcessThread.h
@@ -0,0 +1,29 @@
+#ifndef PROCESSTHREAD_H
+#define PROCESSTHREAD_H
+
+/**
+ * (C) 2019 Jordan Sherer  - All Rights Reserved
+ **/
+
+#include 
+#include 
+#include 
+
+
+class ProcessThread : public QThread
+{
+    Q_OBJECT
+public:
+    ProcessThread(QObject *parent=nullptr);
+    ~ProcessThread();
+
+    void setProcess(QProcess *proc, int msecs=1000);
+    QProcess * process(){
+        return m_proc.data();
+    }
+
+protected:
+    QScopedPointer m_proc;
+};
+
+#endif // PROCESSTHREAD_H
diff --git a/js8call.pro b/js8call.pro
index 924b2aa..722e710 100644
--- a/js8call.pro
+++ b/js8call.pro
@@ -88,7 +88,10 @@ SOURCES += \
     CallsignValidator.cpp \
     AudioDecoder.cpp \
     WaveFile.cpp \
-    WaveUtils.cpp
+    WaveUtils.cpp \
+    ProcessThread.cpp \
+    DecoderThread.cpp \
+    Decoder.cpp
 
 HEADERS  += qt_helpers.hpp \
   pimpl_h.hpp pimpl_impl.hpp \
@@ -127,7 +130,10 @@ HEADERS  += qt_helpers.hpp \
     NotificationAudio.h \
     AudioDecoder.h \
     WaveFile.h \
-    WaveUtils.h
+    WaveUtils.h \
+    ProcessThread.h \
+    DecoderThread.h \
+    Decoder.h
 
 
 INCLUDEPATH += qmake_only
diff --git a/mainwindow.cpp b/mainwindow.cpp
index c8d370a..9cbde48 100644
--- a/mainwindow.cpp
+++ b/mainwindow.cpp
@@ -419,6 +419,8 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
   m_downSampleFactor (downSampleFactor),
   m_audioThreadPriority (QThread::HighPriority),
   m_notificationAudioThreadPriority (QThread::LowPriority),
+  m_decoderThreadPriority (QThread::HighPriority),
+  m_decoder {this},
   m_bandEdited {false},
   m_splitMode {false},
   m_monitoring {false},
@@ -537,7 +539,9 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
 #endif
 
   // decoder queue handler
-  connect(this, &MainWindow::decodedLineReady, this, &MainWindow::processDecodedLine);
+  //connect (&m_decodeThread, &QThread::finished, m_notification, &QObject::deleteLater);
+  //connect(this, &MainWindow::decodedLineReady, this, &MainWindow::processDecodedLine);
+  connect(&m_decoder, &Decoder::ready, this, &MainWindow::processDecodedLine);
 
   on_EraseButton_clicked ();
 
@@ -807,6 +811,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
 
   m_audioThread.start (m_audioThreadPriority);
   m_notificationAudioThread.start(m_notificationAudioThreadPriority);
+  m_decoder.start(m_decoderThreadPriority);
 
 #ifdef WIN32
   if (!m_multiple)
@@ -1561,17 +1566,17 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
 }
 
 void MainWindow::initDecoderSubprocess(){
+    //delete any .quit file that might have been left lying around
+    //since its presence will cause jt9 to exit a soon as we start it
+    //and decodes will hang
     {
-      //delete any .quit file that might have been left lying around
-      //since its presence will cause jt9 to exit a soon as we start it
-      //and decodes will hang
-      QFile quitFile {m_config.temp_dir ().absoluteFilePath (".quit")};
-      while (quitFile.exists ())
+        QFile quitFile {m_config.temp_dir ().absoluteFilePath (".quit")};
+        while (quitFile.exists ())
         {
-          if (!quitFile.remove ())
+            if (!quitFile.remove ())
             {
-              MessageBox::query_message (this, tr ("Error removing \"%1\"").arg (quitFile.fileName ())
-                                         , tr ("Click OK to retry"));
+                MessageBox::query_message (this, tr ("Error removing \"%1\"").arg (quitFile.fileName ())
+                                 , tr ("Click OK to retry"));
             }
         }
     }
@@ -1580,7 +1585,11 @@ void MainWindow::initDecoderSubprocess(){
     if(JS8_DEBUG_DECODE) qDebug() << "decoder lock create";
     QFile {m_config.temp_dir ().absoluteFilePath (".lock")}.open(QIODevice::ReadWrite);
 
-    QStringList js8_args {
+    // create path
+    QString path = QDir::toNativeSeparators(m_appDir) + QDir::separator() + "js8";
+
+    // create args
+    QStringList args {
       "-s", QApplication::applicationName () // shared memory key,
                                              // includes rig
   #ifdef NDEBUG
@@ -1599,79 +1608,10 @@ void MainWindow::initDecoderSubprocess(){
         , "-e", QDir::toNativeSeparators (m_appDir)
         , "-a", QDir::toNativeSeparators (m_config.writeable_data_dir ().absolutePath ())
         , "-t", QDir::toNativeSeparators (m_config.temp_dir ().absolutePath ())
-        };
+    };
 
-    QProcessEnvironment env {QProcessEnvironment::systemEnvironment ()};
-    env.insert ("OMP_STACKSIZE", "4M");
-
-    if(JS8_DEBUG_DECODE) qDebug() << "decoder subprocess starting...";
-
-#if JS8_DECODE_THREAD
-    auto thread = new QThread(nullptr);
-#endif
-
-    auto proc = new QProcess(nullptr);
-
-    connect(proc, &QProcess::readyReadStandardOutput, this,
-            [this, proc](){
-#if JS8_DECODE_THREAD
-                while(proc->canReadLine()){
-                    emit decodedLineReady(proc->readLine());
-                }
-#else
-                readFromStdout(proc);
-#endif
-            });
-
-    connect(proc, static_cast (&QProcess::error),
-            [this, proc] (QProcess::ProcessError error) {
-              subProcessError (proc, error);
-            });
-
-    connect(proc, static_cast (&QProcess::finished),
-            [this, proc] (int exitCode, QProcess::ExitStatus status) {
-#if JS8_DECODE_THREAD
-              proc->deleteLater();
-              proc->thread()->quit();
-#endif
-              subProcessFailed (proc, exitCode, status);
-            });
-
-    proc->setProcessEnvironment (env);
-    proc->start(QDir::toNativeSeparators (m_appDir) + QDir::separator () +
-            "js8", js8_args, QIODevice::ReadWrite | QIODevice::Unbuffered);
-
-#if JS8_DECODE_THREAD
-    if(JS8_DEBUG_DECODE) qDebug() << "decoder subprocess moving to new thread...";
-    // move process handling into its own thread
-    proc->moveToThread(thread);
-    connect(thread, &QThread::finished, thread, &QObject::deleteLater);
-    thread->moveToThread(qApp->thread());
-    thread->start(QThread::HighPriority);
-#endif
-
-    // create a process watcher looking for stdout read...
-    // seems like we're starving the event loop or something?
-    // auto watcher = new QTimer(proc);
-    // watcher->setInterval(500);
-    // connect(watcher, &QTimer::timeout, this,
-    //         [this, proc](){
-    //             if(proc->canReadLine()){
-    //                 if(JS8_DEBUG_DECODE) qDebug() << "decode process watcher intercepted readline";
-    //                 readFromStdout(proc);
-    //             }
-    //         });
-    // watcher->start();
-
-    // kill the previous proc and set the new one
-    m_valid = false;
-    {
-        if(!proc_js8.isNull()){
-            proc_js8->kill();
-        }
-        proc_js8.reset(proc);
-    }
-    m_valid = true;
+    // initialize
+    m_decoder.processStart(path, args);
 
     // reset decode busy
     if(m_decoderBusy){
@@ -2194,6 +2134,9 @@ MainWindow::~MainWindow()
   m_notificationAudioThread.quit();
   m_notificationAudioThread.wait();
 
+  m_decoder.quit();
+  m_decoder.wait();
+
   remove_child_from_event_filter (this);
 }
 
@@ -2468,6 +2411,8 @@ void MainWindow::readSettings()
   m_msAudioOutputBuffered = m_settings->value ("Audio/OutputBufferMs").toInt ();
   m_framesAudioInputBuffered = m_settings->value ("Audio/InputBufferFrames", RX_SAMPLE_RATE / 10).toInt ();
   m_audioThreadPriority = static_cast (m_settings->value ("Audio/ThreadPriority", QThread::HighPriority).toInt () % 8);
+  m_notificationAudioThreadPriority = static_cast (m_settings->value ("Audio/NotificationThreadPriority", QThread::LowPriority).toInt () % 8);
+  m_decoderThreadPriority = static_cast (m_settings->value ("Audio/DecoderThreadPriority", QThread::HighPriority).toInt () % 8);
   m_settings->endGroup ();
 
   if(m_config.reset_activity()){
@@ -3600,14 +3545,12 @@ void MainWindow::closeEvent(QCloseEvent * e)
   mem_js8->detach();
   QFile quitFile {m_config.temp_dir ().absoluteFilePath (".quit")};
   quitFile.open(QIODevice::ReadWrite);
-  if(JS8_DEBUG_DECODE) qDebug() << "decoder lock remove";
-  QFile {m_config.temp_dir ().absoluteFilePath (".lock")}.remove(); // Allow jt9 to terminate
-  if(!proc_js8.isNull()){
-      bool b=proc_js8->waitForFinished(1000);
-      if(!b) proc_js8->close();
+  {
+      if(JS8_DEBUG_DECODE) qDebug() << "decoder lock remove";
+      QFile {m_config.temp_dir ().absoluteFilePath (".lock")}.remove(); // Allow jt9 to terminate
+      m_decoder.processQuit();
   }
   quitFile.remove();
-
   Q_EMIT finished ();
 
   QMainWindow::closeEvent (e);
diff --git a/mainwindow.h b/mainwindow.h
index 43612bb..63f0abf 100644
--- a/mainwindow.h
+++ b/mainwindow.h
@@ -48,6 +48,8 @@
 #include "SpotClient.h"
 #include "keyeater.h"
 #include "NotificationAudio.h"
+#include "ProcessThread.h"
+#include "Decoder.h"
 
 #define NUM_JT4_SYMBOLS 206                //(72+31)*2, embedded sync
 #define NUM_JT65_SYMBOLS 126               //63 data + 63 sync
@@ -500,8 +502,10 @@ private:
   Modulator * m_modulator;
   SoundOutput * m_soundOutput;
   NotificationAudio * m_notification;
+
   QThread m_audioThread;
   QThread m_notificationAudioThread;
+  Decoder m_decoder;
 
   qint64  m_msErase;
   qint64  m_secBandChanged;
@@ -665,7 +669,7 @@ private:
   QFutureWatcher watcher3;
   QFutureWatcher m_saveWAVWatcher;
 
-  QScopedPointer proc_js8;
+  //QPointer proc_js8;
 
   QTimer m_guiTimer;
   QTimer ptt1Timer;                 //StartTx delay
@@ -897,6 +901,7 @@ private:
   unsigned m_downSampleFactor;
   QThread::Priority m_audioThreadPriority;
   QThread::Priority m_notificationAudioThreadPriority;
+  QThread::Priority m_decoderThreadPriority;
   bool m_bandEdited;
   bool m_splitMode;
   bool m_monitoring;