Mein erstes mittelhochdeutsches Hörbuch – Teil 6

Dies ist der sechste Teil des Tutorials. Der Beginn findet sich hier.

Bitte nochmal, mit mehr Gefühl!

Es gibt noch ein paar Aufgaben, denen wir uns widmen wollen:

  1. In einigen wenigen Fällen nimmt Hedda unsere Regeln zu genau und spricht deshalb Wörter mit einer falschen Aussprache aus.
  2. Ansonsten ist ihre Aussprache der mittelhochdeutschen Laute weitgehend korrekt, merkwürdig klingen allerdings ihre Akzentuierungen: Oft betont sie Wörter auf der falschen Silbe und manchmal wird es dann sehr schwer, den Sinn eines Verses zu verstehen.
  3. Wir werden Hedda sicher nicht beibringen können, den Text emotional zu interpretieren, aber etwas weniger monoton und seelenlos könnte ihr Vortrag doch sein.
  4. Schließlich die Satzzeichen: Bevor sie zu lesen beginnt, zeigt uns Hedda weiterhin vier Zeichen an, die ihr unbekannt sind. Es handelt sich um die Satzzeichen Punkt, Komma, Doppelpunkt und Semikolon. Wir sollten uns entscheiden, ob sie die weiterhin überlesen soll.

Auf diesen letzten Punkt können wir als erstes eingehen. Selbstverständlich handelt es sich bei den Satzzeichen, die wir im Text vorfinden um Eingriffe „moderner“ Editoren (des 19. Jahrhunderts). Im mittelalterlichen Manuskript waren sie nicht vorhanden. Die neuzeitlichen Bearbeiter geben durch die Interpunktion Fingerzeige, die beim Textverständnis helfen sollen. Wir müssen nicht zu kritisch sein und ihre Maßnahmen in Frage stellen, sondern können uns für unsere Zwecke dem Sachverstand der Experten anvertrauen. Die nun einmal im Text vorhandenen Zeichen können an Hedda weitergeben werden, sobald wir sie in die UPS-Notation überführt haben. Je nach dem für Hedda voreingestellten kulturellen Muster (Hochdeutsch/Deutschland), wird sie ihre Satzbetonung an der Interpunktion ausrichten. Bei einem Fragezeichen am Satzende hebt sie die Stimme, bei einem Punkt wird sie sie senken.

Wir fügen also unserem LexikonDerBuchstabenErsetzung ein paar Einträge hinzu. Auch wenn diese in unserem Textausschnitt gar nicht vorkommen, geben wir auch (für zukünftige Verwendungen?) die UPS-Notation für Frage- und Ausrufezeichen an. Doppelpunkt und Semikolon sind hingegen zu häufig im Textausschnitt verwendet worden, als dass wir sie ignorieren wollen. Da für sie kein UPS-Äquivalent existiert, wählen wir nach Belieben einen Ersatz, mit dem wir für uns die Frage beantworten, wie sich ein Doppelpunkt anhört (Wie ein Punkt):

            // Satzzeichen:
            ["."]="_.",
            [","]="_,",
            [":"]="_.",
            [";"]="_,",
            ["?"]="_?",
            ["!"]="_!",

Nachdem wir uns (ein wenig) um die Satzmelodie gekümmert haben, widmen wir uns als nächstes der Wortbetonung. Daran, dass das Wortformen des Deutschen meist anfangsbetont sind, hat sich seit mittelhochdeutscher Zeit nichts geändert. Wenn wir einfach jedem Wort, das wir Hedda zu lesen geben, gleich zu Beginn von FindeAussprache die Zeichenfolge [S1 ] voranstellen, wirkt Hedda Lesung druckvoller und energischer, ansonsten ändert sich jedoch nur wenig:

