Mein erstes mittelhochdeutsches Hörbuch – Teil 4

Dies ist der vierteTeil des Tutorials. Der Beginn findet sich hier.

Von Lautschrift zu Lautschrift

Zumindest die Mediävisten unter uns sollte es kaum erstaunen (und diese dürfen deshalb auch die nächsten Absätze bis hierhin überspringen), wenn sich die in unserem Text verwendeten Graphien 1 : 1 in eine Lautschrift übertragen lassen. Denn es handelt sich ja bereits um eine solche, ein System zur Transkription gesprochener Sprache!

Die Rechtschreibung moderner Sprachen steht oft in einem verwirrenden Verhältnis zur Lautung. Diese Verwirrung ergibt sich schon beinahe zwangsläufig aus dem Konzept der Rechtschreibung selber: Die Regelung der Schreibweisen soll verbindlich und allgemeingültig sein und sich durch eine innere Logik auszeichnen, die es leicht macht, sie zu erlernen und im Gedächtnis zu behalten.

  • Das Gebot der Verbindlichkeit führt jedoch häufig dazu, dass an Schreibungen auch dann noch festgehalten wird, wenn sich die Aussprache ändert.
  • Das Gebot der inneren Logik hingegen kann in den Widerspruch zur Lautung geraten, wenn beispielsweise an der Schreibung eines deutschen Wortes auch ablesbar sein soll, zu welchem Paradigma es gehört (vgl. Rad [r a: t] – Räder).

Es wäre ein wenig überheblich, würden wir es nun so sehen wollen, als ob die mittelhochdeutsche Schreibung geringere Ansprüche an sich selber gestellt habe. Es waren andere Ansprüche. Als Mönche im Mittelalter begannen, volkssprachliche Texte aufzuzeichnen, sahen sie ihre Aufgabe primär darin, die barbarischen Laute in den lateinischen Buchstabenzeichen so wiederzugeben, das Leser in der Lage waren, sie beim Lesen wieder in gesprochene Sprache zu verwandeln. Manchmal mussten die Schreiber dabei kreativ werden, um Töne zu verschriftlichen, die dem Lateinischen unbekannt gewesen waren. Und schon deshalb entstanden irgendwann, als die volkssprachlichen Texte häufiger wurden, auch im Mittelalter Normierungen und Konventionen, die zu nicht immer unmittelbar auf der Basis des Lateins nachvollziehbaren Schreibungen führten.

Im 19. Jahrhundert versuchten Philologen das Gestrüpp zu entwirren und entwickelten aus den Gebräuchen der mittelalterlichen Schreibstuben eine eigene, künstliche Orthographie, die sie ihren Editionen zugrundelegten. So konnten sie offensichtliche Verschreibungen korrigieren, Abkürzungen auflösen und von individuellen Marotten oder lokalen Gepflogenheiten abstrahieren. Man muss bedenken, dass viele Texte nur in sehr viel späteren Abschriften bekannt sind, und dass die Gelehrten des 19. Jahrhunderts ihre Aufgabe zuallererst darin sahen, den ursprünglichen Urtext zu rekonstruieren. Sie legten also besonderen Wert gerade auf die exakte Ermittlung und Wiedergabe der Lautung, so wie sie vom Autor (ein dem 19. Jahrhundert fast schon heiliger Begriff!) einmal gemeint war.

Der Text, den wir Hedda zum Vorlesen vorsetzen, entstammt einer solchen Edition (der Digitalisierung einer solchen Edition) und selbstverständlich ist es deshalb möglich, die dort anzutreffende Schreibung in eine (andere) Lautschrift umzusetzen. Wir müssen also einfach eine Methode FindeAussprache in Hedda.cs einfügen in der wir nach und nach die mittelhochdeutschen Graphien durch Lautzeichen in der UPS-Notation ersetzen, um ein Wort unseres Textes für Hedda aufzubereiten:

        public string FindeAussprache(string Wort)
        {
            string Aussprache = Wort;
            Aussprache = Aussprache.Replace("î", "I lng");
            Aussprache = Aussprache.Replace("iu", "Y lng");
            Aussprache = Aussprache.Replace("ei", "E lng + IH");
            Aussprache = Aussprache.Replace("ie", "I lng + EH");

            /* ...
            * Weitere Ersetzungen folgen ...
            * ... bis am Ende jede mittelhochdeutsche Graphie
            * durch ein UPS-Lautzeichen ersetzt ist
            * und Aussprache von Hedda verarbeitet werden kann.
            */

            return Aussprache;
        }

Wir erzeugen eine Kopie des Ausgangswort, die wir Aussprache nennen. Dann ersetzen wir in mehreren Anläufen Aussprache immer wieder durch eine Kopie von sich selber, in der wir alle Vorkommen jeweils eines mittelhochdeutschen Buchstaben (einer Buchstabenfolge) durch die entsprechende UPS-Notation ersetzt haben. Falls das mittelhochdeutsche Ausgangswort den zu ersetzenden Buchstaben nicht enthielt, bedeutet dies, dass die neue Kopie von Aussprache identisch mit der alten ist. Wenden wir die bisher kodierten Ersetzungsbefehle nicht auf ein einzelnes Wort, sondern zum Beispiel auf die ganze zweite Zeile des Lanzelet an, dann können wir uns diese Austauschschritte etwas besser vorstellen:

der gedenke wie ein wîse man
der gedenke wie ein w I lngse man
der gedenke wie E lng + IHn w I lngse man
der gedenke wI lng + EH E lng + IHn wI lngse man

… usw. …

Doch halt! Am jeweiligen Ende der ersten vier Ersatzbefehlen haben wir das in der UPS-Notation vorgeschriebene Leerzeichen vergessen, das Hedda braucht, um die Segmente der Lautschrift zu separieren. Das müssen wir per Hand nachtragen und dürfen es in Zukunft nicht vergessen. Gut, dass uns das jetzt aufgefallen ist. Man stelle sich vor, wir hätten bereits mühsam alle unsere Ersetzungsbefehle kodiert!

