Remote CW Keyer - Implementation Details
by DL4YHF, 2023-12-20

# Contents

1. Introduction
2. TCP Client / Server model
   1. Network Audio Routing
   2. Network Morse Signal Routing
3. Sourcecode overview

## 1. Introduction

This document only contains the technical details about the implementation; mainly about the network protocol between client and server.
It it not required to "just use" the application (either as client or server). All you need to know about using the program, and what kind of simple hardware is used to connect a real Morse key to the PC on the client side, is explained in the Remote CW Keyer manual.

## 2. TCP-based Client / Server communication

Most of the communication between client and server happes in module CwNet.c. This module contains one thread for the client side [ClientThread()] and one thread for the server side [ServerThread()]. At the moment, the 'Remote CW Keyer' application only runs as either client or server (but not both simultaneously in a single instance of the program). However, to test and develop the client/server communication on a single machine, at least on Windows, multiple instances of the program can run side-by-side without a "real network" between them, using the 'localhost' aka dummy IP 127.0.0.1 .
The server can handle connections from multiple clients. This is usually the program that runs on the 'remote radio' site (either on a windows PC, or maybe Linux/WINE, or maybe one fine day on a power-saving microcontroller with an embedded TCP/IP network stack like LwIP).
A client can only connect to a single server (if he needs to connect to a server at all; because the 'Remote CW Keyer' can be used completely locally, using only USB- or serial port connections to the Morse key and the radio).

             ,----------------------------------------,
             |                  Server                |<----- USB ---> Radio
             |                                        |
             |  TCP/IP         TCP/IP         TCP/IP  |
             '----------------------------------------'    __ "Network" (TCP/IP):
                /|\  |         /|\  |         /|\  |         | Audio signals,
                 |   |          |   |          |   |         | CW keying,
                 |  \|/         |  \|/         |  \|/      __| and other data
             ,-----------,  ,-----------,  ,-----------,
             | Client #1 |  | Client #2 |  | Client #3 |
             '-----------'  '-----------'  '-----------'
              /|\ /|\  |     /|\ /|\  |     /|\ /|\  |
               |   |   |      |   |   |      |   |   |
               |   |  \|/     |   |  \|/     |   |  \|/
              Key Mic Spkr   Key Mic Spkr   Key Mic Spkr

Not shown above for simplicity:
  The 'Server' may also have a Morse key and loudspeakers attached.
  In fact, most of the times the server will be the usual 'shack PC',
  temporarily running 'as server' to allow remote operators to listen to their own signal (on the server operator's radio), or even use the server operator's transmitter remotely - if the operator, and the authorities permit (see suggestions for German radio operators here).

To keep the 'network administration' overhead down, there will only be a single TCP socket per client-server connection. The binary data streams traveling from from client to server and back are multiplexed internally (in CwNet.c) to carry ...

- Morse code for transmission (grep for CWNET_CMD_MORSE),
- Audio samples for reception (grep for CWNET_CMD_AUDIO),
- Icom CI-V packets for remote control (.. CWNET_CMD_CI_V),
- and a few other types of 'commands', all defined in CwNet.h (CWNET_CMD_ ...) .

### 2.1 Network Audio Routing

Audio routing on the client side is trivial:

- When configured with an active audio input device,
  the client down-samples the audio to 8 kSamples/second, compresses it into an 8-bit-per-sample bytestream with simple A-Law compression, and merges it to whatever else needs to be sent to the remote server.
- When configured with an active audio output device,
  the client decompresses the A-Law compressed byte stream received from the remote server, resamples (interpolates) the stream from 8 to 48 kSamples/seconds, adds the result to his own, optional, 'locally generated' CW sidetone, and sends the resulting stream to his audio device (e.g. loudspeakers or headphone).

Audio routing on the server side is a bit more elaborate, because the server may be connected by multiple clients simultaneously:

- Audio received from the server's radio (connected via built-in or external 'USB audio device') is down-sampled to 8 kSamples/second, and A-Law compressed. The result is buffered in a classic, lock-free, circular FIFO from where each of the server's "client instances" (in CwNet.h : T_CwNet.RemoteClient[]) can retrieve the next block of samples, which typically happens every 20 milliseconds in CwNet.c : ServerThread() -> CwNet_OnPoll()).
  Thus (important for a possible implementation in a Microcontroller), the radio receiver's audio signal only needs to be processed once, regardless of how many remote clients are currently connected.