public string FindeAussprache(string Wort)
        {
            string Aussprache = "S1 ";

Noch deutlicher sind jetzt allerdings die Wörter auszumachen, für die sie eine völlig falsche Akzentuierung wählt. Sie übertreibt! Uns erinnert sie damit möglicherweise an jemand, der gerade eine Fremdsprache erlernt und sich jetzt ein wenig zu sehr bemüht, in der ihm fremden Rhythmik zu sprechen. Am Auffälligsten hören wir die Fehler bei Wörtern mit einer Vorsilbe, die als Präfix fungiert: ge-, be-, ver-, zer- und so weiter:

Um ihr das auszutreiben, werden wir bei jedem Wort überprüfen, ob eine der als Vorsilben gebräuchlichen Buchstabenfolgen an seinem Anfang zu finden ist. Es bietet sich an, die potentiellen Vorsilben in einer Liste zu sammeln. Wir sollten jedoch überlegen, ob wir nicht wieder ein Lexikon verwenden, in dem Hedda dann gleich die für sie lesbare Lautschrift nachschlagen kann. Die erneute Verwendung eines Lexikons mag auf den ersten Blick überflüssig erscheinen. Haben wir uns  nicht bereits darum gekümmert, alle notwendigen Informationen bereit zu stellen? Welche Laute in den potentiellen Vorsilben enthalten sind, ermitteln wir problemlos, indem wir jeden ihrer Buchstaben im LexikonDerBuchstabenErsetzung nachschlagen. Und mit welcher Betonung die präfigierten Worte zu lesen sind, dürfte sich sehr leicht bestimmen lassen.  Die vor das Wort gesetzten Silben werden nicht betont, der Hauptakzent liegt auf der folgenden (Stamm-) Silbe. Dieses Schema sollte sich auch ohne ein weiteres Lexikon automatisieren lassen.

Dieser Einwand ist berechtigt. Während wir jedoch weitere Tipparbeit durch den Einsatz eines Lexikons haben, ersparen wir Hedda bei jeder Verwendung desselben Arbeit. Arbeit, die sich in Zeit umrechnen lässt! Davon abgesehen, wird tatsächlich das e in einer Nebensilbe anders (murmelnder) ausgesprochen, als in einer, die Bedeutung und Betonung trägt. Dem können wir so Rechnung tragen. Außerdem erleichtert uns ein Lexikon den Umgang mit dem gar nicht so seltenen Sonderfall, der eintritt, wenn das verneinende un- vor ein Wort tritt, das bereits ein Präfix besitzt (un-be-kant). In diesen Fällen wird un- mit einer Betonung gelesen (ún-be-kánt), ansonsten wie die anderen Präfixe (mit Ausnahmen!) unbetont (un-réh-te). Wir begnügen uns mit den Präfixen, die wir in unserem Text finden und erstellen folgendes Lexikon, das wir in die Klasse Hedda.cs einfügen:

 private Dictionary<string, string> LexikonDerPräfixe = new Dictionary<string, string>
        {
            ["be"] = "B EX",
            ["ge"] = "G EX",
            ["ver"] = "F EHX",
            ["zer"] = "T + S EHX",
            ["en"] = "EX N",
            ["unbe"] = "S1 UH N . B EX",
            ["unge"] = "S1 UH N . G EX",
            ["unver"] = "S1 UH N . F EHX",
            ["unzer"] = "S1 UH N . T + S EHX"

        };

Der Punkt [.] markiert in der UPS-Lautschrift eine Silbengrenze. [S1 UH N . B EH] gibt demnach an, dass unbe aus zwei Silben besteht, von denen die eine mit der Lautung [UH N], die zweite mit [B EX] wiedergeben wird. Auf der ersten der beiden liegt eine Betonung [S1]. Falls Hedda in FindeAussprache eine Vorsilbe identifiziert, kann sie folgende Regel anwenden, mit der sie unsere Defaultfüllung von Aussprache (Aussprache=“S1 „) überschreibt, die für alle Wörter im Text eine Anfangsbetonung annimmt:

Aussprache = LexikonDerPräfixe[Präfix] + " . S1 ";

Wir haben somit die Aussprache zu Beginn eines Wortes ermittelt und festgelegt, dass die nächste, folgende Silbe betont auszusprechen ist. Mit dieser können wir jetzt fortfahren und nun die UPS-Kodierung für die Buchstaben im Wort hinter dem Präfix ermitteln:

Wort = Wort.Substring(Präfix.Length);

Vorsicht!!

ez ist mîn bete und ouch mîn rât,

Der Blick in den Text lehrt uns, dass nicht in jedem Wort, das mit be beginnt, be als Präfix verstanden werden darf! bete wird auf der ersten Silbe betont. Andere mittelhochdeutsche Worte beginnen ohne jede Präfigierung mit einem  ge: geste, gern.

Einige sehr häufige Wortformen dieser Art nehmen wir in das Lexikon auf, in welchem Hedda ganze Wörter und die ihnen zugeordnete Aussprache findet. Weiterhin werden wir eine Minimalzahl für die Buchstaben festlegen, die auf die Vorsilbe im Wort folgen. Und wir verlangen, dass dieses mindestens eine weitere Silbe enthält. Mit diesen beiden Methoden wird es uns nicht gelingen, alle Fehllesungen auszumerzen. Durch eine Zählung der Buchstaben und der Silben eines Wortes können wir aber in jedem Fall eine Vorauswahl vornehmen:

Kein Wort kann eine Vorsilbe enthalten, die länger ist, als es selber und einsilbige Wörter enthalten mit Sicherheit keine Vorsilbe!

Um die Silben zählen zu können, greifen wir auf eine weitere Bibliothek von C# zurück, die wir in Hedda.cs einbinden. Wir fügen eine using-Anweisung am Anfang der Datei hinzu:

 using System.Text.RegularExpressions;

Wir können jetzt sogenannte reguläre Ausdrücke benutzen. Diese sind in einer speziellen Syntax verfasst, deren Aufgabe es ist, Muster für Zeichenketten zu beschreiben. Wir können mit ihrer Hilfe in einem Text nach allen Buchstaben oder Buchstabenfolgen suchen, die dem gewünschten Muster entsprechen. Um beispielsweise anzuzeigen, dass aus einer Gruppe von Zeichen jedes gleichermaßen meine Suchkriterien erfüllt, schließe ich die Gruppe in eine eckige [] Klammer ein. In einer geschwungenen {} Klammer kann ich sodann angeben, wie viele dieser Zeichen mindestens und höchstens hintereinander vorkommen sollen. Die Silbenzahl können wir auf folgende Weise schnell mit einem regulären Ausdruck ermitteln:

       private int Silbenzahl(string Text)
        {
            Regex re = new Regex(@"[aâeêiîoôöuûüáéíóúàèìòùæœ]{1,2}");
            return re.Matches(Text).Count; 
        }

Jede Silbe, so unsere Überlegung, enthält mindestens einen Vokal oder eine Abfolge von maximal zwei Vokalzeichen. Wir zählen, wie oft Vokale oder Vokalfolgen in einem Text vorkommen, um zu erfahren, wie viele Silben er enthält. Bei Wörtern wie leier oder meien werden von uns korrekt zwei Silben gezählt.

Mithilfe eines regulären Ausdrucks können wir auch leicht die Präfixe in einem Wort bestimmen. Durch ^ signalisieren wir, dass wir nur Zeichen am Beginn eines Textes in unsere Suche aufnehmen. Um anzuzeigen, dass mehre Abfolgen von Buchstaben gleichwertig unsere Suchkriterien erfüllen, stellen wir zwischen sie ein |, dieses wird als (logisches) oder interpretiert. Mit ^ver|^zer suchen wir also nach ver oder zer am Anfang eines Wortes. Wenn wir den Ausdruck klammern, können wir das einleitende Zeichen auf den gesamten Inhalt der Klammer beziehen: ^(ver|zer). Die Klammerung erfüllt noch einen weiteren Zweck: Wenn wir später wissen wollen, welche Zeichenfolge denn tatsächlich in einem Wort vorkam, auf das wir die Suche angewendet haben, ist diese für uns in einer Liste gespeichert. Um bei mehreren Klammerungen den Überblick nicht zu verlieren, können wir den Elementen der Liste sogar einen Namen geben. Mit ^(? <Präfix>ver|zer) haben wir unserem Suchergebnis den Namen Präfix zugeordnet.

Den regulären Ausdruck müssen wir nicht komplett per Hand eingeben. Mit Join() lassen sich Elemente einer Liste zu einer Zeichenkette zusammenführen, die durch ein Zeichen, das wir frei bestimmen, getrennt sind. Der Suchauftrag wird linear ausgeführt. Wir wollen sicherstellen, dass zuerst auf unbe geprüft wird und nur, wenn dieses nicht am Anfang des Wortes steht, auch auf un oder be. Die Sortierung der Stichwörter unseres Lexikons nach ihrer Länge führen wir nach dem bekannten Schema durch. So erhalten wir den passenden regulären Ausdruck für unsere Suche nach den präfigierenden Vorsilben:

List<string> Präfixe = LexikonDerPräfixe.Keys.ToList();
Präfixe.Sort((Präfix1, Präfix2) => Präfix2.Length.CompareTo(Präfix1.Length));
string Suche = "^(?<Präfix>"+String.Join("|",Präfixe)+")";

Ganz ähnlich können wir auch am anderen Ende der Wörter verfahren: Mehrsilbige mittelhochdeutsche Wörter enden meist in einer unbetonten Silbe, deren Silbenkern als e geschrieben, aber als Murmellaut gesprochen wird. In den ersten fünf Versen unseres Textes finden wir als Beispiele: gemer-ken geden-ke wî-se al-ten zî-ten vol-ge dûh-te.

Jede dieser Silben wird von einem Konsonanten eingeleitet, es ist aber in Worten wie lei-er auch möglich, dass sie einen offenen linken Rand aufweist. Als mögliche Silbenendungen kommen außerdem noch ec, es, ez, er, ers, ern, ent, et, el und els in Frage. Wir wollen uns diese Möglichkeiten veranschaulichen, bevor wir unsere Ersetzungsroutine implementieren:

Endsilbe
Wort Wortbeginn linker Rand Murmellaut rechter Rand
alte al- t e
ziten  zi-  t  e  n
hoerent hoe- r e nt
leier lei- e r

Die Abfrage am Ende des Wortes gestaltet sich etwas schwieriger, als am Anfang. Wir können sie sinnvollerweise erst vornehmen, nachdem wir die Laute der Aussprache, inklusive des zu ersetzenden e, ermittelt haben. Unser regulärer Ausdruck operiert also auf der UPS-Notation. Die Neuformulierung der Aussprache für das Wort werden wir größtenteils aus Elementen, die wir mithilfe des regulären Ausdrucks in der Ausgangszeichenkette ermitteln, zusammen“basteln“. Wir werden wieder benötigte Wortbestandteile deshalb unter einem von uns festgelegten Namen erfassen.  Nicht vergessen dürfen wir schließlich die Möglichkeit, dass am Ende des Wortes ein Satzzeichen steht.  Folgender reguläre Ausdruck erfüllt all unsere Bedingungen und Wünsche:

(?<Anfang>.+[^\.\+] )(?<Ei>E lng \+ IH )?(?<Links>(?(Ei)|(P \+ F |T \+ S |SH |[BCDFGHKLMNRSTVZ] )))EH (?<Rechts>N T |R T |L S |R S |[SZTRNK] )?(?<Satzzeichen>_[\.\,\?\!] )?$

Das ist ein Tatzelwurm, der sich vermutlich selbst ein Rätsel ist. Wir können versuchen, ihn ein wenig zu entwirren, indem wir seine Segmente durch natürliche Sprache wiedergeben und uns dabei an den Namen entlang hangeln, die wir vergeben haben:

Am Anfang eines Wortes, das unseren Bedingungen entspricht gibt es eine beliebige Abfolge von Zeichen, deren vorletztes (vor einem Leerzeichen) aber kein Punkt und kein Plus sein darf. Der Punkt dient in UPS der Silbentrennung.  Das + verbindet UPS-Zeichen zu einem gemeinsam kodierten Lautwert. Diesen wollen wir nicht trennen und damit zerstören und die Silbengrenze wollen wir ja gerade setzen. Wenn wir bei diesem Vorhaben auf einen Punkt treffen, läuft etwas falsch. . und + stellen wir einen Schrägstrich voran. Warum wir das tun, werden wir gleich erklären.

Auf den Anfang kann der Diphthong ei folgen – oder auch nicht. Das Fragezeichen hinter dem Ausdruck ist als Quantifizierer (kein- oder einmal) zu verstehen und könnte durch {0,1} ersetzt werden.

Kommen wir zum linken Rand der gesuchten Endsilbe (Links): Falls soeben ein ei  im Wort gefunden wurde, erfahren wird das durch ?(Ei), das Fragezeichen dient hier der Abfrage. Wenn die Frage bejaht wird, dann gibt es keinen linken Rand. Wenn es kein ei gibt, geht es nach dem | weiter, das für ein logisches oder steht. In diesem Fall wird der linke Rand durch einen Konsonant gebildet, der auch einmal durch mehrere, mit + verbundene, Lautzeichen bezeichnet werden kann. Da das + auch ein Quantifizierer sein kann, müssen wir durch den vorangestellten Schrägstrich signalisieren, dass wir diesmal tatsächlich das Buchstabenzeichen meinen.

Ausnahmsweise ist es nun ganz einfach: Nach dem linken Silbenrand, muss ein EH im Wort aufzufinden sein.

Rechts von diesem kann eine bestimmte Gruppe von Konsonanten oder Konsonantenabfolgen stehen. Auch der rechte Silbenrand kann allerdings leer bleiben, was wir erneut durch den Quantifizierer ? angeben.

Ach ja. Und dann könnte noch ein Satzzeichen folgen … Die Schrägstriche / sind erneut darauf zurückzuführen, dass Satzzeichen eine besondere Bedeutung in regulären Ausdrücken haben können. Dem ? sind wir schließlich oft genug begegnet.

ENDE. Nur, wenn wir jetzt am Wortende $ angekommen sind, entspricht das Wort unseren Bedingungen.

War doch gar nicht so schwer, oder? Jetzt muss nur noch aus diesen Bestandteilen eine neue Zeichenkette für Hedda gebildet werden.

Die mithilfe von regulären Ausdrücken vorgenommenen Änderungen betreffen bisher nur die mehrsilbigen Wörter des Textes. Alle Kurzwörter, also diejenigen, die nur aus einer Silbe bestehen, werden betont ausgesprochen. Nicht zuletzt deshalb wirkte Heddas letzte Lesung so kraftvoll auf uns! Sie liest mit gleichem Verve auch jede Partikel, jedes Pronomen, die Formen des bestimmten Artikel und die kurzen Präpositionen. Damit sie diese Wörter mit weniger Energie von sich gibt, erstellen wir eine Liste, die wir (per copy & paste) in die Klasse Hedda.cs einfügen:

        List<string> Kurzwörter = new List<string>
        { "vor","von","der","diu","die","daz","diz","ditz",
            "den","dem","dan","und","in","im","ir",
            "an","ze","al","ez","für","ich","er","si",
            "wol","bî","hie","wi","wie","ein","niht",
            "mîn","sîn","mich","sich","doch","noch","nâch",
            "dar","zuo","ie","mit","mê", "ouch","nu","unz",
            "als","vil","wan","gar"
        };

Dann können wir für diese Wörter in FindeAussprache den Defaultwert korrigieren:

if (Kurzwörter.Contains(Wort)) Aussprache = ""; 

Nachdem wir jetzt all diese Veränderungen an FindeAussprache vorgenommen haben, sieht die deutlich umfangreichere Methode inzwischen so aus:


        public string FindeAussprache(string Wort)
        {
            string Aussprache = "S1 "; //Default
            if (Kurzwörter.Contains(Wort)) Aussprache = ""; // Wenn das Wort nicht zu den meist unbetonten Kurzwörtern gehört!
            
            /* Wir überprüfen, ob die Möglichkeit einer präfigierenden Vorsilbe besteht.
             * Dazu muss das Wort mindestens vier Buchstaben und zwei Silben aufweisen:
             */
            if (Wort.Length>4 && Silbenzahl(Wort) > 1)
            {
                
                // Wir bauen die Suchanfrage auf:

                List<string> Präfixe = LexikonDerPräfixe.Keys.ToList();
                Präfixe.Sort((Präfix1, Präfix2) => Präfix2.Length.CompareTo(Präfix1.Length));
                string Suche = @"^(?<Präfix>" + String.Join("|", Präfixe) + ")";
                
                // Wir suchen. "Treffer" enthält die Suchergebnisse

                Regex reAnfang = new Regex(Suche);
                Match Treffer =  reAnfang.Match(Wort);

                if (Treffer.Success) // Wenn es einen Treffer gibt ...
                {
                    string Präfix = Treffer.Groups["Präfix"].ToString(); // ist dieser in Präfix gespeichert
                                                                        
                    // Erneute Überprüfung: Ist das gefundene Präfix zu lang?

                    if (Wort.Length-Präfix.Length > 2 && Silbenzahl(Wort.Substring(Präfix.Length))>0)
                    {
                        // Gut. Wir ersetzen.

                        Aussprache = LexikonDerPräfixe[Präfix] + " . S1 ";
                        Wort = Wort.Substring(Präfix.Length);
                    }  
                }
            }
           
            // Präfix oder nicht, jetzt wird die Aussprache des (Rest-)Wortes gefunden
            // Dieser Teil von FindeAussprache hat sich nicht geändert.

            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)
                    {
                        string Ersatz = LexionDerBuchstabenErsetzung[Buchstabenfolge];
                        if (Ersatz[0]=='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!
                    }
                
                }
                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
                }
            }

            // Jetzt kümmern wir uns um das Ende des Wortes:

            if (Silbenzahl(Wort) > 1)
            {
               /* Wir fragen ab, ob eine unbetonte Endsilbe existieren kann und speichern das Ergebnis
                  Der ziemlich komplizierten Abfrage in vier Variablen:
                    Anfang - speichert den Beginn des Wortes VOR der Endsilbe
                    Links - speichert den linken Rand der Silbe.
                    Rechts - entsprechend den rechten Rand.
                    Satzzeichen - nimmt ein etwaiges Satzzeichen auf.
                    */

               
                Regex reEnde = new Regex(@"(?<Anfang>.+[^\.] )(?<Ei>E lng \+ IH )?(?<Links>(?(Ei)|(P \+ F |T \+ S |SH [BCDFGHKLMNRSTVZ] )))EH (?<Rechts>N T |R T |L S |R S |[SZTRNK] )?(?<Satzzeichen>_[\.\,\?\!] )?$");

                Match Suffix = reEnde.Match(Aussprache);
               
               
                if (Suffix.Success)
                {
 
                    // Wenn unsere Suche Erfolg hatte, setzen wir das Ergebnis zusammen:
                  
                    Aussprache = Suffix.Groups["Anfang"]+"" + Suffix.Groups["Ei"] + ". "+Suffix.Groups["Links"] + " EX " + Suffix.Groups["Rechts"]+Suffix.Groups["Satzzeichen"];

                } 
        
            }
       
            return Aussprache;
        } 