Tatsächlich müssen wir jedoch damit rechnen, dass sich noch allerlei Änderungen und Korrekturen ergeben, sobald wir erste Tests mit Heddas verbesserter Aussprache des Mittelhochdeutschen anstellen. Niemand ist schließlich perfekt. Es muss aber auch gar nicht unbedingt an unserer schlechten Planung und phonetischen Vorstellungsgabe liegen, wenn Heddas Vortrag der UPS-Zeichen nicht ganz unseren Erwartungen entspricht. Wir machen uns ja erst vertraut mit der Technologie. Auch deshalb werden wir den schon einmal nützlichen Kniff wiederverwenden und uns ein weiteres Lexikon anlegen. Diesmal werden wir darin die zu ersetzenden Buchstaben und die entsprechenden UPS-Zeichen aufnehmen. So verschaffen wir uns einen besseren Überblick, reduzieren den Schreibaufwand und sind für die späteren Korrekturen gerüstet. Dann können wir FindeAussprache entsprechend abändern:

        Dictionary<string, string> LexionDerBuchstabenErsetzung = new Dictionary<string, string>
        {
            ["î"] = "I lng",
            ["iu"] = "Y lng",
            ["ei"] = "E lng + IH",
            ["ie"] = "I lng + EH"
            // Weitere Ersetzungen folgen ...
        };
        public string FindeAussprache(string Wort)
        {
            string Aussprache = Wort;
             foreach(string Grapheme in LexionDerBuchstabenErsetzung.Keys)
            {
                /* Wir gehen das Lexikon durch und
                 * ersetzen die im Ausgangswort vorgefundenen Buchstaben
                 * durch die diesen entsprechenden Einträge.
                 * Am Ende jeder Ersetzung ergänzen wir ein Leerzeichen.
                 */
                Aussprache = Aussprache.Replace(Grapheme, LexionDerBuchstabenErsetzung[Grapheme]+' ');
            }            return Aussprache;
        } 

Durch das neue LexikonDerBuchstabenersetzung haben wir uns wohl zukünftig Tipparbeit gespart. Bis wir es gefüllt haben, werden wir trotzdem noch ganz gut zu tun haben. Und einige Ersetzungen sind ziemlich redundant, unser Lexikon müsste beispielsweise auch diesen Eintrag beinhalten:

         ["t"] = "T",

oder diesen:

         ["f"] = "F",

Jeglicher Buchstabe, „der gesprochen wird, wie man ihn schreibt“, bei dem die UPS-Notation und das System zur Wiedergabe mittelhochdeutscher Laute das gleiche lateinische Buchstabenzeichen verwenden, müsste so aufgeführt werden. Selbstverständlich kann man eine solche Aufgaben automatisieren. Und selbstverständlich werden wir das tun! Eine kleine Programmschleife wird noch vor der ersten Verwendung des Lexikons die entsprechenden Befüllungen vornehmen. Wir ergänzen unsere Klasse Hedda.cs um einen Konstruktor. So wird eine Methode genannt, die bei jeder Erzeugung einer Instanz von Hedda genau einmal aufgerufen wird und Initialisierungen vornimmt. Der Konstruktor hat stets denselben Namen, wie die Klasse selber. Hedda.Hedda füllt also unser Lexikon mit den gewünschten Einträgen:

        public Hedda()
        {
            string EinfachBuchstaben = "bdfghklmnprst";
            foreach(  char c in EinfachBuchstaben)
            {
                LexionDerBuchstabenErsetzung[c.ToString()] = c.ToString().ToUpper();
            }
        }

UPS verwendet Großbuchstaben zur Kodierung. Ersetzt werden müssen nur die Kleinbuchstaben, die UPS nicht kennt. Die haben wir in einer Zeichenkette zusammengestellt und ordnen jetzt einfach jedem kleingeschriebenen Einzelbuchstaben von EinfachBuchstaben einen Eintrag zu, der denselben Buchstaben, jetzt aber großgeschrieben, enthält. Die Liste der Buchstaben ist vielleicht noch nicht ganz vollständig (wo sind beispielsweise die Vokale?), aber wir können Lücken ja nach Bedarf schließen. Und warum übernehmen wir diese praktische Methode nicht gleich ganz zur Füllung unseres Lexikons? Beispielsweise auf diese Weise:

            string KomplexereErsetzungen = "î-I lng,iu-Y lng,ei-E lng + IH,ie-I lng + EH";
            foreach(string Ersatzpaar in KomplexereErsetzungen.Split(','))
            {
                string[] Ersatz = Ersatzpaar.Split('-');
                LexionDerBuchstabenErsetzung[Ersatz[0]] = Ersatz[1];
            }

Diesmal haben wir eine Zeichenkette erzeugt, in der wir die Lexikoneinträge durch Kommata voneinander abtrennen. Jeder Eintrag besteht wiederum aus zwei Teilen: Der mittelhochdeutschen Graphie auf der einen Seite des Bindestrichs und der UPS-Lautschrift auf der anderen. Jetzt müssen wir diese Kodierung nur wieder durch intelligente Verwendung von Split() aufspalten und können unser Lexikon so befüllen. Das erste, vom Rest durch ein Komma getrennte, Paar eines mittelhochdeutschen Buchstaben und der ihm zugeordneten UPS-Zeichen ist zum Beispiel î-I lng. Das Lexikon erhält nach dem Entpacken in der span style="font-family: courier new,courier,monospace;">foreach-Schleife den neuen Eintrag:

    ["î"] = "I lng";

Dieser Eintrag sollte bekannt vorkommen. Wer aufmerksam liest, erkennt, dass es sich bei den Ersatzpaaren in der durch Kommata getrennten Liste bisher um dieselben Einträge handelt, die wir zuvor etwas konventioneller in unser Lexikon eingetragen hatten. Man muss aber wirklich aufmerksam hinsehen, denn was wir mit dieser Methode an Tipparbeit sparen, verlieren wir an Übersichtlichkeit.

