Files
Nonemm/docs/digital-bridge.md
ericek111 917a05b898 Key MMTTY the way N1MM does, with the control's PTT property
Nothing was ever transmitted. SetMmttyPTT does not start a transmission: N1MM
calls it with 1 to stop once the buffer is empty, which is its XmitOff, and with
0 to stop now, which is its AbortXmit. A transmission starts by setting the
control's PTT property, in XmitOn.

So the bridge learns `key <0|1>` for that property, and MmttyEngine now keys
with it, ends a message with SetMmttyPTT(1) so the buffer still goes out, and
aborts with SetMmttyPTT(0). This is the digital {TX} in the entry window and the
digital window as well as the probe: none of them could key the engine before.

The probe checks that the engine keys before it measures anything, and stops
with a plain statement if it does not, rather than reporting numbers from an
engine sitting still. It also asks a new question: whether the engine holds a
word until the space after it, which is MMTTY's Way to send. Received characters
are marked as noise while the engine is not transmitting, since a machine with a
sound card decodes the band all the way through the run.

The first run on a real engine says MMTTY 1.70 connects, the control answers
TxBufLen and refuses NotAProperty with DISP_E_UNKNOWNNAME. What TxBufLen counts
is still open: read while nothing was transmitting it rose over time and rose by
four after four backspaces, which is not what characters-left would do.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RtspmWmS7f8kUvcyaHpRWZ
2026-09-01 22:51:43 +00:00

6.9 KiB

MMTTY and 2Tone through Wine

MMTTY is a Windows program with no socket, pipe or file interface. N1MM does not talk to it directly either: it hosts XMMT.ocx, a 32-bit ActiveX control that starts MMTTY.EXE and exchanges window messages with it. A Linux process cannot load that control, so Nonemm runs a small Windows program under Wine — bridge/nonemm-mmtty-bridge.exe — which hosts the control and passes lines over its standard input and output.

Nonemm  ──stdin/stdout──▶  bridge.exe (Wine)  ──XMMT.ocx──▶  MMTTY.EXE / 2Tone

2Tone uses the same control, so it runs the same way. The only differences are the command line and the window title, both in MmttyOptions.

The line protocol

One line per message: a verb, then fields, separated by tabs. Backslash, tab, carriage return and newline inside a field are backslash-escaped, so a field never splits a line. BridgeLine.cs and bridge/Protocol.cpp are the two ends of it.

Wine writes its own diagnostics to standard error, which leaves standard output to the protocol.

To the bridge:

Line What the bridge does
open <title> <port> <command line> sets Title, ComName and InvokeCommand, sets bActive, then posts the host window handle
send <text> SendString
key <0|1> sets the control's PTT property, which is what starts a transmission
ptt <0|1> SetMmttyPTT: 0 stops now, 1 stops once the buffer is empty
post <message> <parameter> PostMmttyMessage, for everything in MmttyMessage
buffer [property] reads TxBufLen, or the property named instead, and answers buffer
close shuts the engine down and leaves the bridge running
quit shuts the engine down and exits

From the bridge:

Line Where it comes from
ready the control is created and listening
connected <version> OnConnected, with verMMTTY
disconnected <status> OnDisconnected: 0 failed to close, 1 closed, 2 failed to start
rx <code> one decoded character
tx <0|1> the engine started or stopped transmitting
mark <hz>, space <hz> the tone pair moved
switch <bits>, view <bits> AFC (4), net (8) and reverse (256), and the engine's view state
buffer <n> how many characters the engine still has to transmit, or -1 when the control would not say
log <text>, error <text> anything the bridge has to say

buffer is the one thing that is asked for rather than reported: the control has no event for the length of the transmit buffer, so it is polled. A property name in the request is only for finding out what the control answers to; a name it does not know fails with DISP_E_UNKNOWNNAME, which comes back as a log line and buffer -1 rather than an error, because a property the control will not answer stops nothing.

Each of the others comes from an event of the control's own: OnCharRcvd, OnPttEvent, OnFreqChanged (mark and space in one event), OnSwitchChanged and OnViewChanged. The control raises OnTranslateMessage only for the MMTTY messages it has no event for — width, resolution, thread and the like — so the bridge does not listen to it. N1MM reads the characters the same way, in DigitalMultiRXWindow.XMMR_OnCharRcvd; the OnTranslateMessage handler in DigitalInterface.cs that maps codes 32771 to 32778 never runs with this version of the control.

Starting the engine

The order is N1MM's, from DigitalInterface.cs:

  1. Read Mmtty.INI beside the program. [Define] PTT is the serial port, and AFC, TxNet and Rev are the switch word the engine starts with. Without them, the first toggle moves the wrong way.
  2. Build the command line: the quoted program, the window size (-r normal, -t small, -s medium 1, -u medium 2), -a when the window is not on top, and -Z for MMTTY but not for 2Tone.
  3. open. The control appends -h<handle> of its own, which is how the engine finds its way back.
  4. An engine that answers disconnected 2 is started again, up to ten times. That is N1MM's retry, and it is needed: MMTTY does not always come up first time.

What the control needs that is not obvious

Three things cost a day between them, so they are written down:

  • IPersistStreamInit::InitNew before anything else. The control is MFC based, and until it is initialised every dispatch call returns E_UNEXPECTED — property gets included. Creating and activating it is not enough.
  • A real window. In-place activation needs a parent window, so the bridge needs an X display even though it shows nothing.
  • The control's own shutdown blocks. IOleObject::Close and setting bActive to false wait for the engine while holding the thread that would have to pump the messages, so neither returns. The bridge posts the shutdown message, closes the engine's windows, and kills the process if it is still there five seconds later.

Setting it up

Config > Digital has a Register button. It does what the next paragraph describes, in the prefix named in the same dialog, and writes the result under the button. Press it once, before the first start.

The steps are in WinePrefixSetup. A prefix that is not there yet is made with wineboot -u and WINEARCH=win32, because the control is 32 bit. The control is then copied into the Windows folder and registered:

export WINEPREFIX=~/.wine-nonemm WINEARCH=win32
wineboot -u
cp bridge/XMMT.ocx "$WINEPREFIX/drive_c/windows/system32/"
wine regsvr32 'C:\windows\system32\XMMT.ocx'

A 64-bit prefix keeps 32-bit code in syswow64 and has a second regsvr32.exe there. That is the one to register the control with: the 64-bit one cannot load it.

XMMT.ocx ships with N1MM Logger+ and is distributed with Nonemm in the bridge folder. MMTTY itself is downloaded separately and can live anywhere in the prefix.

Build the bridge with the mingw-w64 cross compiler:

make -C bridge

Where the program looks

MMTTY, the bridge and XMMT.ocx are distributed with Nonemm, in mmtty and bridge folders beside the program, and the build copies them there from the working copy. Config > Digital starts at those three paths, so an operator who runs the distributed copy has nothing to fill in. An engine kept somewhere else, or 2Tone in place of MMTTY, is browsed for in the same dialog. Neither program is in the repository.

The bridge keeps its own window hidden. Started with --show it shows it, which is the way to see what the control is doing.

What has not been tested

The bridge has been run against MMTTY 1.70 under Wine 10: it starts the engine, connects, reports the version, takes text, opens MMTTY's setup window, moves the tone pair and reports mark, space, switch, view and tx back. It has never been run with a sound card, so nothing has been decoded and no rx line has ever come from a real signal. FSK keying through EXTFSK and PTT on a serial port have not been tried either, and neither has 2Tone.