Ob sich unsere Mühen gelohnt haben, können wir uns anhören, während wir den C#-Code studieren und versuchen die regulären Ausdrücke zu verstehen:

Endet ein Vers übrigens mit einem Wort, dessen letzte Silbe unbetont ist, wird er traditionell als klingend, oder (etwas chauvinistisch) als weiblich bezeichnet. Endet er hingegen auf betonter Silbe, so nennt man dies eine stumpfe, beziehungsweise männliche Kadenz. Um zu erfahren wie der Vers endet werden wir wieder einen regulären Ausdruck verwenden. Diesmal können wir wieder mit der vielleicht lesbareren mittelhochdeutschen Buchstabenschrift arbeiten:

        private bool Klingend(string Zeile)
        {
            Regex reVersende =new Regex( @"ei|[bcdfghklmnprstvwz]e[rntlsz\.,;:\?!]{0,3}$");
            if (reVersende.Match(Zeile).Success)
            {
                // Wir akzeptieren das Resultat, wenn das letzte Wort von Zeile mehrsilbig ist:
                if (Silbenzahl(Zeile.Split().Last()) > 1) return true;
            }
            return false; // Alle anderen Fälle.
        }

Wenn wir diese Methode zu Hedda.cs hinzufügen, können wir die dadurch zugängliche Information für zwei weitere kleine Verbesserungen nutzen. Als erstes entfernen wir aus Main in Program.cs die Zeile:

