The macro buttons went wrong at the end of a transmission, both faults in
DigitalEngineSender:
- The engine reports the transmitter drop on its own thread.
WhenTransmitChanged took the state lock, decided the message had ended,
released the lock, and only then called Buffer.Ended(), which clears
what has gone to the engine. A macro pressed on the last character got
through StartAsync in that gap and had already flushed its own text into
Sent, so Ended() wiped the new text off the pane while the engine
transmitted it. Ended() is now called inside the same lock.
- The pane only started again when the engine reported a drop. Two macros
in a row keep the transmitter up, so that report never came and Sent
grew with every press. Everything in Sent is locked, because it is in
the engine and cannot be taken back, so the whole pane became
read-only. TypeAhead.Started() drops the last message's sent text and
keeps what was typed ahead, and StartAsync calls it whenever it keys a
new transmission.
The rest of this commit is the digital transmit work these fixes sit on:
the pane as one coloured box, the sender's three keying states, the
type-ahead feeder paced by the clock with the engine's count as a brake,
{RX} flushing what is left in one piece, and the entry window's function
keys reading the digital macros on a digital mode.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdAYHcdRktqKry7nk414TU
The engine probe answered two questions on the air, and they point opposite
ways.
TxBufLen is the number of characters left to transmit. Seventeen characters
pushed, five decoded back, and it read 12, counting down to 0 as the message
went out. So the pump no longer counts character times off the clock: it asks
the engine, feeds while the answer is under Lead, and asks again. The baud rate
is now only the fallback for an engine that will not answer.
A backspace is not an edit. Pushed in as a character it made the count go up by
four and the text went out unchanged, so text the engine has been given cannot
be taken back, only aborted. EngineTypeAhead was built on the opposite belief
and is gone, along with the setting that chose it and the interface that existed
to switch between the two.
A count that stops going down means the engine is holding what it has: MMTTY set
to Word out keeps a word until the space after it. The pump feeds one character
per look while that lasts, so the space arrives and the word goes out rather
than the message sitting there.
The probe kept two bugs of its own that this run showed: it read the count a
millisecond after pushing, saw 0 and called the message finished, and it started
a step while the previous unkey was still in flight, so that step ran with the
engine down. It now needs two empty answers in a row and waits for the engine to
say it has stopped. It also asks what {RX} does to a word the engine is holding,
which decides whether a macro without a trailing space loses its last word.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RtspmWmS7f8kUvcyaHpRWZ
MMTTY has a type-ahead buffer of its own: characters go into it, a backspace
takes back one it has not transmitted, and TxBufLen says how many are left. Used
that way it paces itself, so there is no gap between characters to tune and no
baud rate to keep in step with the engine.
EngineTypeAhead does that, and Config > Digital picks between it and the pump
that is there now. Off is still the default: three things it rests on have never
been seen with a real engine.
tools/Nonemm.EngineProbe asks the engine those three questions and writes the
answers to a file. It starts MMTTY through the bridge, pushes a message, polls
TxBufLen while it goes out, backspaces over text that has and has not been
transmitted, and logs what came back on the receive side and when.
The bridge learns one verb for it: `buffer` reads TxBufLen and answers with the
count, or -1 when the control will not say. A property the control does not know
is a log line rather than an error, since it stops nothing.
TypeAhead and EngineTypeAhead share the TransmitBuffer interface, which is what
the digital window now works through, so the window does not know which one it
has.
docs/unfinished.md states what each buffer assumes and how to run the probe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RtspmWmS7f8kUvcyaHpRWZ
The transmit pane is now the type-ahead buffer. What has gone to the engine is
in red beside the box and cannot be reached; what is still to go is in the box
and can be typed over, added to or deleted while the engine works through it.
A macro fills the pane rather than the engine, so the operator can correct a
call the message is still sending.
The cursor holds the pump: nothing behind it goes out, so an engine that
catches up with the operator idles instead of transmitting half a word. With
the box out of focus there is no cursor and everything goes.
Escape and the RX button drop what has not gone out and stop the engine where
it is. Enter puts in the carriage return the engine takes as a new line. The
pane starts empty for the next message.
The baud rate the pump counts by is in the digital setup window, beside the
alignment frequency. It has to match the engine: 45.45 unless the contest says
otherwise.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
The window now carries what N1MM's does. Across the top: the TX indicator, the
Letters/Figs box showing the word under the pointer in the other Baudot case,
and the MouseOver box showing the word itself. Down the left of the receive
pane is the pause bar, green while text prints and yellow while it is held.
Under the panes are the message buttons and the row of engine controls, in
N1MM's order.
Both of N1MM's window scroll types are here. The scrolling pane adds lines at
the bottom; the non-scrolling pane keeps text where it was printed, underlines
the line being printed to and walks it down the window, so a call does not move
as the pointer reaches it.
A callsign is checked before it is coloured: anything shaped like a call under
the generic routine, only calls in the files that are ticked under the
resources routine, and under the combination everything, with a call the files
do not hold marked in yellow. The garbage text search finds a Master.scp call
inside a run of noise.
The mouse and the keys are N1MM's. Shift+click converts the case before the
word is transferred, Ctrl+click puts it in the box the cursor is in, Alt+click
puts a call on the stack, a double click replaces the call in the box. The
receive pane and the grab list have their right click menus. Alt+T and Ctrl+K
turn the transmitter on with the cursor in the transmit pane, Alt+G grabs, Esc
aborts.
New from the menus: hover mode, the callsign pause routines, sending a text
file, a date-stamped copy of the receive pane in ExportFiles, clearing the grab
list on a CQ or a QSY, the ham default and AFC off on a run to S&P change, AFC
on with CQ, and raising the window with the entry window it belongs to. The
grab list holds ten calls, as N1MM's does.
The setup window is N1MM's dialog: a General / MMTTY Setup tab, an Engine tab
for the Wine and bridge paths, and a Message Setup tab with the buttons laid
out as they sit in the window, a caption and text box for the one being edited,
the macro list, the count of buttons to show, and import and save of a .mc
file. Right-clicking a message button opens that tab, so the free-form macro
dialog is gone.
The title carries the dial frequency with the mark tone added to it, or the
dial frequency alone when the radio already reads out what it transmits.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
MMTTY and 2Tone have no socket or pipe interface: N1MM hosts XMMT.ocx and
exchanges window messages with the engine. A Linux process cannot load that
control, so bridge/nonemm-mmtty-bridge.exe hosts it under Wine and passes
lines over its standard input and output. docs/digital-bridge.md states the
protocol and what the control needs.
The window is N1MM's: receive pane with coloured callsigns, grab list, call
stacking, twenty-four macro buttons and the engine controls. One left click
copies what is under it — a callsign to the callsign box, anything else to the
exchange box the contest keeps for that kind of value.
Config > Digital registers XMMT.ocx in the Wine prefix on its own, making the
prefix first if it is not there. The engine, the bridge and the control start
at the copies shipped beside the program.
The bridge reads the control's own events. OnTranslateMessage carries only the
messages the control has no event for, so nothing was ever decoded through it.
hamlib's data mode names read as digital modes now, and a mode typed into the
callsign box changes mode the way a frequency changes band, so a station with
no radio can reach RTTY at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD