diff --git a/src/Nonemm.Session/Baudot.cs b/src/Nonemm.Session/Baudot.cs new file mode 100644 index 0000000..da029f1 --- /dev/null +++ b/src/Nonemm.Session/Baudot.cs @@ -0,0 +1,48 @@ +namespace Nonemm.Session; + +/// The two cases of the Baudot alphabet RTTY sends. One shift character tells +/// the printer whether the five-bit codes that follow are letters or figures, +/// and a shift lost to noise prints a whole word in the wrong case: 599 comes +/// out as TOO, and CQ TEST as :1 53'5. +/// +/// `LettersFigs` prints a word in the case it was not printed in, which is what +/// the digital window's Letters/Figs box shows for the word under the pointer. +/// The table is the US teleprinter one N1MM uses in +/// `DigitalMMTYIo.Letters_Figs`: a letter and the digit or punctuation mark on +/// the same key are a pair. J has no pair and is left as it is, and so is +/// anything outside the table. +/// +/// N1MM converts a letter to its figure but not a figure back to its letter, +/// so its box turns VE4AEO into ;3R-39 but not ;3R-39 back into VE4AEO. The +/// table here is read both ways, which is what MMTTY's own right click does and +/// what the box is for: a call printed in the wrong case is the case an +/// operator has to read. +public static class Baudot +{ + /// The letters in the order of the figures they share a key with, so A and + /// - are a pair and R and 4 are a pair. + private const string Letters = "ABCDEFGHIKLMNOPQRSTUVWXYZ"; + + private const string Figures = "-?:$3!().,9014'57;2/6\""; + + public static string LettersFigs(string text) + { + char[] swapped = new char[text.Length]; + for (int at = 0; at < text.Length; at++) + { + swapped[at] = Swap(text[at]); + } + return new string(swapped); + } + + private static char Swap(char c) + { + int figure = Figures.IndexOf(c); + if (figure >= 0) + { + return Letters[figure]; + } + int letter = Letters.IndexOf(char.ToUpperInvariant(c)); + return letter >= 0 ? Figures[letter] : c; + } +} diff --git a/tests/Nonemm.Session.Tests/BaudotTests.cs b/tests/Nonemm.Session.Tests/BaudotTests.cs new file mode 100644 index 0000000..b9a3804 --- /dev/null +++ b/tests/Nonemm.Session.Tests/BaudotTests.cs @@ -0,0 +1,47 @@ +using Nonemm.Session; + +namespace Nonemm.Session.Tests; + +/// The Letters/Figs box: a word printed in the case it was not printed in. +/// The examples are the ones in N1MM's manual. +public class BaudotTests +{ + [Fact] + public void ALostFiguresShiftPrints599AsToo() + { + Assert.Equal("TOO", Baudot.LettersFigs("599")); + Assert.Equal("599", Baudot.LettersFigs("TOO")); + } + + [Fact] + public void SeventyThreePrintsAsUe() + { + Assert.Equal("UE", Baudot.LettersFigs("73")); + } + + [Fact] + public void ACallsignGoesToFiguresAndBack() + { + Assert.Equal(";3R-39", Baudot.LettersFigs("VE4AEO")); + Assert.Equal("VE4AEO", Baudot.LettersFigs(";3R-39")); + } + + [Fact] + public void PunctuationCarriesTheSpacesAcross() + { + Assert.Equal(":1 53'5", Baudot.LettersFigs("CQ TEST")); + } + + [Fact] + public void ASerialNumberComesBackFromLetters() + { + Assert.Equal("012", Baudot.LettersFigs("PQW")); + } + + [Fact] + public void WhatTheTableHasNoPairForIsLeftAlone() + { + Assert.Equal("J", Baudot.LettersFigs("J")); + Assert.Equal("", Baudot.LettersFigs("")); + } +}