MorseGenerator
Siehe auch morse.h und morse.cpp.
Aufgabe der Klasse MorseGenerator
:
- Umwandeln von Klartext in Morse-Symbole, z.B. von "ton" -> "- --- -."
- Umwandeln von Morse-Symbole in Zeiten (in ms) für die Tonerzeugung, basierend auf Einstellungen zu WPM und auf Faktoren zur künstlichen Verlängerung/Verkürzung von Punkten, Strichen, Interzeichen-, Buchstaben- und Wort-Pausen.
Die Morse-Tabelle
Zunächst muß das Programm die Kodierung der Morsezeichen kennen.
Wenn ich für einen PIC programmieren würde, dann würde ich die Morsezeichen als Bitmuster speichern. Ich programmiere hier aber als Desktop, also lege ich auf Speicherplatz nicht so großen Wert.
In codes
wird's gespeichert:
Hash<QString, QString> codes; // e.g. "-.."
Ein QHash ist sozusagen ein assoziatives Array, also ein Array mit Strings als Index. So kommen die Daten rein:
store("a", ".-");
store("b", "-...");
store("c", "-.-.");
//...
store("KA", "-.-.-"); // Spruchanfang
store("BT", "-..-."); // Pause
store("AR", ".-.-."); // Spruchende
// ...
void GenerateMorse::store(const QString &sign, const QString &code)
{
if (codes.contains(sign))
qFatal("'%s' already defined as '%s'",
qPrintable(sign), qPrintable(codes[sign]) );
codes[sign] = code;
}
Womit man auch sogleich sehen konnte, wie man mit so einem QHash
umgeht.
Umwandlung Buchstaben nach Morse
Um ein Buchstaben in die Morse-Sequenz umzuwandeln, müssen wir nur nachschauen, ob für den Buchstaben ein Eintrag existiert:
void GenerateMorse::append(const QString &str, bool addSpace)
{
if (codes.contains(str)) {
appendMorse(codes[str], str);
return;
}
// ...
In der Praxis ist die Funktion noch etwas komplexer, da man ihr auch komplette Strings übergeben kann:
morse->append("73 de dh3hs");
Nun habe ich dann also sowas wie "-.", also Punkte und Striche. Was mache ich damit? Direkt morsen kann ich das nicht. Ich muß es noch in eine Folge von Pulsen bzw. Pausen umwandeln.
Das erlegigt die Funktion appendMorse()
. Sie bekommt einen String,
der nur die drei Zeichen "-", "." und " " enthalten darf. Das sind
Punkt, Strich, und der Leerraum zwischen zwei Wörtern.
Daraus erstellt sie eine Liste von Integers, QList<int> morse genannt, in der die zu gebenden Pulse als positive Längen und die zu wartenden Pausen als negative Längen hineingeschrieben werden.
Generell gibt es fünf unterschiedliche Längen:
- die Länge eines Striches (1 Einheit)
- die Länge eines Punktes (3 Einheiten)
- die Länge der Pause zwischen den Punkten/Strichen (1 Einheit)
- die Länge der Pause zwischen zwei Zeichen, innerhalb eines Wortes (3 Einheiten)
- die Länge der Pause zwischen zwei Wörtern, also für das Leerzeichen (7 Einheiten)
Damit sichd die Werte alle Unterschreiten, nehme ich für die Pausen einfach negative Einheiten. Heraus kommt 1, 3, -1, -3, -7. Und nun drösele ich "-. ." auf in die Sequenz
- 3 (drei Einheiten Ton)
- -1 (eine Einheit Pause)
- 1 (eine Einheit Ton)
- -7 (drei Einheiten Pause)
- 1 (eine Einheit Ton)
Abspielen der Morsezeichen
Der MorseGenerator
kann diese Puls/Längeninformation nun auch
abspielen. Nunja, wirklich abspielen tut er sie nicht, er macht das,
was viele Qt-Objekte tun: er schickt zum richtigen Zeitpunkt Signale
an beliebige Empfänger.
Bevor es aber an die Signale geht, müssen die Längeneinheiten in Zeiteinheiten, gemessen in Millisekunden, umgerechnet werden.
Dafür ist die Methode MorseGenerator::playNext()
. Sie holt sich den
jeweils nächsten Eintrag aus der QList morse
, und berechnet seine
Länge durch 1200 / wpm
. Wer wissen will, wie ich auf diese Funktion
komme, mag sich den Source-Code ansehen. Dort befindet sich ein
entsprechender Kommentar mit der Ableitung.
wpm
steht übrigens für "Words per Minute", bezogen auf den
Morse-Code für das Wort "paris " (einschließlich Leerzeichen!).
Nun kann es sein, das man bei hohen WPM-Werten nicht so schnell nachkommt. Also wäre es doch nett, wenn man die Pause zwischen zwei Zeichen erhöhen könnte. Oder bei 75 WPM ist der Punkt so kurz, das man ihn kaum hört. Da wäre es nett, wenn man ihn künstlich etwas länger machen könnte.
Und deswegen hat jede der 5 Längen eine eigenen Faktor zum Verlängern (wenn > 1.0) oder Verkürzen (wenn < 1.0). Diese setzt man mit
setDitFactor(float)
setDahFactor(float)
setIntraFactor(float)
setCharFactor(float)
setWordFactor(float)
und natürlich gibt's auch noch
setWpm(float)
Alle diese Methoden sind übrigens keine normalen Methoden, sondern
Slots eines
QObject. Man kann sie daher
einfach an das valueChanged()
-Signal eines
QDoubleSpinBox oder
einem QSlider hängen.
Werden diese Faktoren auf Werte ungleich 1.0 gesetzt, dann ändert sich
natürlich die effektive WPM-Geschwindigkeit. Die kann man aber, wieder
bezogen auf "paris ", mit getWpm()
abrufen.
Wenn schließlich die Zeiten klar sind, erzeigt MorseGenerator
die
folgenden Signale zum richtigen Zeitpunkt:
playSound(int ms)
playSound(bool)
charChanged(const QString &)
symbolChanged(const QString &)
hasStopped()
Die ersten beiden Signale tun im Prinzip dasselbe. Das obere wird an
Anfang eines jeden auszugebenden Morse-Tons ausgegeben. Damit kann man
den Tongenerator anweissen, nun für ms
Millisekunden einen Ton zu
erzeugen.
Das zweite Signal wird bei jeden Übergang von Nicht-Ton auf Ton und
umgekehrt erzeugt. Man kann es beispielsweise direkt mit einem Objekt
verbinden, welches den akt. Zustand anzeigt, z.B einem QLed
-Objekt.
symbolChanged()
wird für jedes zu sendende Symbol ("-", "." oder " ") aufgerufen.
charChanged()
wird für jedes zu sendende Klartext-Zeichen aufgerufen.
Und hasStopped()
schließlich wird aufgerufen, wenn der
MorseGenerator
alle Längeneinheiten in morse
abgespielt hat ---
und er nicht im Loop-Modus ist.