summaryrefslogtreecommitdiffstats
path: root/kttsd/plugins/hadifix/SSMLtoTxt2pho.xsl
diff options
context:
space:
mode:
Diffstat (limited to 'kttsd/plugins/hadifix/SSMLtoTxt2pho.xsl')
-rw-r--r--kttsd/plugins/hadifix/SSMLtoTxt2pho.xsl164
1 files changed, 164 insertions, 0 deletions
diff --git a/kttsd/plugins/hadifix/SSMLtoTxt2pho.xsl b/kttsd/plugins/hadifix/SSMLtoTxt2pho.xsl
new file mode 100644
index 0000000..5a81c8f
--- /dev/null
+++ b/kttsd/plugins/hadifix/SSMLtoTxt2pho.xsl
@@ -0,0 +1,164 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+<xsl:output method="text" encoding="ISO-8859-1" indent="no"/>
+
+<!-- XSLT stylesheet to convert SSML into a format that can be processed by the
+ Hadifix txt2pho processor.
+
+ (c) 2004 by Gary Cramblitt
+
+ Original author: Gary Cramblitt <garycramblitt@comcast.net>
+ Current Maintainer: Gary Cramblitt <garycramblitt@comcast.net>
+
+ This program is free software; you can redistribute it and/or modify *
+ it under the terms of the GNU General Public License as published by *
+ the Free Software Foundation; either version 2 of the License, or *
+ (at your option) any later version. *
+
+ The txt2pho processor permits special markup to be embedded in text to control
+ speech attributes such as speed (duration), pitch, etc.
+ The markup must be inside curly braces and separated from other text by a space.
+ Spaces within the markup are not permitted.
+ See the txt2pho README file for details.
+
+ Something the README does not say is that {Pitch} markup applies only to one
+ sentence and reverts back to normal when the sentence is completed.
+ It means that we must repeatedly ouput {Pitch} markup
+ for each sentence in the text. For example, the SSML
+
+ <prosody pitch="high">Sentence one. Sentence two.</prosody> Sentence three.
+
+ must be converted to
+
+ {Pitch:300} Sentence one. {Pitch:300} Sentence two. Sentence three.
+
+ {Duration} markup, on the other hand, is addative and stays in effect until
+ changed. For example, the SSML
+
+ <prosody rate="fast">Sentence one. Sentence two.</prosody> Sentence three.
+
+ must be converted to
+
+ {Duration:-0.5} Sentence one. Sentence two. {Duration:0.5} Sentence three.
+
+ txt2pho will also stop processing when it sees a newline. Therefore, we must take
+ care to strip all newlines and avoid inserting any newlines.
+ -->
+
+<!-- Strip all elements and attributes from output. -->
+<xsl:strip-space elements="*" />
+
+<xsl:template match="speak">
+ <xsl:apply-templates/>
+</xsl:template>
+
+<!-- Handle markup that maintains state. -->
+<xsl:template match="prosody">
+ <!-- Rate (speed), Rates are addative and stay in effect until changed. -->
+ <!-- TODO: SSML permits nesting of prosody elements. As coded below,
+ <prosody rate="slow">One<prosody rate="slow">Two</prosody</prosody>
+ will pronounce "Two" doubly slow, which it should not do. -->
+ <xsl:choose>
+ <xsl:when test="@rate='fast'">
+ <!-- Increase speed. -->
+ <xsl:value-of select="'{Duration:-0.5} '"/>
+ <!-- Continue processing. -->
+ <xsl:apply-templates/>
+ <!-- Decrease speed. -->
+ <xsl:value-of select="'{Duration:0.5} '"/>
+ </xsl:when>
+ <xsl:when test="@rate='slow'">
+ <!-- Decrease speed. -->
+ <xsl:value-of select="'{Duration:0.5} '"/>
+ <!-- Continue processing. -->
+ <xsl:apply-templates/>
+ <!-- Increase speed. -->
+ <xsl:value-of select="'{Duration:-0.5} '"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:apply-templates/>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<!-- Outputs a sentence with txt2pho markup.
+ Called in the context of a text node. Obtain markup by
+ extracting ancestor node attributes.
+ @param sentence The sentence to mark up.
+ @return Sentence preceeded by txt2pho markup.
+ -->
+<xsl:template name="output-sentence">
+ <xsl:param name="sentence"/>
+ <!-- Pitch -->
+ <xsl:if test="ancestor::prosody/@pitch='high'">
+ <xsl:value-of select="'{Pitch:300} '"/>
+ </xsl:if>
+ <xsl:if test="ancestor::prosody/@pitch='low'">
+ <xsl:value-of select="'{Pitch:-40} '"/>
+ </xsl:if>
+ <!-- Output the sentence itself. -->
+ <xsl:value-of select="$sentence"/>
+</xsl:template>
+
+<!-- Return the first sentence of argument.
+ Sentence delimiters are '.:;!?'
+ @param paragraph Paragraph from which to extract first sentence.
+ @return The first sentence, or if no such sentence, the paragraph.
+ -->
+<xsl:template name="parse-sentence">
+ <xsl:param name="paragraph"/>
+ <!-- Copy paragraph, replacing all delimeters with period. -->
+ <xsl:variable name="tmp">
+ <xsl:value-of select="translate($paragraph,':;!?','....')"/>
+ </xsl:variable>
+ <!-- Look for first period and space and extract corresponding substring from original. -->
+ <xsl:choose>
+ <xsl:when test="contains($tmp, '. ')">
+ <xsl:value-of select="substring($paragraph, 1, string-length(substring-before($tmp, '. '))+2)"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$paragraph"/>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<!-- Process a paragraph, outputting each sentence with txt2pho markup.
+ @param paragraph The paragraph to process.
+ @return The paragraph with each sentence preceeded by txt2pho markup.
+ -->
+<xsl:template name="output-paragraph">
+ <xsl:param name="paragraph" />
+ <!-- Stop when no more sentences to output. -->
+ <xsl:choose>
+ <xsl:when test="normalize-space($paragraph)!=''">
+ <!-- Split the paragraph into first sentence and rest of paragraph (if any). -->
+ <xsl:variable name="sentence">
+ <xsl:call-template name="parse-sentence">
+ <xsl:with-param name="paragraph" select="$paragraph"/>
+ </xsl:call-template>
+ </xsl:variable>
+ <!-- debug: <xsl:value-of select="concat('[sentence: ',$sentence,']')"/> -->
+ <xsl:variable name="rest">
+ <xsl:value-of select="substring-after($paragraph,$sentence)" />
+ </xsl:variable>
+ <!-- Output the sentence with markup. -->
+ <xsl:call-template name="output-sentence" >
+ <xsl:with-param name="sentence" select="$sentence" />
+ </xsl:call-template>
+ <!-- Recursively process the rest of the paragraph. -->
+ <xsl:call-template name="output-paragraph">
+ <xsl:with-param name="paragraph" select="$rest" />
+ </xsl:call-template>
+ </xsl:when>
+ </xsl:choose>
+</xsl:template>
+
+<!-- Process each text node. -->
+<xsl:template match="text()">
+ <!-- debug: <xsl:value-of select="concat('[paragraph: ',normalize-space(.),']')"/> -->
+ <xsl:call-template name="output-paragraph">
+ <xsl:with-param name="paragraph" select="concat(normalize-space(.),' ')"/>
+ </xsl:call-template>
+</xsl:template>
+
+</xsl:stylesheet>