Egal auf welche Weise wir unser Lexikon der Buchstabenersetzungen auch befüllen, es gibt drei Schwierigkeiten, mit denen wir uns auseinandersetzen müssen:

  1. Wie garantieren wir die Vollständigkeit der Ersetzungen?
  2. Wie gehen wir mit mehrdeutigen Buchstabenfolgen um?
  3. In welcher Abfolge ersetzen wir?

Das erste Problem ist durch Heddas Verhalten bei unkorrekten UPS-Notationen bedingt: Wenn sie nicht jedes Lautsegment der Abfolge einer im SSML-Format kodierten Zeichenkette identifizieren kann, bricht sie ihren Versuch der Interpretation ab. Wir müssen ihr also einen Text zum Sprechen vorlegen, der nur solche Zeichen enthält, die sie umsetzen kann. Das ist gar nicht so einfach, wie es erscheinen mag. Unser kleiner Textausschnitt ist relativ leicht überschaubar. Aber was ist, wenn wir Hedda das Nibelungenlied zum Lesen präsentieren? In einem Format, möglicherweise, das auch editorische Randbemerkungen enthält, in denen Buchstaben vorkommen, die nicht zum typischen Alphabet des Mittelhochdeutschen gehören? Beispielsweise könnte die Originalschreibweise eine französischen, griechischen oder italienischen Namens angegeben werden, ein linguistischer oder literaturwissenschaftlicher Fachterminus erscheinen … Wenn wir darauf hoffen sollten, Hedda auch einen Text zum Vorlesen vorlegen zu können, den wir nicht Zeile für Zeile für sie präpariert haben (… und könnten wir dann nicht einfacher selber lesen?), wird  sie zwangsläufig irgendwann auf Buchstabenzeichen stoßen, für die wir ihr keine Ausspracheregelung vorgegeben haben.

Mehrdeutige Buchstaben und Buchstabenfolgen ergeben sich aus zwei Gründen. Zum Einen kodiert ein Zeichen manchmal mehrere Aussprachevarianten, von denen aber im jeweiligen Kontext nur eine realisiert wird. Im modernen Hochdeutschen beispielsweise verwenden wir die Buchstabenfolge ch sowohl zur Bezeichnung eines sehr weit hinten am Gaumen, fast schon im Rachen produzierten Lautes ( Beispiel: lachen IPA: [x]), als auch eines viel weiter vorne am Gaumen entstehenden Zischlautes (Beispiel: Kichern IPA: [ç]). Beide Varianten des Bigraphen werden von deutschen Muttersprachlern beim Sprechen nicht durcheinandergebracht, denn [x] tritt nur hinter den „dunklen“ Vokalen a/o/u auf, [ç] hinter den „hellen“ e/i/ä/ö/ü. Das führt dann dazu, dass sich diese Stelle in Wortpaaren wie der Fluch – die Flüche, die Rache – rächen, das Loch – die Löcher bei genauem Hinhören unterscheidet. Einige deutsche Dialekte haben übrigens die Unterscheidung ein wenig weiter getrieben: Bei ihnen rutscht das ch nach dunklen Vokalen noch tiefer in den Rachen und aus dem ch nach hellen Vokalen wird ein sch.

Doch für ein Programm, das Ersetzungen nach den Einträgen eines Lexikons vornimmt, gibt es noch eine zweite Form der Mehrdeutigkeit, sobald ein ch in seinem Ausgangstext erscheint. Schließlich beinhaltet sein Lexikon auch eine Vorschrift, was es tun soll, wenn es auf ein einzelnes c oder ein einzelnes h trifft, die ja ebenfalls in einem hochdeutschen Wort vorkommen können. Welche Regel ist nun anzuwenden?  Unser Lexikon der mittelhochdeutschen Graphien enthält bisher bereits drei Einträge für Buchstabenfolgen, für eine Abfolge mehrerer Buchstaben, die zusammen gelesen und gemeinsam durch eine UPS-Notation ersetzt werden sollen. Bei ei und ie handelt es sich (mittelhochdeutsch) um Diphthonge, also Doppellaute. Die Notation durch zwei Buchstabenzeichen bietet sich durchaus an. Es macht einen wahrnehmbaren Unterschied, ob Hedda an diesen Stellen UPS-konform EH I oder E lng + IH spricht! In einer mittelhochdeutschen Wortform wie ge-irren wäre der Diphthong ei allerdings fehl am Platz. Und iu ist gar kein Doppellaut, sondern wird als langes ü ( IPA: [y:], UPS: [Y lng]) gesprochen!

Spätestens jetzt müssen wir auch darüber nachdenken, in welcher Reihenfolge die Ersetzungen vorgenommen werden sollen. Denn Wörter wie meien, leien, reien etc. sind gar nicht so selten in mittelhochdeutschen Texten. Wird auf diese Wörter als Erstes die Ausspracheregel [„ie“] = „I lng + EX“ angewendet, dann ergibt sich als Ergebnis dieses Austauschs „meI lng + EX n„. Wenn Hedda alle Ersetzungen vorgenommen hat, sollten wir danach Folgendes hören:

Doch selbst dieses Ergebnis dürfen wir nicht unbedingt erwarten, denn die Zeichenfolge lng besteht aus l n und g. Und das sind ebenfalls mittelhochdeutsche Buchstaben, die unser Programm sonst durch die UPS-konformen L, N und G ersetzen soll, so dass das Ergebnis unserer Interpretation von meien auch M E I L N G + EX N oder M E L N G + IH EH N lauten könnte:

