Für Telefonanwendungen sollte eine Sprachausgabe in den Mikrocontroller hineinpassen. Das erspart eventuell Displays und ähnliches. Um nicht extra Flash-Speicher anbauen zu müssen kommt nur synthetisierte Sprache in Frage, wie sie in den 80-er Jahren des letzten Jahrhunderts üblich war: Eine Roboterstimme mittels Formanten. Klingt altbacken, aber ist verständlich und erfüllt den Zweck. Leider ist dazu kaum Software aufzutreiben. Schon gar nichts was in einen 8-Bit-Mikrocontroller passt. Auch klingt alles (was es so gab und gibt) englisch. Eine Deutsch-Anpassung wäre hilfreich. Dazu ist eine Anpassung des Vokaldreiecks erforderlich. Das „Übersetzen“ von Text in Phoneme kann zur Compilezeit erfolgen, sodass der Mikrocontroller nur noch konstante Phoneme ausspucken muss.
Eine Alternative ist eine auf Sprachausgabe optimierte Audiokompression: LPC = Linear Predictive Coding nennt sich das, und dafür gibt es (zu spät, 2021 gefunden) eine Arduido-Implementierung: Talkie nennt die sich.
Ausgangspunkt für die phonembasierte Sprachausgabe ist eine auf der Softwaremüllhalde gefundene Abandonware, dessen Quelltext offenbar von einem Disassembler / Decompiler stammt, mühevoll zur Funktion gebracht aber bei weitem noch nicht ausgemistet.
Hier ist der erste Entwicklungsstand abgelegt. Ausgangspunkt ist die Sprachausgabe für einen 32-Bit-Mikrocontroller namens ESP8266 mit WLAN auf dem Chip, wie's scheint. Ich brauche es allerdings für 8 Bit Verarbeitungsbreite.
Es ist soweit umgestrickt, dass:
"Can you hear me now?"
"KAEN YUW /HIY5R MIY NAW4?"
L"\x0648\x0149\x044A\x0B08\x071C\x041A\x0935\x0814\x6224\x5B05\x0A12\x071B\x0805\x581C\x4F33\x4D14\x0202"
Die (überflüssige) Phonemschrift stammt noch aus der Quelltextvorlage.
Der Binärphonem-String ist aus 3 64-Byte-Puffern entstanden
(array-of-struct statt struct-of-array)
und kann zweckmäßigerweise als Unicode-ähnliche
wchar_t
-Zeichenkette abgelegt werden.
Immer noch darauf aus, den Wust auseinanderzunehmen, habe ich das Programm in 4 Teilprogramme aufgeteilt, die dazu bestimmt sind, in eine Pipe verkettet zu werden. Das Kunswerk ist diesmal nur für (das gute alte) Visual Studio 6 gemacht. Um es selbst zu kompilieren benötigt man mein msvcrt-light-Paket. Denn nur so ist sichergestellt, dass das Kompilat auch wirklich nur aus selbstgeschriebenem Kode besteht. Andernfalls wirft der Kompiler mehr oder weniger viel Microsoft-Kode hinein, 30 Kilobyte Minimum.
Nur das dritte Teilstück muss in einen
AVR implementiert werden.
Da sogar das Windows-Programm (die „Echse“) < 8 KByte groß ist,
passt es sicherlich mühelos in einen ATmega32-8 oder -U4,
also Arduino Uno oder Leonardo.
Natürlich nicht diese „Echse“, sondern das Ergebnis der
Kompilierung des Quelltextes mit
avr-gcc.
Entsprechende PROGMEM
-Attribute für die Tabellenablage im Flash-Speicher
und pgm_read_byte()
s zum Lesezugriff darauf sind bereits eingebaut.
Immer noch sind eine Unmenge goto
s mit Sprungmarken
aus Hexadezimalzahlen sowie globale Variablen drin.
Das auszumisten wird noch zwei weitere Überarbeitungen erfordern,
ganz zu schweigen von der Aufgabe, das ganze etwas mehr deutsch klingen zu lassen.
Die Teile im einzelnen:
"Can you hear me now?"
wird "KAEN YUW /HIY5R MIY NAW4?"
.
"KAEN YUW /HIY5R MIY NAW4?"
den String
L"\x0648\x0149\x044A\x0B08\x071C\x041A\x0935\x0814\x6224\x5B05\x0A12\x071B\x0805\x581C\x4F33\x4D14\x0202"
.
Die Bedeutung der 16-Bit-Werte ist in phonem_t
deklariert.
Der Index (Lo-Byte) ist dabei relativ willkürlich gewissen Phonemen zugeordnet.
Es gibt in s2 und s3 genau 80 Phoneme mit den Indizes 1..80.
Die Null dient programmintern zum Abschluss der Zeichenkette.
Das High-Byte enthält Länge und Betonung, nicht als Lautstärke sondern als Tonhöhe.
Dass der entstehende Zeichenstrom Steuerzeichen enthalten kann ist, gelinde gesagt, unglücklich.
Es wäre ein leichtes, den Indexbereich um 32 zu verschieben.
Noch besser wäre es, wenn der Zeichenstrom mnemonisch ist,
also teilweise passende ASCII-Zeichen zur Aussprache enthält.
Nun, wozu das ganze?
Der Testfall ist echo Can you hear me now? | s1 | s2 | s3 | s4!
Die Zeichenkette Can you hear me now? wird zum Eingabetext für s1.exe,
dessen Ausgabe die Eingabe für s2.exe usw. bis es aus dem Lautsprecher schallt.
Um das ganze auf einen Arduino zu bringen erscheint es wenig sinnvoll, die Programme s1.exe und s2.exe zu implementieren, es sei denn, man steht auf Englisch und hat noch viel Flash-Speicher frei. Denn niemand wird sich von einem 8-Bit-Controller variablen Text vorlesen lassen. Schon gar nicht Namen aus einem Telefonbuch. Feste Texte, wie bspw. Zahlen, genügen vollauf.
Die Datei phonems.bin kann als Eingabedatei von s3.exe dienen, als eine Art Abkürzung: s3 < phonems.bin | s4 Und phonems enthält zwei menschenlesbare UTF-16-Beispiel-Phonemstrings.
Durch den Verweis auf LPC = Linear Predictive Coding angestachelt, anscheinend eine TI-Erfindung? (Nein, allenfalls die geänderte Bitkodierung zur weiteren Speicher-Einsparung, die Grundlage ist LPC10 bzw. FIPS137), soll Sprachausgabe auch mit einem ATtiny85 möglich sein. Nein, die Unmachbarkeit von schnellen Multiplikationen ohne MUL-Befehl, daran glaube ich nicht. Wenn die Framerate für den Multiplikationsblock 8 kHz beträgt, hat man bei 16 MHz 2000 Takte Zeit. Macht bei 20 Multiplikationen 100 Takte. Das reicht geradeso.
Synchronansatz von Talkie, funktioniert mit ATmega32U4,
zur Reduktion der Interruptlast und des Speicherhungers.
Arduino-Bezug entfernt und die vielen #ifdef
s entfernt.
Aus einem faktischen Singleton ein
„statisches Objekt“ gemacht.
(Es ist bekanntlich relativ egal, ob man in C++ ein Objekt mit lauter statischen Memberfunktionen bastelt
oder Funktionen und Variablen in einen Namespace tut. Abgesehen vom dann fehlenden Defaultkonstruktor.
Aber darauf kann/sollte man bei Mikrocontrollerprojekten verzichten können.
Daher ist's hier mittels Namespace gelöst.)
Das Beispiel würde auf einem ATmega32U4 2,5 KB Kode und für die Zahlen
und einige Worte 1,8 KB Flash-Tabellenplatz benötigen.
Die Software implementiert ein Latticefilter mit unendlicher Impulsantwort (IIR) 10. Ordnung in der folgenden Form:↓Subtraktion ┌Spannung u[8] ╔═══╗ ╔═══╗ ↓ ╔═══╗ ╔═══╗ ╔═══╗ ╔═══╗ ╔═══╗ ╔═══╗ ╔═══╗ ╔═══╗ →u10╢ - ╟→u9─╢ - ╟─→u8─┬╢ - ╟─→u7─┬╢ - ╟─→u6─┬╢ - ╟─→u5─┬╢ - ╟─→u4─┬╢ - ╟─→u3─┬╢ - ╟─→u2─┬╢ - ╟─→u1─┬╢ - ╟─→u0─┬───┬→ ╚═╤═╝ ╚═╤═╝┌×K8←┘╚═╤═╝┌×K7←┘╚═╤═╝┌×K6←┘╚═╤═╝┌×K5←┘╚═╤═╝┌×K4←┘╚═╤═╝┌×K3←┘╚═╤═╝┌×K2←┘╚═╤═╝┌×K1←┘╚═╤═╝┌×K0←┘ │ └×K9←┐ └←─┼×K8┐ └←─┼×K7┐ └←─┼×K6┐ └←─┼×K5┐ └←─┼×K4┐ └←─┼×K3┐ └←─┼×K2┐ └←─┼×K1┐ └←─┼×K0┐ │ ↑ │╔══╗╔═╧═╗ │╔══╗╔═╧═╗ │╔══╗╔═╧═╗ │╔══╗╔═╧═╗ │╔══╗╔═╧═╗ │╔══╗╔═╧═╗ │╔══╗╔═╧═╗ │╔══╗╔═╧═╗ │╔══╗╔═╧═╗ │╔══╗│ │ └╢x9╟╢ + ╟─┴╢x8╟╢ + ╟─┴╢x7╟╢ + ╟─┴╢x6╟╢ + ╟─┴╢x5╟╢ + ╟─┴╢x4╟╢ + ╟─┴╢x3╟╢ + ╟─┴╢x2╟╢ + ╟─┴╢x1╟╢ + ╟─┴╢x0╟┘ │ ╚══╝╚═══╝ ╚══╝╚═══╝ ╚══╝╚═══╝ ╚══╝╚═══╝ ╚══╝╚═══╝ ╚══╝╚═══╝ ╚══╝╚═══╝ ╚══╝╚═══╝ ╚══╝╚═══╝ ╚══╝ └Multiplikation ↑Speicherstelle=Verzögerung ↑Addition
Das AVR-Programm als JavaScript:
Bits: | 4 | 1 | 6 | 5 | 5 | 4 | 4 | 4 | 4 | 4 | 3 | 3 | 3 | 200 × 8 | 200 × 10 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
frame # | energy | repeat | period | K0 | K1 | K2 | K3 | K4 | K5 | K6 | K7 | K8 | K9 | Signal 25 ms | Wasserfall IIR |
Es fiel (im Debugger) auf, dass die Amplitude der originalen Talkie-Firmware viel zu gering ist, Spitzenwerte liegen bei 25 %. Das ist beim Arduino verschenkte Ausgangsleistung. Die ursprünglichen Daten führen zur Instabilität im Filter bei der Ziffer Acht, ich habe die Daten leicht frisiert: Den Faktor K0 reduziert. Die Ausgangsspannung tendiert zu stark zu positiven Werten.
Nunmehr wurde das Kettenfilter auf 8 Bit reduziert und jede Additionsstufe mit Sättigung ausgestattet (das kostet dem AVR nicht so viel). Damit konnte die Ausgabelautstärke ohne hörbare Übersteuerungseffekte auf das Vierfache(!) gesteigert werden. Mit der Bitbreitenreduktion geht eine Verdopplung der Multiplikationsgeschwindigkeit einher, sodass sich dieses Kettenfilter auch auf einem ATtiny (der bekanntlich keinen MUL-Befehl hat) mit 16 oder gar 8 MHz Taktfrequenz betreiben lässt. Was zu beweisen wäre. Diese Javascript-Ausgabe simuliert genau ein derart konstruiertes Latticefilter.
Viel schwieriger als die Dekompression ist die Kompression. Die von mir gefundene Implementierung von 2011 Peter Knight in freemat habe ich auf Javascript umgestellt. Damit kann jedermann gesprochene Texte im Browser komprimieren. Zum Talkie-Kompressor