- Audio received by the server from any of the currently connected clients is either ...
  - ignored (if the client isn't allowed to talk / transmit or 'chat' with the other users),
  - or sent to the other currently-logged-in users (helpful if the 'Remote CW Keyer' is used in an off-air round table or in an 'online CW lesson', when the instructor / teacher runs the server on his PC, possibly without using a real transceiver at all)
  - or (future plan, not materialized yet because we have enough applications to remotely control radios for voice transmissions) pass on the audio signal to the transmitter (if the particular client currently "has the microphone").

Due to the 8-bit A-Law compression, and 8 kSamples/second, each client's audio stream adds approximately 64 kBit / second (plus some overhead for the internal multiplexing) to the server's up-stream bandwidth. This may be a problem when serving multiple clients from a really 'remote' site, without a fast internet connection.

### 2.2 Network Morse Signal Routing

On the client side, the input from the Morse key adapter is first converted into a stream of 'key-down' and 'key-up' events, each with a seven-bit timestamp (in milliseconds, relative to the previous event).

## 3. Sourcecode overview

After unzipping the program (including files and subdirectories) into a directory of your choice (not neccessarily "Programme", "Program File (x86)", "Program Files" or wherever your OS wants you to 'install' everything), that directory should contain at least this:

       Remote_CW_Keyer
         |--- manual
         |        |-- Remote_CW_Keyer.htm : The manual in HTML format
         |        '-- *.png, *.jpg        : Images loaded by your browser
         |                                  when opening the manual
         |--- sources : The 'most interesing sourcecodes' (written in C).
         |        |-- KeyerThread.c :
         |        |     This is where the program polls the operator's
         |        |     'local' Morse key, connected to a serial port.
         |        |-- Elbug.c : Emulates an electronic keyer
         |        |             ('dot' and 'dash' contact states in,
         |        |              Morse code pattern out, in real-time).
         |        |-- CwGen.c : Converts Text (ASCII) into Morse code.
         |        |-- CwDSP.c : Creates audio waveforms from the
         |        |             on/off keyer Morse code pattern, etc.
         |        |-- CwNet.c : Socket-based network server and client.
         |        |             Multiplexes and demultiplexes different
         |        |             kind of 'bytestreams' into the same
         |        |             network socket (TCP/IP connection
         |        |             between client and server).
         |        |-- CwStreamEnc.c : 'CW Stream Encoder / Decoder' .
         |        |             Converts the raw 'CW keying signal'
         |        |             into a low-bandwidth bytestream,
         |        |             with an efficiently encoded 7-bit
         |        |             'timestamp' (actually the number of
         |        |             milliseconds to wait before emitting
         |        |             the next 'key up / key down'-flag),
         |        |             and the 'key up / key down'-flag
         |        |             in the most significant bit of each
         |        |             byte in the CW keying bytestream.
         |        |-- dsound_wrapper.c : Interface between the application
         |        |             and one of the many Windows 'Audio APIs'.
         |        |-- Timers.c, Utilities.c : Ugly stuff for Windows.
         |        '-- Keyer_Main.cpp :
         |              This is just the GUI (User Interface), written
         |              in C++ Builder, thus not sooo interesting.
         |    You can safely delete the entire 'sources' folder.
         |    It is not required to run the executable.
         |
         '--- Remote_CW_Keyer.exe : This is the executable file .
              Lauch it via double-click, command shell, or create
              a link on the desktop to it.

Links in the above sourcecode overview only work after unpacking the zipped archive. They don't work when viewed 'online' on the website, and there is no public repository (GitXYZ) for it. The sources contained in the archive are considered 'open source', but take care - some of them may be 'available as source' but not really 'open', like the now-discarded ASIO support.
As of November 2024, the GUI could only be compiled with Borland C++Builder V6. An attempt to adapt the GUI for Embarcadero C++Builder V12 "Community Edition" was abandoned after a week of trying to convert the old project (*.bpr) into a new one (*.cbproj) [basically, the new IDE can not import *.bpr files even though someone claimed the opposite]. Experiences with the new IDE were not convincing: The IDE crashed often (and had to be killed via task manager); the built-in help system was horrible ("you don't have the permission to read this page" after pressing F1 to get help on certain items, C++ classes etc); and the price for a non-expiring multi-platform-capable version (Windows+Linux) made the developer fall off his chair.