Mein erstes mittelhochdeutsches Hörbuch – Teil 5

Dies ist der fünfte Teil des Tutorials. Der Beginn findet sich hier.

Die kleine Raupe Hedda 

Wie bringen wir Hedda nun dazu sich Buchstabe für Buchstabe durch den Text zu fressen?

Ganz einfach: Wir verwenden eine Schleife, die einen Buchstabenindex i bei jedem Durchlauf weiterzählt. Wir beginnen mit dem ersten Buchstaben (in C# und anderen Programmiersprachen als 0 gezählt), dann folgt der zweite (C#: 1) und so weiter … bis keine Buchstaben mehr zu zählen sind. Was dann der Fall ist, wenn  unser Index i mit der Text- oder Wortlänge identisch ist:

        public string FindeAussprache(string Wort)
        {
            string Aussprache = "";
            int i = 0; // Index des Buchstabens, an dem wir uns gerade befinden.
            while (i < Wort.Length) // Solange i kleiner als die Wortlänge ist.
            {
                // Zähle den Index i weiter
                // Schreibe die zur Wortstelle[i] passende Lautung an das Ende von Aussprache
            }            
            return Aussprache;
        }            

Der Wert des Index i erhöht sich vermutlich in den meisten Durchläufen genau um 1, um den einen Buchstaben, der ersetzt werden soll. Doch manchmal handelt es sich bei dem Buchstaben an der Stelle i um den Anfang einer Buchstabenfolge, die im Ganzen ersetzt werden muss: ein Diphthong beispielsweise, oder der Trigraph sch für das IPA-Lautzeichen [ʃ]. i wird dann um die Länge der zu ersetzenden Folge weitergezählt (also 3 im letzten Fall). An die Zeichenkette Aussprache wird in diesem Fall die der IPA-Kodierung [ʃ] entsprechende UPS-Variante [SH ] angehängt, die sich im Lexikon unter dem Stichwort [„sch“] befindet, bzw. allgemeiner und in C# formuliert:

            if (Wort.Substring(i, Buchstabenfolge.Length) == Buchstabenfolge)
            // Wenn das Wort ab der Position i bis zur Länge der Buchstabenfolge 
            // identisch mit der Buchstabenfolge ist, dann ...
            {
                Aussprache = Aussprache + LexionDerBuchstabenErsetzung[Buchstabenfolge];
                i = i + Buchstabenfolge.Length;
            }

Was wir jetzt noch brauchen, ist eine Liste aller Lexikoneinträge, bei denen das Stichwort mit dem Buchstaben beginnt, der sich an Position i in unserem Wort befindet. Um an diese zu gelangen, verwenden wir eine für spezielle Syntax (LINQ), die C# für solche Zwecke bereitstellt:

                List<string> MöglicheBuchstabenFolgen; 
                MöglicheBuchstabenFolgen = LexionDerBuchstabenErsetzung.Keys // Die mhd. Buchstabenfolgen
                                            .Where(Buchstaben => Buchstaben[0] == Wort[i])
                                         // bei denen der erste Buchstabe und der aktuelle im Suchbegriff identisch sind.
                                            .ToList();                         // als Liste.

Wir gehen von nun an davon aus, dass Buchstabenfolgen, die sich im Lexikon der Buchstabenersetzungen auffinden lassen, auch immer als diese Folgen zu lesen sind. Ein e gefolgt von einem i ist also immer als ein ei aufzufassen, die hintereinander geschriebenen Buchstaben s,c und h immer als sch. Uns ist durchaus bewusst, dass wir mit dieser Annahme Gefahr laufen, ein paar Blumento-Pferde einzufangen. Das mittelhochdeutsche Beispiel geirren haben wir schon angeführt, das selbstverständlich als ge-irren zu lesen ist und nicht als geir-ren. Doch wenn wir uns recht erinnern, haben wir ja bereits ein anderes Lexikon angelegt, in dem wir ganze mittelhochdeutsche Wörter nachschlagen und ihre Verlautung auffinden können.

Dieses Lexikon kümmert sich um die (hoffentlich!) nicht so häufigen Sonderfälle und ansonsten sollten wir im Normalfall mit unserer Verallgemeinerung zurechtkommen. Deshalb können wir die Liste der möglichen Buchstabenfolgen mit dem gesuchten Anfangsbuchstaben der Länge nach sortieren. Dann zählen wir vom längsten Eintrag zum kürzesten abwärts, solange, bis ein Eintrag in unser Wort an die Stelle i passt. Der Eintrag darf dazu nicht länger sein, als Buchstaben nach i im Wort sind und keiner seiner Buchstaben darf im Wort an entsprechender Stelle fehlen. Stimmt alles, schlagen wir im Lexikon nach, um unserer Ausgabezeichenkette Aussprache die passende Lautung anzuhängen. Die komplette Methode FindeAussprache lautet also:

        public string FindeAussprache(string Wort)
        {
            string Aussprache = "";
            int i = 0; // Index des Buchstabens, an dem wir uns gerade befinden.
            while (i<Wort.Length) // Solange i < als die Wortlänge ist.
            {
                List<string> MöglicheBuchstabenFolgen; 
                MöglicheBuchstabenFolgen = LexionDerBuchstabenErsetzung.Keys // Die mhd. Buchstabenfolgen
                                            .Where(Buchstaben => Buchstaben[0] == Wort[i]) // bei denen der erste Buchstabe und der aktuelle unseres Suchbegriffes identisch sind.
                                            .ToList();                         // als Liste.
                MöglicheBuchstabenFolgen.Sort((Folge1,Folge2)=>Folge2.Length.CompareTo(Folge1.Length)); // Sortiere die Liste absteigend nach ihrer Länge.

                /* Wir gehen die Buchstabenfolgen mit dem gesuchten Anfang,
                 * die der Länge nach, absteigend sortiert sind,
                 * eine nach der anderen durch und überprüfen,
                 * ob sie sich ganz an der Stelle i in unserem Wort finden.
                 * Wenn die erste (längste) Folge identifiziert wird,
                 * brechen wir die Suche ab und ersetzen.
                 * Andernfalls suchen wir weiter.*/
                bool Gefunden = false; // 
                foreach (string Buchstabenfolge in MöglicheBuchstabenFolgen)
                {
                    if (Wort.Length-i >= Buchstabenfolge.Length && Wort.Substring(i, Buchstabenfolge.Length) == Buchstabenfolge)
                    {
                        Aussprache += LexionDerBuchstabenErsetzung[Buchstabenfolge]; // X += Y ist X = X + Y
                        Aussprache += ' '; //nicht das Leerzeichen vergessen!
                        i +=  Buchstabenfolge.Length;
                        Gefunden = true;
                        break; // Ein Ersatz gefunden, wir suchen nicht mehr weiter!
                    }
                
                }
               if (!Gefunden) i += 1; // Unbekannter Buchstabe, zähle weiter und tue so als sei nichts geschehen.

            }
       
            return Aussprache;
        }

Jetzt wollen wir endlich austesten, was unsere Methode FindeAussprache leisten kann und deshalb ändern wir LeseZeile so ab, dass für jedes Wort, welches sich nicht als Lexikoneintrag finden lässt, FindeAussprache aufgerufen wird:

        public  void LeseZeile (string Zeile, PromptBuilder pb)
        {

            /* Los geht's Hedda!
             * Lies Wort für Wort.
             * Überprüfe, ob das Wort im Lexikon vermerkt wurde.
             * Wenn ja, dann folge der dort in Lautschrift notierten Aussprache.
             * Wenn nicht ... mach's wie immer, sprich so, wie es da "geschrieben" steht.*/

            foreach (string Wort in Zeile.Split(' '))
            {
                if (Lexikon.Keys.Contains(Wort))
                {
                    //Sonderfall, der im Lexikon gefunden wurde:
                    pb.AppendSsmlMarkup(@"<phoneme alphabet=""ups"" ph=""" + Lexikon[Wort] + @""">"// Die Lautschriftvariante
                                        + Wort + "</phoneme>");//  gefolgt von der Graphie  
                } 
                else
                {
                    // Unbekanntes Wort, ermittel den Ausspracheregeln folgend die korrekte Lautung:
                    string Lautung = FindeAussprache(Wort);
                    if (Lautung !="")
                    pb.AppendSsmlMarkup(@"<phoneme alphabet=""ups"" ph=""" + Lautung + @""">"// Die Lautschriftvariante
                                        + Wort + "</phoneme>");//  gefolgt von der Graphie  
                }
            }
        }

Ab jetzt liest uns Hedda Wort für Wort wirkliches Mittelhochdeutsch vor! Denn was wir zu hören bekommen, ist nicht mehr ein Hochdeutsch, in dem wir nur die in dieser Aussprache zu merkwürdigen Lautungen ersetzt haben:

Der Höreindruck erinnert uns in erschreckender Eindringlichkeit daran, dass das Lexikon der Buchstabenersetzung bisher nur eine Liste von Konsonanten, einen Vokal und drei Diphthonge enthält. Es ist Zeit, es auszubauen! Dabei kann uns Hedda eigentlich helfen, indem sie über die ihr fremden Buchstaben … ähm … Buch führt, anstatt einfach still über sie hinwegzuzählen. Wir richten ihr dazu eine Liste ein, in die sie diese Zeichen einträgt und ändern den Beginn der Klasse Hedda.cs entsprechend ab:

   class Hedda
    {
        // Die Liste der von Hedda in Text vorgefundenen unbekannten Buchstaben:
        public List<char> UnbekannteZeichen = new List<char>();

        // Ein Lexion zur Aufnahme der abweichend auszusprechenden Wortformen:
        private Dictionary<string, string> Lexikon = new Dictionary<string, string>
        {
            ["rehtiu"] = "S1 R EH C . T Y lng",
            ...

Ebenso ändern wir ihr Verhalten in FindeAussprache, wenn sie auf diese Fremdkörper trifft.
Aus:

               if (!Gefunden) i += 1; // Unbekannter Buchstabe, zähle weiter und tue so als sei nichts geschehen.

machen wir:

               if (!Gefunden)
               {
                    // Unbekannter Buchstabe
                    // Falls noch nicht in der Liste der unbekannten Zeichen,
                    // trage ihn ein.
                    if (!UnbekannteZeichen.Contains(Wort[i])) UnbekannteZeichen.Add(Wort[i]);
                    i += 1; // Zähle Weiter
               }

Diese Buchstaben kann uns Hedda selber vorlesen, denn (neuhochdeutsches) Buchstabieren beherrscht sie auch. Da sie aber nicht mit jedem Zeichen zurechtkommt, lassen wir uns die Buchstaben nach einer Anpassung von Main in Program.cs auch in Schriftform im Konsolenfenster anzeigen:

        static void Main(string[] args)
        {
            SpeechSynthesizer synth = new SpeechSynthesizer(); 
            PromptBuilder pb = new PromptBuilder();
            Hedda Hedda = new Hedda();

            //Unsere Textdatei: 
           
            StreamReader Textdatei = new StreamReader("D:/Lanzelet 1 - 49.txt");

            string Zeile;

            /* Schleife:
             * Solange bis das Ende der Textdatei erreicht ist (der Befehl ReadLine() null ergibt)
             * Lies die nächste Zeile ein
             * Und übergib sie an Hedda zum Vorlesen.
             */
          
            while (( Zeile = Textdatei.ReadLine())!=null)
            {
                Hedda.LeseZeile(Zeile, pb); 
            }
           
            synth.Speak("Ich habe folgende mir unbekannten Zeichen gefunden:");
            Console.WriteLine("Ich habe folgende mir unbekannten Zeichen gefunden:");

            foreach (char c in Hedda.UnbekannteZeichen)
            {
                Console.Write(c); // Schreibe die Hedda unbekannten Zeichen ins Konsolenfenster .
                synth.Speak(c.ToString()); // und lass Hedda die Buchstaben sprechen.   
            }
            Console.ReadKey(); //Warte auf eine Zeicheneingabe, bevor Hedda mit dem Lesen des Textes beginnt.

            synth.Speak(pb);  
        }

Hedda kann uns nur helfen, Einzelbuchstaben, die sie nicht kennt, zu identifizieren. Die von ihr angegebene Liste der fehlenden mittelhochdeutschen Schriftzeichen ergänzen wir darum direkt um die Lautungen der mittelhochdeutschen Diphthonge und anderer Mehrbuchstabenkombinationen und tragen die Lautungen in unser LexikonDerBuchstabenErsetzung ein:

Dictionary<string, string> LexionDerBuchstabenErsetzung = new Dictionary<string, string>
{
            ["î"] = "I lng",
            ["iu"] = "Y lng",
            ["ei"] = "E lng + IH",
            ["ie"] = "I lng + EH",
            ["a"]="AA",
            ["â"]="A lng",
            ["ä"]="A",
            ["ae"]="EH lng",
            ["æ"] ="EH lng",
            ["c"]="K",
            ["ch"]="X",
            ["e"]="EH",
            ["ê"]="E lng",
            ["i"]="IH",
            ["o"]="O",
            ["ô"]="O lng",
            ["oe"]="EU",
            ["œ"] ="EU",
            ["ö"]="OE",
            ["ou"]="AO + UH",
            ["öu"]="OI",
            ["ph"]="P + F",
            ["sc"]="SH",
            ["sch"]="SH",
            ["u"] = "UH",
            ["û"]="U lng",
            ["uo"]="U lng + O",
            ["ü"]="YH",
            ["üe"]="Y lng + EH",
            ["w"]="v",
            ["v"]="f",
            ["z"]="T + S",
            ["zz"]="Z"
};

Bevor wir es vergessen: Hedda führt in der Liste der von ihr nicht erkannten Buchstabenzeichen auch alle Großbuchstaben auf, die es im Textausschnitt gibt. Ob ein Buchstabe groß oder klein geschrieben wird, kann man jedoch nicht hören! Wir wandeln deshalb einfach mit ToLower() jede komplette Verszeile, bevor wir sie in Worte aufspalten, komplett in Kleinbuchstaben um. Der Beginn der Leseschleife in der Methode LeseZeile der Klasse Hedda.cs lautet danach:

           foreach (string Wort in Zeile.ToLower().Split(' '))

 

Jetzt können wir die Taste F5 betätigen und gespannt die Ohren spitzen. Diesmal ist es nun wirklich, wirklich wahr: Was aus unseren Lautsprechern ertönt, ist zwar noch nicht perfekt, aber zum ersten Mal klingt es tatsächlich nach Mittelhochdeutsch! Es sollte sogar möglich sein, zu verstehen, was Hedda vorliest:

Wenn sich unsere Euphorie ein wenig gelegt hat, werden wir spätestens beim zweiten Hören noch zahlreiche kleine „Baustellen“ entdecken. Wir könnten uns allerdings deutlich besser auf diese Probleme konzentrieren, wenn Hedda nicht ganz so atemlos durch den Text hasten würde. Wir drosseln deshalb ihr ungestümes Tempo etwas, indem wir nach jeder von ihr gelesenen Verszeile eine kleine Pause einfügen. Die Leseschleife in der Methode Main der Datei Program.cs lautet also nun:

            while (( Zeile = Textdatei.ReadLine())!=null)
            {
                Hedda.LeseZeile(Zeile, pb);
                pb.AppendBreak(PromptBreak.Small);
            }

Die Länge der Pausen haben wir relativ angegeben, es bleibt Hedda überlassen, was sie unter Small im Gegensatz etwa zu ExtraLarge versteht. Etwas mehr als eine halbe Sekunde pro Zeile braucht Hedda auf meinem Computer nun zusätzlich, um ans Ende ihres Vortrags zu gelangen, und für meine Ohren sind das (insgesamt) sehr wohltuende 27 Sekunden:

Es fällt nun leichter, die Stellen auszumachen, an denen ihr Vortrag unseren Ansprüchen noch nicht entspricht. Zwei Phänomene fallen besonders auf:

  • Für ein merkwürdiges Zischeln sorgt Heddas Aussprache eines ch (IPA: [x], UPS: [X]) nach den hellen Vokalen zum Beispiel in ich oder mich.
  • Ein z nach Vokalen, beispielsweise in daz, spricht Hedda wie in Batzen. Die Aussprache als tz (IPA: [t.s], UPS: [T + S])  sollte für den Buchstaben z im Mittelhochdeutschen aber eigentlich nur nach Konsonanten oder am Anfang eines Wortes (ze) verwendet werden. Nach Vokalen wird das z  nicht als tz gesprochen, sondern als stimmloses s (IPA: [z], UPS: [Z]).

Das Zischeln bei Heddas Aussprache eines ch nach den hellen Vokalen, scheint darauf zurückzuführen, dass sie nun mal für die Aussprache des modernen Hochdeutschen vorgesehen ist. Selbst ausgebildete Mediävisten, sofern sie mit der neuhochdeutschen Muttersprache aufgewachsen sind, müssen sich sehr konzentrieren, um den hellen Vokalen den weiter hinten am Gaumen gebildeten Laut folgen zu lassen. Wir können Heddas Akzent akzeptieren, wenn wir an sie nicht den Anspruch stellen, eine bessere Aussprache als die meisten Experten zustande zu bringen. Dann aber können wir für die beiden uns auffällig gewordenen Probleme ihrer Aussprache dieselbe Ursache feststellen: Beide Male müsste sie den Laut vor der problematischen Stelle berücksichtigen, um Fehler zu vermeiden.

Um zu erfahren, ob vor einem z ein Vokal in dem von Hedda gelesenen Wort vorkommt, können wir zum Beispiel fragen, ob dieser Buchstabe in der Zeichenkette „aouâôûiîeêöüäœæ“ enthalten ist. Diese enthält alle Buchstaben, die zur Wiedergabe von Vokalen verwendet werden. Darunter sind natürlich auch die, die in Kombination einen Diphthong ergeben. Streichen wir aus dieser Zeichenkette der Vokale diejenigen, die wir als dunkel einstufen, bleiben selbstverständlich nur die zurück, nach denen Hedda von uns aus ein UPS [C] sprechen darf. Wie ist es jedoch hier mit den Diphthongen?

Wir dürfen nicht vergessen, dass es diese Unterscheidung im Mittelhochdeutschen ja gar nicht gibt und wir nur notgedrungen ein neuhochdeutsches Phänomen dulden. Dort aber gilt, dass Diphthonge, die in einem hellen Vokal enden, auch die „helle“ Aussprache des chs herbeiführen. Wir können also die reduzierte Zeichenkette zur Abfrage prinzipiell verwenden. Mit ihr erfassen wir jedoch nicht die Buchstabenkombination iu, die ja keinen Diphthong, sondern ein langes ü wiedergibt. Und dieses ist ebenfalls als heller Vokal zu betrachten. Eine Änderung in FindeAussprache können wir dann so formulieren:

                   if (Wort.Length-i >= Buchstabenfolge.Length && Wort.Substring(i, Buchstabenfolge.Length) == Buchstabenfolge)
                    {
                        string Ersatz = LexionDerBuchstabenErsetzung[Buchstabenfolge];
                        if (Ersatz=="X")
                        {
                            if (i - 1 >= 0 && "iîeêöüäœæ".Contains(Wort[i - 1])) Ersatz = "C";
                            if (i - 2 >= 0 && Wort.Substring(i-2,2)=="iu") Ersatz = "C";
                        }

                        if (Ersatz=="T + S")
                        {
                            if (i - 1 >= 0 && "aouâôûiîeêöüäœæ".Contains(Wort[i - 1])) Ersatz = "Z"; 
                        }
                        Aussprache += Ersatz; // X += Y ist X = X + Y
                        Aussprache += ' '; //nicht das Leerzeichen vergessen!
                        i +=  Buchstabenfolge.Length;
                        Gefunden = true;
                        break; // Ein Ersatz gefunden, wir suchen nicht mehr weiter!
                    }

Wenn wir eine Buchstabenfolge oder einen Buchstaben in einem Wort identifiziert haben, zu der ein UPS-Code im LexikonDerBuchstabenErsetzung existiert, speichern wir den UPS-Code jetzt in der Zeichenkette Ersatz. Falls diese einen Wert enthält, für den wir den Kontext (= die Buchstaben davor) überprüfen müssen, tun wir das und ersetzen gegebenenfalls den Inhalt von Ersatz, bevor wir Ersatz an die Zeichenkette Aussprache anhängen.

Wir nutzen bei unserem Vorgehen erneut, dass nicht nur UPS oder IPA Lautschriften sind, sondern auch das normierte Verschriftungssystem des Mittelhochdeutschen. So können wir einen Zeichenkette aus mittelhochdeutschen Buchstaben bilden, von denen jeder einzelne ein Vokalzeichen repräsentiert, das wir abfragen können. In UPS werden für die meisten von uns abgefragten Vokale hingegen mehrere Buchstaben verwendet. Wir müssten uns also ein anderes Verfahren einfallen lassen, das vermutlich mehr Zeilen Programmcode benötigen würde. Umgekehrt haben wir in Ersatz ein Zeichen oder eine Zeichenkette in UPS-Kodierung zur Verfügung. Das nutzen wir bei der Abfrage des Lautes, der in UPS durch ein einziges [X], im mittelhochdeutschen jedoch durch zwei Zeichen ch wiedergeben wird.

Doch diese Sicht beinhaltet (noch) einen Fehler! Denn das Mittelhochdeutsche verwendet nicht nur ein ch um den UPS-Wert [X] abzubilden! Gleich im zweiten Wort unseres Textes, rehtiu, wird dieser Laut durch ein einfaches h wiedergegeben. Wir haben dieses Wort bereits früher für unsere Experimente genutzt und seit damals ist es in unserem Lexikon mitsamt der von uns ermittelten Lautung enthalten. Denjenigen, denen zu diesem Zeitpunkt bereits aufgefallen ist, dass diese Lautung eigentlich (das neuhochdeutsche ch!) nicht ganz korrekt ist, darf jetzt gratuliert werden. Nicht nur in rehtiu sondern ganz allgemein gilt, dass die Abfolge ht (Weiteres Beispiel: gevohten) stets als cht zu lesen ist, beziehungsweise je nach vorangehendem Vokal neuhochdeutsch als (in UPS-Kodierung) [X T] oder [C T]. Genauso verhält es sich bei hs (Beispiel: wehsel). Auch die Buchstabenfolgen lh (welhes) und rh (vorhten) sind als lch und rch zu lesen. Für sie gelten im Neuhochdeutschen stets UPS: [R C] und [L C]. Wir können unser LexikonDerBuchstabenErsetzung entsprechend ergänzen (die letzte Zeile einer Lexikondefinition darf nicht mit einem Komma abgeschlossen werden!):

            ["zz"]="Z", // hier ein Komma eintragen!!
            // ht,lh,rh
            ["ht"]="X T",
            ["hs"]="X T",
            ["lh"]="L C",
            ["rh"]= "R C"

Damit auch niht und wehsel ohne ein Zischen zu vernehmen sind, ändern wir eine Zeile in FindeAussprache ab.

Aus:

if (Ersatz == "X")

machen wir:

if (Ersatz[0]=='X')

Wer will kann das Lexikon übrigens noch um folgende Werte ergänzen, die Hedda ein r nach Vokalen „hochdeutscher“ aussprechen lässt:

            // deutsche r-Laute:
            ["ür"]="UYX",
            ["or"]="OWX",
            ["ör"]="OE + AX",
            ["oer"]="EU lng + AX",
            ["œr"] = "EU lng + AX",
            ["er"]="EHX",
            ["ûr"]="UAX",
            ["aer"] = "EH lng + AX",
            ["ær"] = "EH lng + AX",

Und dann hören wir uns Heddas verbesserten Vortrag an:

Wir sind jetzt beinahe zufrieden, aber bevor wir aus Heddas Lesung ein Hörbuch machen, wollen wir noch ein paar  weitere „Schönheitkorrekturen“ vornehmen, die vorrangig die Betonung betreffen.

… im sechsten Teil unseres Projektes.

 

Veröffentlicht von

Doktor Tom

Klar, Studium der Computerlinguistik und Germanistik mit Spezialisierung auf das Mittelhochdeutsche. So gesehen Digital Humanist. Vor allem aber Tüftler, der Spaß an ungewöhnlichen Aufgabenstellungen und einfachen, aber effizienten Lösungen hat.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

I accept that my given data and my IP address is sent to a server in the USA only for the purpose of spam prevention through the Akismet program.More information on Akismet and GDPR.