diff options
Diffstat (limited to 'kttsd/plugins/hadifix/SSMLtoTxt2pho.xsl')
-rw-r--r-- | kttsd/plugins/hadifix/SSMLtoTxt2pho.xsl | 164 |
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> |