pb.AppendBreak(PromptBreak.Small);

Stattdessen setzen wir ganz am Ende, direkt vor der schließenden Klammer }, in der Methode LeseZeile von Hedda.cs folgende Zeile ein:

pb.AppendBreak(Klingend(Zeile)?PromptBreak.ExtraSmall:PromptBreak.Small);

Wir machen die Länge der Pause nach einem Vers dadurch jetzt abhängig vom Versausgang. Wenn er klingend endet, ist die Pause sehr kurz. Nach einer betonten Silbe ist die Pause ein bisschen länger.

Und schließlich widmen wir uns noch einmal den  Kurzwörtern: Das mittelhochdeutsche Versmaß ist gleichmäßig alternierend. Auf eine betonte Silbe folgt, abgesehen von ein paar sehr seltenen Ausnahmen, immer eine unbetonte. Und umgekehrt.

Für ein Kurzwort, das auf eine betonte Silbe fällt, bedeutet dies, das es in diesem Falle doch betont zu lesen ist. Um zu verstehen, warum dies wichtig sein kann, betrachten wir uns eine Wortform wie der. Wenn wir in  einem Text auf dieses Wort treffen, ist es meist der bestimmte Artikel. Als solcher hat es eine untergeordnete Funktion, der (Haupt-) Akzent liegt selbstverständlich auf dem Súbstantiv, dem das Wórt der vorangeht. Wenn wir unsere Betonung jedoch ändern, DER Artikel sagen, dann geben wir zu verstehen, dass wir einen ganz bestimmten, herausgehobenen Artikel meinen. dér ist dann kein Artikel mehr, sondern ein Demonstrativpronomen (in der Bedeutung: dieser, genau der).  Selbstverständlich wird der auch als  Relativpronomen mit Betonung gesprochen (der Artikel, dér). Auch in einem mittelhochdeutschen Vers kann die Betonung eines Wortes einen Bedeutungsunterschied machen und wir sollten annehmen dürfen, dass die Dichter Akzente bewusst gesetzt haben.