Es verschiedene Möglichkeiten mit diesen Problemen umzugehen. Bleiben wir allerdings dabei, unsere Ersetzungen durch eine Abfolge von Replace-Befehlen durchzuführen, wird es sehr schwierig, die Kontrolle über die Ersetzungen zu behalten und eine Abfolge auszuknobeln, die zum gewünschten Ergebnis führt. Wir sollten es nicht als Kapitulation vor diesen Schwierigkeiten auffassen, wenn wir eine andere Lösung wählen, und Hedda sich von nun an brav wie eine Erstklässlerin Buchstabe für Buchstabe durch den Text arbeiten lassen. Trifft sie dabei auf ein e, wird sie solange mit der Entscheidung, wie sie dieses e aussprechen soll warten, bis sie sich auch den nächsten Buchstaben angesehen hat. Handelt es sich bei diesem beispielsweise um ein i, dann gehören die beiden Buchstaben wohl zusammen und müssen als ei ausgesprochen werden. Wenn Hedda irgendwann einmal einen ihr völlig unbekannten Buchstaben vorfindet, überspringt sie den (Wir kümmern uns später darum, versprochen!) und liest einfach weiter. Hedda frisst sich durch den mittelhochdeutschen Text und hinterlässt hinter sich ein Spur „verarbeiteter“ lautsprachlicher Zeichen, die sie uns dann später (lesend) vorsetzt:

S V E lng R R E C T Y lng 

Das ist eine Metapher, über die wir einen Moment nachdenken müssen, bevor wir uns daran machen, sie als Programmroutine zu implementieren.

… im fünften Teil des Projektes

 

Mein erstes mittelhochdeutsches Hörbuch – Teil 3

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

Der Blick ins Lexikon hilft!

Wir wollen, dass Hedda uns einen mittelhochdeutschen Text flüssig vorliest und das in möglichst korrekter Aussprache. Um beiden Anforderungen gerecht zu werden,  blieb uns nichts anderes übrig, als den gesamten Text in Lautschrift zu übersetzen und dann im Ganzen an Hedda zur Aussprache zu übergeben. Denn als wir versuchten, den Text in Einzelpassagen zu zerlegen, die wir Hedda in unterschiedlichem Format übergaben, war die Unterbrechung und der Neuansatz bei jeder Einheit deutlich und störend zu hören. Dabei könnte es doch so einfach sein: Hedda ist für die Ausgabe moderner deutscher Texte optimiert und bei diesen hat sie keine Schwierigkeiten, sie relativ unfallfrei und sogar mit so etwas wie einer angemessenen Betonung in flüssigem Vortrag vorlesen. Trifft sie dabei auf ihr unbekannte Wörter, liest sie ohne Unterbrechung so, wie es der üblichen Aussprache deutscher Buchstaben entspricht. Da sich jedoch das Mittelhochdeutsche nicht in jeder Hinsicht vom modernen Hochdeutsch unterscheidet, könnten wir häufig ihre Voreinstellungen übernehmen und müssten nur in Einzelfällen eingreifen.  Wir bräuchten dafür nur eine Möglichkeit, Hedda einen Text im Ganzen zum Lesen vorzulegen, in dem Passagen in Lautschrift auf solche in Buchstabenschrift folgen und umgekehrt.

Tatsächlich ist das möglich. Als Prompt wird in der englischsprachigen Terminologie der speech.dll eine sprachliche Äußerung verstanden und mit der Klasse Promptbuilder gibt es die Möglichkeit, eine solche Äußerung aus elementaren und heterogenen Bestandteilen zusammenzusetzen. Probieren wir es gleich aus:

        static void Main(string[] args)
        {
            
            SpeechSynthesizer synth = new SpeechSynthesizer();
            
            PromptBuilder pb = new PromptBuilder();
            pb.AppendText("Swêr");
            pb.AppendSsmlMarkup(@"<phoneme alphabet=""ups"" ph=""S1 R EH C . T Y lng"">rehtiu</phoneme>");
            pb.AppendText("wort gemerken kan,");

            synth.Speak(pb);
          
        }

 

 