Wir sollten uns deshalb darum bemühen, Hedda dazu zu bringen, dem Versmaß entsprechend zu lesen. Alles, was sie dazu wissen muss, ist wie der Vers jeweils endet.  Denn am Anfang des Verses darf der Dichter ein wenig schummeln. Wenn wir jedoch vom Versende rückwärts zählen, wissen wir, dass im stumpfen Vers die zweite, vierte, sechste, im klingenden die erste, dritte, fünfte und siebte Silbe der Zeile betont sein sollte. Wer sich jetzt wundern sollte: Wir zählen selbstverständlich nach Informatikerart und  beginnen mit dem Index 0! Die Regulierung in LeseZeile lautet also:

                       if (Kurzwörter.Contains(Wort))
                        {
                            if ((SilbenImVers-Silbe) % 2 == 1 && Klingend(Zeile)) Lautung = "S1 " + Lautung;
                            if ((SilbenImVers-Silbe) % 2 == 0 && !Klingend(Zeile)) Lautung = "S1 " + Lautung;                                 
                        }

Silbe ist der Index der Silben, die Hedda bisher bearbeitet hat, inklusive des gerade bestimmten Wortes. In der Variablen SilbenImVers haben wir zuvor die Silbenzahl der Verszeile gespeichert. Mit dem Rechenzeichen % (dem Modulo) teilen wir die Differenz dieser Zahlen durch zwei und erhalten den Rest, erfahren also, ob es sich um eine gerade oder ungerade Silbenzahl vom Versende (Index: 0!) gesehen handelt. Die komplette Methode LeseZeile sieht nun so aus:

        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.*/

            int SilbenImVers = Silbenzahl(Zeile);
            int Silbe = 0;

            foreach (string Wort in Zeile.ToLower().Split(' '))
            {
                Silbe += Silbenzahl(Wort);
                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 != "")
                    {
                        if (Kurzwörter.Contains(Wort))
                        {
                            if ((SilbenImVers-Silbe) % 2 == 1 && Klingend(Zeile)) Lautung = "S1 " + Lautung;
                            if ((SilbenImVers-Silbe) % 2 == 0 && !Klingend(Zeile)) Lautung = "S1 " + Lautung;
                                       
                        }
                        pb.AppendSsmlMarkup(@"<phoneme alphabet=""ups"" ph=""" + Lautung + @""">"// Die Lautschriftvariante
                                            + Wort + "<Phoneme>");//  gefolgt von der Graphie  
                    }
                }
            }
            pb.AppendBreak(Klingend(Zeile)?PromptBreak.ExtraSmall:PromptBreak.Small);
                   
        }

Wir sind nun vorläufig am Ende unserer selbst gestellten Aufgabe angelangt. Denn jetzt müssen wir Heddas Lesung nur noch in eine Audiodatei umleiten. Außerdem werden wir noch einige kleine Aufräumarbeiten vornehmen und uns ein paar Gedanken darüber machen, wie eine Weiterführung unseres Projektes jenseits des Tutorials aussehen könnte. Doch vorerst hören wir uns an, was wir geleistet haben:

Und weiter geht es dann im siebten und letzten 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.