Heureka! Mit dieser Technik können wir uns viel Arbeit ersparen. Eine zusätzliche Ersparnis ergibt sich durch die Verwendung eines Lexikons. In diesem vermerken wir die Sonderfälle der Aussprache für Hedda. Diese müssen wir dann nur einmal angeben und nicht bei jedem Vorkommen im Text. Wir müssen Hedda nur mitteilen, dass sie von nun an für jedes Wort im Text überprüfen soll, ob es zu diesem einen Eintrag im Lexikon der Sonderfälle gibt:

 

        static void Main(string[] args)
        {

            SpeechSynthesizer synth = new SpeechSynthesizer(); 
            PromptBuilder pb = new PromptBuilder();

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

            // unser Beispieltext:
            string Text = "Swêr rehtiu wort gemerken kan,";

            /* 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 Text.Split(' '))
            {
                if (Lexikon.Keys.Contains(Wort))
                {
                    //Sonderfall:
                    pb.AppendSsmlMarkup(@"<phoneme alphabet=""ups"" ph=""" +Lexikon[Wort]+@""">"// Die Lautschriftvariante
                                        +Wort+"</phoneme>");//  gefolgt von der Graphie
                }
                else
                {
                    pb.AppendText(Wort); // Normalfall: Hochdeutsche Aussprache.
                }
            }       

            synth.Speak(pb);  
        }
    }

 

An Heddas Vortrag hat sich nichts geändert, nur an der Art und Weise, wie er zustande kam. Deshalb sollen die Details noch einmal genauer betrachtet werden. Wir haben es diesmal Hedda, bzw. dem von uns geschriebenen Computerprogramm überlassen, den vorzulesenden Text in Wörter zu zerlegen. Wir tun das durch die Anweisung Split(' '), die der alten computerlinguistischen Faustregel folgt, dass als Wort jede Abfolge von Zeichen zwischen zwei Leerzeichen angesehen wird. Das ist selbstverständlich eine grobe Vereinfachung, die aber für unsere Zwecke erst einmal genügt. Wort für Wort schlägt das Programm dann unter den Schlüsselbegriffen mit Lexikon.Keys.Contains(Wort) im Lexikon nach und hängt gegebenenfalls den dort vorgefundenen Eintrag an das Prompt der Aussage an. Gibt es keinen solchen Eintrag, dann wird das Wort selber an Hedda zur späteren Verarbeitung weitergereicht.

Nun ist unser Lexikon mit exakt einem Eintrag nicht gerade umfangreich. Aber wir haben uns ja auch nicht die Aufgabe gestellt, nur eine einzige Verszeile in gesprochene Sprache umzuwandeln, sondern wollten uns ein ganzes Buch vorlesen lassen. Oder zumindest die ersten 50 Verse davon. Doch bevor wir daran gehen, werden wir ein wenig „aufräumen“.

Für unsere bisherigen Experimente haben wir nur ein paar Zeilen Programmiercode gebraucht, die wir in der statischen Methode Main untergebracht haben. Wir wollen jetzt aber zu komplexeren Arbeitsschritten übergehen, die Hedda erledigen soll. Und deshalb werden wir nun als nächstes eine eigene Klasse für diese Aufgaben anlegen und unserem Projekt hinzufügen. Der geben wir den Namen Hedda.cs. Dann können wir auch weiter, und jetzt mit deutlich mehr Berechtigung davon sprechen, dass wir Hedda einen Befehl geben, oder Hedda etwas im Lexikon nachschlägt:

Die automatisch generierte Klasse Hedda.cs ändern wir ab, so dass sie nun so aussieht:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Speech.Synthesis;

namespace Mittelhochdeutsches_Hörbuch1
{
    class Hedda
    {
        // 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"
        };

        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:
                    pb.AppendSsmlMarkup(@"<phoneme alphabet=""ups"" ph=""" + Lexikon[Wort] + @""">"// Die Lautschriftvariante
                                        + Wort + "</phoneme>");//  gefolgt von der Graphie
                }
                else
                {
                    pb.AppendText(Wort); // Normalfall: Hochdeutsche Aussprache.
                }
            }
        }
    }
}

 

Problemlos dürften sich die Elemente wiedererkennen lassen, die wir aus der Methode Main in Program.cs übernommen haben: Das Lexikon, die Schleife, in der wir Hedda die Worte der Zeile lesen lassen … Nur, dass wir diese jetzt in eine Methode der Klasse Hedda.cs, einen Befehl an Hedda, verpackt haben. Wichtig ist es übrigens, nicht die Änderung an den using – Anweisungen zu übersehen: Damit Hedda ihren Dienst tun kann, braucht auch sie die Speech.dll. „Unsere“ Methode Main müssen wir auch noch ändern, sie sieht jetzt so aus:

        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(pb);  
        }
[/csharp]

&nbsp;

<em><strong><span style="color: #ff0000;">ACHTUNG!</span></strong> Ich habe die Textdatei, die wir verwenden, auf meinem Rechner im Hauptverzeichnis von Laufwerk D: abgespeichert. Wenn sie auf einem anderen Laufwerk oder Verzeichnis gespeichert wurde, muss die Pfadangabe natürlich entsprechend angepasst werden.</em>

Und auch in <code><span style="font-family: courier new,courier,monospace;">Program.cs</span></code> müssen wir noch eine <code><span style="font-family: courier new,courier,monospace;">using</span></code>-Zeile einfügen, damit <code><span style="font-family: courier new,courier,monospace;">Main</span></code> die Datei einlesen kann:

[code language="csharp"]using System.IO;

Dann können wir uns entspannt zurücklehnen und unser erstes Hörbuch genießen …

Na?

Zufrieden?

Wie …? Wirklich?    … gar nicht?

Es ist wohl nicht zu leugnen: Wir haben noch etwas Arbeit vor uns!!!

Wir mögen damit zurechtkommen, wenn Hedda beim Vortrag einer einzelnen Verszeile, deren Text wir am Bildschirm mitlesen können, ein einziges Wort als völlig unverständliche Lautfolge umsetzt. Wenn wir sie jedoch eine längere Passage lesen lassen, ist es kaum noch möglich ihr zu folgen. Wir sollten uns jedoch vom ziemlich katastrophalen ersten Höreindruck nicht enttäuschen lassen. Schließlich enthält unser Lexikon ja bisher nur einen Eintrag! So kann es kaum eine Hilfe darstellen. Füllen wir es doch mit ein paar Einträgen und ändern die Klasse Hedda.cs entsprechend ab:

        // 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",
            ["gemerken"]="G EX . S1 M EH R . K EX N",
            ["gedenke"]="G EX . S1 D EH N . K EX",
            ["wie"] = "S1 V I lng + EX",
            ["ein"] = "E + I N",
            ["wîze"] = "S1 V I lng . Z EX",
            ["hie"] = "S1 H I lng + EX",
            ["bî"] = "S1 B I lng",
            ["zîten"]= "S1 TS I lng . T EX N",
            ["sît"] = "S1 S I lng T",
            ["diu"] = "D Y lng",
            ["dûhte"] = "S1 D U lng X . T EX",
            ["niht"] = "N IH C T",
            ["gemuot"] = "G EH . S1 M U lng + O T"

        };

 

Die ersten fünf Verszeilen sind nach der kleinen Änderung jetzt deutlich besser zu verstehen:

… und wenn wir uns konzentrieren, begegnen uns auch im weiteren Text einige der neuen Einträge unseres Lexikons wieder und machen diesen damit vereinzelt verständlicher.

Versuchen wir es positiv zu sehen: Wir sind auf dem richtigen Weg!

Ein Weg, der allerdings noch recht lang zu werden verspricht, wie diejenigen sicher bestätigen werden, die sich die Mühe gemacht haben, die Lexikoneinträge per Hand in Hedda.cs einzutragen und sie nicht einfach per copy & paste übernommen haben. Diesen fleißigen Tippern wird dabei jedoch vermutlich auch aufgefallen sein, dass die Umsetzung der Grapheme in Phoneme sehr regelhaft erfolgt. Als UPS-Notation der Aussprache für ein î haben wir zum Beispiel in wîze, bî, zîten und sît jeweils I lng ermittelt (IPA-Notierung: [i:]). Nach unserer bisherigen Erfahrung gilt anscheinend:

Bestimmten Buchstaben (-folgen) entsprechen stets und immer wieder bestimmte (Abfolgen von) Lautzeichen.

Wir werden versuchen aus dieser Beobachtung Nutzen zu ziehen, um uns weitere (unnötige) Tipparbeit zu ersparen. Und damit beginnen wir umgehend …

… im vierten Teil unseres Projektes.

Mein erstes mittelhochdeutsches Hörbuch – Teil 2

Dies ist der zweite Teil des Tutorials (daher heißt es auch Teil 2 im Titel). Der erste Teil findet sich hier.

Hedda spricht (eine Zeile) Mittelhochdeutsch!

Wir legen los und in Visual Studio ein neues Projekt an:

Aus dem Auswahlmenü wählen wir die Konsolen-App (Net-Framework), der wir einen sprechenden Namen geben. Wie wäre es mit „Mittelhochdeutsches Hörbuch1“? Unter welchem Namen auch immer, das Projekt wird erzeugt und uns die Datei Program.cs präsentiert:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Mittelhochdeutsches_Hörbuch1
{
    class Program
    {
        static void Main(string[] args)
        {
        }
    }
}

Bevor wir unseren Computer zum Sprechen bringen können, müssen wir noch die zur Spracherzeugung benötigte Bibliothek einbinden. Dazu öffnen wir den Reiter „Projekt/Verweis hinzufügen“.

In den Assemblys aktivieren wir „System.Speech“ mit einem Häkchen:

Dann ändern wir die Ausgangsdatei (per copy & paste oder abtippen) folgendermaßen ab und starten mit F5 unser erstes Experiment:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Speech.Synthesis;

namespace Mittelhochdeutsches_Hörbuch1
{
    class Program
    {
        static void Main(string[] args)
        {            
            SpeechSynthesizer synth = new SpeechSynthesizer();
            synth.Speak("Swêr rehtiu wort gemerken kan,");            }
        }
    }
}

 

Jetzt sollten wir eine Stimme hören, die mit der erwarteten falschen Aussprache unseren Text spricht. Es ist gut möglich, dass diese Stimme uns unbekannt ist und nicht derjenigen entspricht, die wir von der Windows-Sprachausgabe gewohnt sind. Auf meinem Rechner ist es jedenfalls eine weibliche Stimme und damit definitiv nicht Stefan, die ich höre. Der Grund hierfür ist, dass Stefan dafür vorgesehen ist, Deutsch mit mir zu sprechen und anscheinend nur dazu in der Lage ist. Ihm fehlt die Möglichkeit, sich an ein anderes Idiom anzupassen. Etwas technischer formuliert: Das Sprachprofil, das mit dem Namen Stefan verbunden wird, bietet nicht alle Möglichkeiten, die durch die Programmierschnittstelle System.Speech.dll vorgesehen sind, und steht uns damit nicht zur Verfügung. Oder vielmehr: mir, zum Zeitpunkt, an dem ich dies schreibe. Denn selbstverständlich kann sich das mit dem nächsten Windows-Update ändern oder mit der nächsten Generation der Speech-Schnittstelle.

Wir können uns, wenn wir wollen, beim nächsten Durchlauf unseres kurzen Programms die uns zur Verfügung stehenden Stimmen vorstellen lassen, indem wir unsere statische Methode Main leicht verändern:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Speech.Synthesis;

namespace Mittelhochdeutsches_Hörbuch1
{
    class Program
    {
        static void Main(string[] args)
        {
            
            SpeechSynthesizer synth = new SpeechSynthesizer();

            foreach (var voice in synth.GetInstalledVoices())
            {
                synth.SelectVoice(voice.VoiceInfo.Name);
                synth.Speak(voice.VoiceInfo.Description);
                synth.Speak("Swêr rehtiu wort gemerken kan,");            }
            }
        }
     }
}

 

Mir stehen eine weibliche Stimme mit dem Namen Hedda zur Verfügung, die deutsch spricht, und eine Amerikanerin namens Sira. Ob es sich bei der um eine Verwandte von Apples ureigener Siri handelt? Und wer hat der „Deutschen“ den so typisch deutschen Namen Hedda verpasst? Die Auswahl der Stimmen ist sehr begrenzt, aber zumindest durch die Namensgebung für einen kleinen nerdigen Lacher gut.  Wer wie ich mit Hedda leben muss (Nein, Sira kann ich mir nicht als Vorleserin der Aventiure vorstellen), mag vielleicht mit der Option käuflicher Stimmenprofile liebäugeln, aber eigentlich reicht ja eine Stimme für unser Vorhaben.

Sofern wir sie dazu bringen können, phonetisch korrektes Mittelhochdeutsch von sich zu geben.

Hedda sollte also eine Lautschrift verstehen, beziehungsweise in der Lage sein, einen in dieser transkribierten Text angemessen wiederzugeben. Genau das verspricht uns die Speech.dll, wenn sie Hedda unter den installierten – und der Norm gerechten –  Stimmen aufführt. Und genau das werden wir jetzt austesten. Wenn wir es (vorerst) nicht ganz so kritisch sehen wollen, gibt es in dieser ersten Zeile unseres Textes ja nur ein Wort, das wirklich so falsch klingt, dass wir handeln müssen: rehtiu. Die anderen Wörter sind in ihrer hochdeutschen Aussprache nicht perfekt, aber, wenn wir ein paar Augen zudrücken, halbwegs akzeptabel. Konzentrieren wir uns auf rehtiu und versuchen Hedda beizubringen, das etwas besser zu artikulieren:

 

        static void Main(string[] args)
        {
            
            SpeechSynthesizer synth = new SpeechSynthesizer();
            string ssmlRehtiu =  @"<speak version=""1.0"" xml:lang=""de-DE"">"
                                + @<phoneme alphabet=""ups"" ph=""S1 R EH C . T YH lng"">"
                                + "Rehtiu</Phoneme></speak>";
            synth.SpeakSsml(ssmlRehtiu);
        }

 

 

Das klingt doch schon besser, oder? Was wir hier benutzen, ist der SSML-Standard, ein XML-Codierungsschema zur Steuerung von Sprachausgaben (Speech Synthesis Markup Language). Wenn wir eine dafür passendere Formatierung wählen sieht das ungefähr so aus:

   <speak version="1.0" xml:lang="de-DE">
       <phoneme Alphabet="ups" ph="S1 R EH C . T YH lng"&>rehtiu</Phoneme>
   </speak>

 

Das <speak>-Tag klammert eine Sprachausgabe ein. Innerhalb des <phoneme>-Tags finden wir unseren Ausgabetext wieder, so wie wir ihn kennen („kennen“ heißt, in der Buchstabendarstellung). Die Lautschriftfassung findet sich im Attribut ph = „S1 R EH C . T YH lng“. Es handelt sich um das Lautschriftsystem UPS, eine Microsoft-Eigenkreation. Dass wir dieses Kodierungssystem verwenden, müssen wir natürlich auch angeben: alphabet = „ups“. Im <speak>-Tag vermerken wir noch (verpflichtend!) die Version des Sprachstandards auf die wir uns beziehen und die Sprache, an der sich unsere Hedda orientieren soll. „de-DE“ steht selbstverständlich für das in Deutschland gesprochene Deutsch (im Gegensatz zum Schweizerischen beispielsweise).

Soweit so gut. Hedda ist in der Lage Text in einer Lautschrift zu verstehen. Tatsächlich sind es sogar drei Lautschriften, allen voran das internationale phonetische Alphabet (IPA). Dieses besteht bekanntlich in seinem Kern aus den lateinischen Buchstaben, die um einige Graphien ergänzt wurden, die besonders Altphilologen gut vertraut sind und normalerweise in der Sprache, der sie entnommen wurden, Laute repräsentieren, die es im Lateinischen nicht gab. Viele dieser Zeichen finden sich nicht auf der Standardtastatur und nicht im Standard-ASCII-Zeichensatz. Beispielsweise der ⁠ʃ⁠-Laut in hübsch: [‚hyp⁠ʃ⁠]). Aus diesem Grund gibt es die beiden anderen Varianten der Lautkodierung, die Hedda versteht. Für Computerprogramme leicht zu verarbeiten ist die SAPI-ID (SAPI = Speech API), ein System, das jedem der IPA-Zeichen einen Zahlenwert zuweist: hübsch wird als [02C8 0068 028F 0283] erfasst. Uns kommt das weniger entgegen und deshalb haben wir uns für UPS entschieden, um mit Hedda zu kommunizieren.

UPS nutzt nur Zeichen, die auf der Tastatur (und im ASCII-Code) vorkommen. Häufig, aber nicht immer, entsprechen auch hier einzelne Buchstaben den lateinischen Lautwerten (wenn auch eher in der englischen Aussprache). Komplexere oder (vom englisch getönten Latein her betrachtet) ungewöhnlichere Laute werden durch Buchstabenkombinationen (EH AX etc.) wiedergegeben, die jeweils eine Einheit der Lautzeichenkette repräsentieren. Um die Segmente deutlich voneinander zu trennen muss ein Leerzeichen zwischen ihnen eingefügt werden.

ACHTUNG! Hedda, bzw. die Speech.dll, ist in dieser Hinsicht ausgesprochen pingelig! Fehlt das Leerzeichen, und lässt sich kein einzelner Lautwert finden, der einer Buchstabenfolge zugeordnet werden kann, bricht die Sprachausgabe ab!

In unserem Beispiel findet sich unter anderem auch die Kodierung lng. Sie gibt keinen Lautwert wieder, sondern zeigt an, dass der vorangehende Laut lang gesprochen werden soll (IPA verwendet den Doppeltpunkt [:] zur Bezeichnung der Länge). Auch solche Signale müssen durch Leerzeichen von ihren Nachbarn getrennt werden. Die weitere Erläuterung der Symbole der Lautschrift erspare ich uns vorerst. Sie findet sich schließlich ausführlich auf den Supportseiten von Microsoft dokumentiert.

Machen wir erst einmal weiter: Wir lassen Hedda den Beginn der Zeile sprechen und akzeptieren ihr modernes Hochdeutsch als Mittelhochdeutsch, fügen dann das problematische rehtiu in Lautschrift ein, und setzten danach den Text fort – jetzt wieder Neuhochdeutsch-Pseudomittelhochdeutsch:

 

        static void Main(string[] args)
        {
            
            SpeechSynthesizer synth = new SpeechSynthesizer();
            string ssmlRehtiu =  @""
                                + @""
                                + "rehtiu";
            synth.Speak("Swêr");
            synth.SpeakSsml(ssmlRehtiu);
            synth.Speak("wort gemerken kan,");
        }

 

Das
hört sich
jetzt
sehr
abgehackt an. Vielleicht sollten wir doch gleich die ganze Zeile in phonetischer Schreibweise erfassen. Dann brauchen wir auch nicht so viele faule Kompromisse einzugehen:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Speech.Synthesis;

namespace Mittelhochdeutsches_Hörbuch1
{
    class Program
    {
        static void Main(string[] args)
        {
            
            SpeechSynthesizer synth = new SpeechSynthesizer();

            foreach (var voice in synth.GetInstalledVoices())
            {
                synth.Speak(voice.VoiceInfo.Description);
                string phoneme = " S1 S V EH lng R . "
                                +"S1 R EH C . T YH lng . "
                                +"S1 V AOX T . "
                                +"G EH . S1 M EX R . K EX N . "
                                +"S1 K A N _,";

                string grapheme = "Swêr rehtiu wort gemerken kan,";
                synth.SpeakSsml(@""
                                +grapheme+"");
            }
        }
    }
}

Wenn wir also unseren Text einfach Zeile für Zeile in Lautschrift umsetzen und diese dann zu einer sehr, sehr, seeeehr lange Zeichenkette zusammensetzen … die wir dann natürlich noch zwischen die SSML-tags quetschen …

Nein. Das ergibt selbst dann kaum entzifferbare Bandwurmkonstruktionen, wenn  wir Hedda eine Verschnaufpause am Ende jeder Zeile zugestehen, wo es nicht so auffällt. Und überhaupt wäre es letztlich deutlich einfacher, den Text direkt selber einzusprechen. Wir müssen und werden einen Weg finden, der mit weniger Aufwand zum Ziel führt.

… im dritten Teil des Projektes.

Mein erstes mittelhochdeutsches Hörbuch – Teil 1

Einleitung: Stefan liest

Nicht jede Lesung, nicht jede musikalische Interpretation eines mittelhochdeutschen Gedichts, die sich auf YouTube finden lässt, genügt wissenschaftlichen germanistischen Ansprüchen, aber wenn es nur darum geht, einmal einen Eindruck vom Klang der mittelhochdeutschen Sprache zu erhalten, dann wird man dort schnell fündig. Wer allerdings auf das Hören als Ersatz für das Sehen angewiesen ist, oder einfach nur den Wunsch verspürt, einen längeren mittelhochdeutschen Text anzuhören, anstatt ihn zu lesen, wird nicht nur auf den einschlägigen Plattformen vergeblich nach „Material“ suchen.

Das im folgenden Artikel vorgestellte Projekt versucht ein wenig Abhilfe zu schaffen. Es soll selbstverständlich nicht darum gehen, Hilfestellung bei der Einrichtung eines eigenen Tonstudios oder bei der Aufnahme eigener Lesungen zu geben. Nein, wir beabsichtigen, unseren Computer dazu zu bringen, uns einen mittelhochdeutschen Text vorzulesen und zwar so, dass wir ihn gut verstehen und seine Aussprache auch Fachleuten für das Mittelhochdeutsche keine Schmerzen verursacht. Die Ausgabe leiten wir dann in eine Audio-Datei, damit wir unser Hörbuch auf jedem uns genehmen Gerät abspielen können.

Das scheint auf den ersten Blick gar nicht so schwer zu bewerkstelligen: Schließlich lässt sich jedes Handy und jeder übliche Browser dazu bewegen, Texte vorzulesen. Dazu muss man allerdings teilweise eine zusätzliche App oder ein Add-On installieren. Das Betriebssystem eines Laptop- oder Desktop-Computer lässt sich natürlich ebenfalls um die Fähigkeit zur Sprachsynthese erweitern, wenn es diese nicht bereits von Haus aus mitbringt. Wer diesen Text beispielsweise auf einem Windows Rechner vor sich sieht, muss nur die  Strg-, die Windows– und die Enter-Taste gemeinsam betätigen, um die automatische Sprachausgabe zu aktivieren. Die auf meinem Rechner zu hörende Stimme von Windows10-Stefan klingt zwar manchmal etwas blechern, aber doch im Ganzen gut verständlich:

Ein ritter sô gelêret was,
daz er an den buochen las,
swaz er dar an geschriben vant:
der was Hartman genant,
dienstman was er zOuwe.

Nun ja, ein paar Worte hören sich etwas exotisch an  – ab und zu scheint Stefan bei fremdartigen Schreibungen zu vermuten, dass sie wohl englisch ausgesprochen werden müssen. Andere Wörter hingegen klingen viel zu vertraut. Kein Wunder! Stefan spricht und liest modernes Hochdeutsch und ahnt ja nicht, dass er in „dienstman“ einen Diphthong von sich geben soll. Probieren wir es gleich mit einem anderen Text als dem Armen Heinrich (Natürlich erkannt, oder?). Wie wäre es beispielsweise mit der Einleitung des Lanzelet des Ulrich von Zatzikhoven, eines ungefähren Zeitgenossen des gelehrten Ritters Hartmann von Aue?

Swer rehtiu wort gemerken kan,
der gedenke wie ein wîse man
hie vor bî alten zîten sprach,
dem sît diu welt der volge jach.

Das … klingt, zumindest von Stefan gelesen, schon weit weniger akzeptabel für mich. Jemand müsste Stefan ein paar Lektionen in der Aussprache mittelhochdeutscher Worte erteilen, bevor ich bereit wäre mir von ihm die fast 9500 Verse der an sich recht spannenden (und actionreichen!) Erzählung vom Artushof vorlesen zu lassen.

Dieser Gedanke soll der Ausgangspunkt für das heutige Projekt sein. Es gibt neben kommerziellen Angeboten zahlreiche Open-Source-Ansätze zur Spracherzeugung und -ausgabe, die Schnittstellen zur individuellen Anpassung und Programmierung bieten. Aber wir bleiben vorerst in der Microsoft-Umgebung und werden unsere Lösung in C# im Visual-Studio umsetzen. Größere Programmierkenntnisse brauchen wir dafür nicht und das ist der Hauptgrund für unsere Wahl. Zudem können wir uns in diesem Fall darauf verlassen, dass die Technologie stabil und fehlerfrei auf allen Rechnern läuft, auf denen Windows stabil und fehlerfr … Naja … auf denen halt …

Wer noch nie mit Visual-Studio oder C# gearbeitet hat, findet bei Verständnisfragen in reichem Maße Hilfe auf den Supportseiten von Microsoft. Dort kann er auch die (für private Zwecke) kostenlose Community-Version der Entwicklungsumgebung herunterladen:

Zum Download von Visual-Studio 2017

Als Übungstext verwenden wir nicht den gesamten Lanzelet-Roman, sondern beschränken uns vorerst bescheiden auf die ersten 49 Zeilen, die wir per copy & paste der Textfassung der Bibliotheca Augustana entnommen haben:

Lanzelet Verse 1 – 49

In Windows 10 per Rechtsklick das Kontextmenü öffnen und „Ziel speichern“ wählen, um den Text auf den eigenen Computer herunter zu laden und dort zu speichern. Jetzt ist alles vorbereitet. Wir können loslegen …

… und zwar im Teil 2.

Wer allerdings jetzt schon wissen will, was auf ihn zukommen wird, kann jetzt auch erst einen Blick auf den Tutorial-Überblick werfen:

Mein erstes mittelhochdeutsches Hörbuch – Teil 1 weiterlesen