Permalink
Browse files

Merge branch 'release/0.1.0'

  • Loading branch information...
2 parents d01ed70 + db02c3a commit 69ae194a7f993a2649853935665a8b9e511d3cff @benlangfeld committed Jul 2, 2011
View
3 .gitignore
@@ -2,3 +2,6 @@
.bundle
Gemfile.lock
pkg/*
+spec/reports
+.yardoc
+doc
View
3 .rspec
@@ -0,0 +1,3 @@
+--format documentation
+--colour
+--tty
View
2 CHANGELOG.md
@@ -0,0 +1,2 @@
+# 0.1.0
+ * Initial Release
View
69 README.md
@@ -1,22 +1,68 @@
-RubySpeech
---------
+# RubySpeech
+RubySpeech is a library for constructing and parsing Text to Speech (TTS) and Automatic Speech Recognition (ASR) documents such as [SSML](http://www.w3.org/TR/speech-synthesis) and [GRXML](http://www.w3.org/TR/speech-grammar/). The primary use case is for construction of these documents to be processed by TTS and ASR engines.
+## Installation
+ gem install ruby_speech
+## Library
+RubySpeech provides a DSL for constructing SSML documents like so:
-Installation
-============
- gem install ruby_speech
+```ruby
+require 'ruby_speech'
+
+RubySpeech::SSML.draw do
+ voice gender: :male, name: 'fred' do
+ string "Hi, I'm Fred. The time is currently "
+ say_as 'date', format: 'dmy' do
+ "01/02/1960"
+ end
+ end
+end
+```
+
+becomes:
+
+```xml
+<?xml version="1.0"?>
+<speak xmlns="http://www.w3.org/2001/10/synthesis" version="1.0" xml:lang="en-US">
+ <voice gender="male" name="fred">
+ Hi, I'm Fred. The time is currently <say-as format="dmy" interpret-as="date">01/02/1960</say-as>
+ </voice>
+</speak>
+```
+
+Check out the [YARD documentation](http://rdoc.info/github/benlangfeld/ruby_speech/master/frames) for more
-Library
-=======
+## Features:
+### SSML
+* Document construction
+* `<voice/>`
+* `<prosody/>`
+* `<emphasis/>`
+* `<say-as/>`
+* `<break/>`
+## TODO:
+### SSML
+#### Document Structure
+* `<p/>` and `<s/>`
+* `<phoneme/>`
+* `<sub/>`
+* `<lexicon/>`
+* `<meta/>` and `<metadata/>`
+#### Misc
+* `<audio/>`
+* `<mark/>`
+* `<desc/>`
-Check out the [YARD documentation](http://rdoc.info/github/mojolingo/ruby_voice/master/frames) for more
+## Links:
+* [Source](https://github.com/benlangfeld/ruby_speech)
+* [Documentation](http://rdoc.info/github/benlangfeld/ruby_speech/master/frames)
+* [Bug Tracker](https://github.com/benlangfeld/ruby_speech/issues)
-Note on Patches/Pull Requests
------------------------------
+## Note on Patches/Pull Requests
* Fork the project.
* Make your feature addition or bug fix.
@@ -25,7 +71,6 @@ Note on Patches/Pull Requests
* If you want to have your own version, that is fine but bump version in a commit by itself so I can ignore when I pull
* Send me a pull request. Bonus points for topic branches.
-Copyright
----------
+## Copyright
Copyright (c) 2011 Ben Langfeld. MIT licence (see LICENSE for details).
View
21 Rakefile
@@ -1 +1,22 @@
require 'bundler/gem_tasks'
+Bundler::GemHelper.install_tasks
+
+require 'rspec/core'
+require 'rspec/core/rake_task'
+require 'ci/reporter/rake/rspec'
+RSpec::Core::RakeTask.new(:spec) do |spec|
+ spec.pattern = 'spec/**/*_spec.rb'
+ spec.rspec_opts = '--color'
+end
+
+RSpec::Core::RakeTask.new(:rcov) do |spec|
+ spec.pattern = 'spec/**/*_spec.rb'
+ spec.rcov = true
+ spec.rspec_opts = '--color'
+end
+
+task :default => :spec
+task :ci => ['ci:setup:rspec', :spec]
+
+require 'yard'
+YARD::Rake::YardocTask.new
View
442 assets/synthesis-core.xsd
@@ -0,0 +1,442 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ elementFormDefault="qualified">
+ <xsd:annotation>
+ <xsd:documentation>SSML 1.0 Core Schema (20040615)</xsd:documentation>
+ </xsd:annotation>
+ <xsd:annotation>
+ <xsd:documentation>
+Copyright 1998-2004 W3C (MIT, ERCIM, Keio),
+All Rights Reserved. Permission to use, copy, modify and
+distribute the SSML core schema and its accompanying documentation
+for any purpose and without fee is hereby granted in
+perpetuity, provided that the above copyright notice and this
+paragraph appear in all copies. The copyright holders make no
+representation about the suitability of the schema for any purpose.
+It is provided "as is" without expressed or implied warranty.
+</xsd:documentation>
+ </xsd:annotation>
+ <xsd:annotation>
+ <xsd:documentation>Importing dependent namespaces</xsd:documentation>
+ </xsd:annotation>
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace"
+ schemaLocation="http://www.w3.org/2001/xml.xsd"/>
+ <xsd:annotation>
+ <xsd:documentation>General Datatypes</xsd:documentation>
+ </xsd:annotation>
+
+ <xsd:simpleType name="duration">
+ <xsd:annotation>
+ <xsd:documentation>Duration follows "Times" in CCS
+ specification; e.g. "25ms", "3s"
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="(\+)?([0-9]*\.)?[0-9]+(ms|s)"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <xsd:simpleType name="number">
+ <xsd:annotation>
+ <xsd:documentation>number: e.g. 10, 5.5, 1.5, 9., .45
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:decimal">
+ <xsd:minInclusive value="0"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="relative">
+ <xsd:annotation>
+ <xsd:documentation>Modeled on number datatype: e.g. +4.5, -.45
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="[+\-]([0-9]+|[0-9]+.[0-9]*|[0-9]*.[0-9]+)"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="percent">
+ <xsd:annotation>
+ <xsd:documentation>Modeled on number datatype: e.g. +4.5%, -.45%
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="[+\-]?([0-9]+|[0-9]+.[0-9]*|[0-9]*.[0-9]+)%"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="semitone">
+ <xsd:annotation>
+ <xsd:documentation>Modeled on relative datatype: e.g. +4.5st, -.45st
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="[+\-]([0-9]+|[0-9]+.[0-9]*|[0-9]*.[0-9]+)st"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+
+ <xsd:simpleType name="hertz.number">
+ <xsd:annotation>
+ <xsd:documentation>number Hertz: e.g. 10Hz, 5.5Hz, 1.5Hz,, 9.Hz,, .45Hz
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="([0-9]+|[0-9]+.[0-9]*|[0-9]*.[0-9]+)Hz"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="hertz.relative">
+ <xsd:annotation>
+ <xsd:documentation>relative Hertz: e.g. +10Hz, -5.5Hz, +1.5Hz, +9.Hz,+.45Hz
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="[+\-]([0-9]+|[0-9]+.[0-9]*|[0-9]*.[0-9]+)Hz"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="volume.number">
+ <xsd:annotation>
+ <xsd:documentation>Modeled on number datatype: 0.0 - 100.0
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="number">
+ <xsd:minInclusive value="0.0"/>
+ <xsd:maxInclusive value="100.0"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="height.scale">
+ <xsd:annotation>
+ <xsd:documentation>descriptive values for height</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="x-high"/>
+ <xsd:enumeration value="high"/>
+ <xsd:enumeration value="medium"/>
+ <xsd:enumeration value="low"/>
+ <xsd:enumeration value="x-low"/>
+ <xsd:enumeration value="default"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="speed.scale">
+ <xsd:annotation>
+ <xsd:documentation>descriptive values for speed</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="x-fast"/>
+ <xsd:enumeration value="fast"/>
+ <xsd:enumeration value="medium"/>
+ <xsd:enumeration value="slow"/>
+ <xsd:enumeration value="x-slow"/>
+ <xsd:enumeration value="default"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="volume.scale">
+ <xsd:annotation>
+ <xsd:documentation>descriptive values for volume</xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="silent"/>
+ <xsd:enumeration value="x-soft"/>
+ <xsd:enumeration value="soft"/>
+ <xsd:enumeration value="medium"/>
+ <xsd:enumeration value="loud"/>
+ <xsd:enumeration value="x-loud"/>
+ <xsd:enumeration value="default"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+<xsd:simpleType name="pitch.datatype">
+ <xsd:union memberTypes="hertz.number hertz.relative percent semitone height.scale"/>
+</xsd:simpleType>
+
+<xsd:simpleType name="range.datatype">
+ <xsd:union memberTypes="hertz.number hertz.relative percent semitone height.scale"/>
+</xsd:simpleType>
+
+<xsd:simpleType name="rate.datatype">
+ <xsd:union memberTypes="number percent speed.scale"/>
+</xsd:simpleType>
+
+<xsd:simpleType name="volume.datatype">
+ <xsd:union memberTypes="volume.number relative percent volume.scale"/>
+</xsd:simpleType>
+
+<xsd:simpleType name="contourpoint.datatype">
+ <xsd:annotation>
+ <xsd:documentation>(Number%,pitch.datatype) </xsd:documentation>
+ </xsd:annotation>
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="\(([0-9]+|[0-9]+.[0-9]*|[0-9]*.[0-9]+)%,(([0-9]+|[0-9]+.[0-9]*|[0-9]*.[0-9]+)Hz|[+\-]([0-9]+|[0-9]+.[0-9]*|[0-9]*.[0-9]+)Hz|[+\-]?([0-9]+|[0-9]+.[0-9]*|[0-9]*.[0-9]+)%|[+\-]([0-9]+|[0-9]+.[0-9]*|[0-9]*.[0-9]+)st|x-high|high|medium|low|x-low|default)\)"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+
+<xsd:simpleType name="contour.datatype">
+ <xsd:annotation>
+ <xsd:documentation>list of whitespace separated contourpoints </xsd:documentation>
+ </xsd:annotation>
+ <xsd:list itemType="contourpoint.datatype"/>
+</xsd:simpleType>
+
+<xsd:simpleType name="gender.datatype">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="male"/>
+ <xsd:enumeration value="female"/>
+ <xsd:enumeration value="neutral"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+
+<xsd:simpleType name="level.datatype">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="strong"/>
+ <xsd:enumeration value="moderate"/>
+ <xsd:enumeration value="none"/>
+ <xsd:enumeration value="reduced"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="strength.datatype">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="none"/>
+ <xsd:enumeration value="x-weak"/>
+ <xsd:enumeration value="weak"/>
+ <xsd:enumeration value="medium"/>
+ <xsd:enumeration value="strong"/>
+ <xsd:enumeration value="x-strong"/>
+
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="version.datatype">
+ <xsd:restriction base="xsd:NMTOKEN"/>
+</xsd:simpleType>
+
+
+<xsd:simpleType name="voicename.datatype">
+ <xsd:restriction base="xsd:token">
+ <xsd:pattern value="\S+"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+<xsd:simpleType name="voicenames.datatype">
+ <xsd:list itemType="voicename.datatype"/>
+</xsd:simpleType>
+
+<xsd:simpleType name="alphabet.datatype">
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="(ipa|x-.*)"/>
+ </xsd:restriction>
+</xsd:simpleType>
+
+
+ <xsd:annotation>
+ <xsd:documentation>Attributes and Groups</xsd:documentation>
+ </xsd:annotation>
+
+
+
+ <xsd:attributeGroup name="speak.attribs">
+ <xsd:attribute name="version" type="version.datatype"/>
+ <xsd:attribute ref="xml:lang"/>
+ <xsd:attribute ref="xml:base"/>
+ </xsd:attributeGroup>
+
+
+ <xsd:annotation>
+ <xsd:documentation>Content Models</xsd:documentation>
+ </xsd:annotation>
+
+ <xsd:group name="allowed-within-sentence">
+ <xsd:choice>
+ <xsd:element ref="aws"/>
+ </xsd:choice>
+ </xsd:group>
+
+
+ <xsd:group name="paragraph.class">
+ <xsd:sequence>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:group ref="allowed-within-sentence"/>
+ <xsd:element ref="s"/>
+ </xsd:choice>
+ </xsd:sequence>
+ </xsd:group>
+
+ <xsd:group name="sentence.class">
+ <xsd:sequence>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:group ref="allowed-within-sentence"/>
+ </xsd:choice>
+ </xsd:sequence>
+ </xsd:group>
+
+ <xsd:group name="sentenceAndStructure.class">
+ <xsd:sequence>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:group ref="allowed-within-sentence"/>
+ <xsd:group ref="structure"/>
+ </xsd:choice>
+ </xsd:sequence>
+ </xsd:group>
+
+ <xsd:group name="descAndSentenceAndStructure.class">
+ <xsd:sequence>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:group ref="sentenceAndStructure.class"/>
+ <xsd:element ref="desc"/>
+ </xsd:choice>
+ </xsd:sequence>
+ </xsd:group>
+
+ <xsd:group name="speak.class">
+ <xsd:sequence>
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="meta" type="ssml-meta"/>
+ <xsd:element name="metadata" type="ssml-metadata"/>
+ <xsd:element name="lexicon" type="ssml-lexicon"/>
+ </xsd:choice>
+ <xsd:group ref="sentenceAndStructure.class" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:group>
+
+ <xsd:annotation>
+ <xsd:documentation>Elements</xsd:documentation>
+ </xsd:annotation>
+ <xsd:element name="aws" abstract="true">
+ <xsd:annotation>
+ <xsd:documentation>The 'allowed-within-sentence' group uses this abstract element.</xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+
+ <xsd:element name="struct" abstract="true"/>
+ <xsd:group name="structure">
+ <xsd:choice>
+ <xsd:element ref="struct"/>
+ </xsd:choice>
+ </xsd:group>
+
+ <xsd:element name="speak" type="speak"/>
+
+ <xsd:complexType name="speak" mixed="true">
+ <xsd:group ref="speak.class"/>
+ <xsd:attributeGroup ref="speak.attribs"/>
+ </xsd:complexType>
+
+ <xsd:element name="p" type="paragraph" substitutionGroup="struct"/>
+
+ <xsd:complexType name="paragraph" mixed="true">
+ <xsd:group ref="paragraph.class"/>
+ <xsd:attribute ref="xml:lang"/>
+ </xsd:complexType>
+
+ <xsd:element name="s" type="sentence" substitutionGroup="struct"/>
+ <xsd:complexType name="sentence" mixed="true">
+ <xsd:group ref="sentence.class"/>
+ <xsd:attribute ref="xml:lang"/>
+ </xsd:complexType>
+
+ <xsd:element name="voice" type="voice" substitutionGroup="aws"/>
+
+ <xsd:complexType name="voice" mixed="true">
+ <xsd:group ref="sentenceAndStructure.class"/>
+ <xsd:attribute name="gender" type="gender.datatype"/>
+ <xsd:attribute name="age" type="xsd:nonNegativeInteger"/>
+ <xsd:attribute name="variant" type="xsd:positiveInteger"/>
+ <xsd:attribute name="name" type="voicenames.datatype"/>
+ <xsd:attribute ref="xml:lang"/>
+ </xsd:complexType>
+
+ <xsd:element name="prosody" type="prosody" substitutionGroup="aws"/>
+ <xsd:complexType name="prosody" mixed="true">
+ <xsd:group ref="sentenceAndStructure.class"/>
+ <xsd:attribute name="pitch" type="pitch.datatype"/>
+ <xsd:attribute name="contour" type="contour.datatype"/>
+ <xsd:attribute name="range" type="range.datatype"/>
+ <xsd:attribute name="rate" type="rate.datatype"/>
+ <xsd:attribute name="duration" type="duration"/>
+ <xsd:attribute name="volume" type="volume.datatype" default="100.0"/>
+ </xsd:complexType>
+
+ <xsd:element name="audio" type="audio" substitutionGroup="aws"/>
+ <xsd:complexType name="audio" mixed="true">
+ <xsd:group ref="descAndSentenceAndStructure.class"/>
+ <xsd:attribute name="src" type="xsd:anyURI"/>
+ </xsd:complexType>
+
+ <xsd:element name="desc" type="desc"/>
+ <xsd:complexType name="desc">
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute ref="xml:lang"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+ <xsd:element name="emphasis" type="emphasis" substitutionGroup="aws"/>
+ <xsd:complexType name="emphasis" mixed="true">
+ <xsd:group ref="sentence.class"/>
+ <xsd:attribute name="level" type="level.datatype" default="moderate"/>
+ </xsd:complexType>
+
+ <xsd:element name="sub" type="sub" substitutionGroup="aws"/>
+ <xsd:complexType name="sub">
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:string">
+ <xsd:attribute name="alias" type="xsd:string" use="required"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+
+
+ <xsd:element name="say-as" type="say-as" substitutionGroup="aws"/>
+ <xsd:complexType name="say-as" mixed="true">
+ <xsd:attribute name="interpret-as" type="xsd:NMTOKEN"
+ use="required"/>
+ <xsd:attribute name="format" type="xsd:NMTOKEN"/>
+ <xsd:attribute name="detail" type="xsd:NMTOKEN"/>
+ </xsd:complexType>
+
+ <xsd:element name="phoneme" type="phoneme" substitutionGroup="aws"/>
+ <xsd:complexType name="phoneme" mixed="true">
+ <xsd:attribute name="ph" type="xsd:string" use="required"/>
+ <xsd:attribute name="alphabet" type="alphabet.datatype"/>
+ </xsd:complexType>
+
+ <xsd:element name="break" type="break" substitutionGroup="aws"/>
+ <xsd:complexType name="break">
+ <xsd:attribute name="time" type="duration"/>
+ <xsd:attribute name="strength" type="strength.datatype" default="medium"/>
+ </xsd:complexType>
+
+ <xsd:element name="mark" type="mark" substitutionGroup="aws"/>
+ <xsd:complexType name="mark">
+ <xsd:attribute name="name" type="xsd:token"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="ssml-metadata">
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:any namespace="##other" processContents="lax"/>
+ </xsd:choice>
+ <xsd:anyAttribute namespace="##any" processContents="strict"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="ssml-meta">
+ <xsd:attribute name="name" type="xsd:NMTOKEN"/>
+ <xsd:attribute name="content" type="xsd:string" use="required"/>
+ <xsd:attribute name="http-equiv" type="xsd:NMTOKEN"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="ssml-lexicon">
+ <xsd:attribute name="uri" type="xsd:anyURI" use="required"/>
+ <xsd:attribute name="type" type="xsd:string"/>
+ </xsd:complexType>
+
+</xsd:schema>
View
63 assets/synthesis.xsd
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsd:schema targetNamespace="http://www.w3.org/2001/10/synthesis"
+xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+xmlns="http://www.w3.org/2001/10/synthesis" elementFormDefault="qualified">
+ <xsd:annotation>
+ <xsd:documentation>SSML 1.0 Schema (20040615)</xsd:documentation>
+ </xsd:annotation>
+ <xsd:annotation>
+ <xsd:documentation>Copyright 1998-2004 W3C (MIT, ERCIM, Keio),
+ All Rights Reserved. Permission to use, copy, modify and
+ distribute the SSML schema and its accompanying documentation
+ for any purpose and without fee is hereby granted in
+ perpetuity, provided that the above copyright notice and this
+ paragraph appear in all copies. The copyright holders make no
+ representation about the suitability of the schema for any purpose.
+ It is provided "as is" without expressed or implied warranty.
+ </xsd:documentation>
+ </xsd:annotation>
+
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace"
+ schemaLocation="xml.xsd"/>
+ <xsd:redefine schemaLocation="synthesis-core.xsd">
+
+ <xsd:annotation>
+ <xsd:documentation>restriction: version and attributes on speak mandatory</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType name="speak" mixed="true">
+ <xsd:complexContent>
+ <xsd:restriction base="speak">
+ <xsd:group ref="speak.class"/>
+ <xsd:attribute name="version" type="version.datatype" use="required"/>
+ <xsd:attribute ref="xml:lang" use="required"/>
+ </xsd:restriction>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:annotation>
+ <xsd:documentation>restriction: src attribute on audio is mandatory</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType name="audio" mixed="true">
+ <xsd:complexContent>
+ <xsd:restriction base="audio">
+ <xsd:group ref="descAndSentenceAndStructure.class"/>
+ <xsd:attribute name="src" type="xsd:anyURI" use="required"/>
+ </xsd:restriction>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+
+ <xsd:annotation>
+ <xsd:documentation>restriction: name attribute on mark is mandatory</xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexType name="mark">
+ <xsd:complexContent>
+ <xsd:restriction base="mark">
+ <xsd:attribute name="name" type="xsd:token" use="required"/>
+ </xsd:restriction>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ </xsd:redefine>
+
+</xsd:schema>
View
287 assets/xml.xsd
@@ -0,0 +1,287 @@
+<?xml version='1.0'?>
+<?xml-stylesheet href="../2008/09/xsd.xsl" type="text/xsl"?>
+<xs:schema targetNamespace="http://www.w3.org/XML/1998/namespace"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns ="http://www.w3.org/1999/xhtml"
+ xml:lang="en">
+
+ <xs:annotation>
+ <xs:documentation>
+ <div>
+ <h1>About the XML namespace</h1>
+
+ <div class="bodytext">
+ <p>
+ This schema document describes the XML namespace, in a form
+ suitable for import by other schema documents.
+ </p>
+ <p>
+ See <a href="http://www.w3.org/XML/1998/namespace.html">
+ http://www.w3.org/XML/1998/namespace.html</a> and
+ <a href="http://www.w3.org/TR/REC-xml">
+ http://www.w3.org/TR/REC-xml</a> for information
+ about this namespace.
+ </p>
+ <p>
+ Note that local names in this namespace are intended to be
+ defined only by the World Wide Web Consortium or its subgroups.
+ The names currently defined in this namespace are listed below.
+ They should not be used with conflicting semantics by any Working
+ Group, specification, or document instance.
+ </p>
+ <p>
+ See further below in this document for more information about <a
+ href="#usage">how to refer to this schema document from your own
+ XSD schema documents</a> and about <a href="#nsversioning">the
+ namespace-versioning policy governing this schema document</a>.
+ </p>
+ </div>
+ </div>
+ </xs:documentation>
+ </xs:annotation>
+
+ <xs:attribute name="lang">
+ <xs:annotation>
+ <xs:documentation>
+ <div>
+
+ <h3>lang (as an attribute name)</h3>
+ <p>
+ denotes an attribute whose value
+ is a language code for the natural language of the content of
+ any element; its value is inherited. This name is reserved
+ by virtue of its definition in the XML specification.</p>
+
+ </div>
+ <div>
+ <h4>Notes</h4>
+ <p>
+ Attempting to install the relevant ISO 2- and 3-letter
+ codes as the enumerated possible values is probably never
+ going to be a realistic possibility.
+ </p>
+ <p>
+ See BCP 47 at <a href="http://www.rfc-editor.org/rfc/bcp/bcp47.txt">
+ http://www.rfc-editor.org/rfc/bcp/bcp47.txt</a>
+ and the IANA language subtag registry at
+ <a href="http://www.iana.org/assignments/language-subtag-registry">
+ http://www.iana.org/assignments/language-subtag-registry</a>
+ for further information.
+ </p>
+ <p>
+ The union allows for the 'un-declaration' of xml:lang with
+ the empty string.
+ </p>
+ </div>
+ </xs:documentation>
+ </xs:annotation>
+ <xs:simpleType>
+ <xs:union memberTypes="xs:language">
+ <xs:simpleType>
+ <xs:restriction base="xs:string">
+ <xs:enumeration value=""/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:union>
+ </xs:simpleType>
+ </xs:attribute>
+
+ <xs:attribute name="space">
+ <xs:annotation>
+ <xs:documentation>
+ <div>
+
+ <h3>space (as an attribute name)</h3>
+ <p>
+ denotes an attribute whose
+ value is a keyword indicating what whitespace processing
+ discipline is intended for the content of the element; its
+ value is inherited. This name is reserved by virtue of its
+ definition in the XML specification.</p>
+
+ </div>
+ </xs:documentation>
+ </xs:annotation>
+ <xs:simpleType>
+ <xs:restriction base="xs:NCName">
+ <xs:enumeration value="default"/>
+ <xs:enumeration value="preserve"/>
+ </xs:restriction>
+ </xs:simpleType>
+ </xs:attribute>
+
+ <xs:attribute name="base" type="xs:anyURI"> <xs:annotation>
+ <xs:documentation>
+ <div>
+
+ <h3>base (as an attribute name)</h3>
+ <p>
+ denotes an attribute whose value
+ provides a URI to be used as the base for interpreting any
+ relative URIs in the scope of the element on which it
+ appears; its value is inherited. This name is reserved
+ by virtue of its definition in the XML Base specification.</p>
+
+ <p>
+ See <a
+ href="http://www.w3.org/TR/xmlbase/">http://www.w3.org/TR/xmlbase/</a>
+ for information about this attribute.
+ </p>
+ </div>
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+
+ <xs:attribute name="id" type="xs:ID">
+ <xs:annotation>
+ <xs:documentation>
+ <div>
+
+ <h3>id (as an attribute name)</h3>
+ <p>
+ denotes an attribute whose value
+ should be interpreted as if declared to be of type ID.
+ This name is reserved by virtue of its definition in the
+ xml:id specification.</p>
+
+ <p>
+ See <a
+ href="http://www.w3.org/TR/xml-id/">http://www.w3.org/TR/xml-id/</a>
+ for information about this attribute.
+ </p>
+ </div>
+ </xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+
+ <xs:attributeGroup name="specialAttrs">
+ <xs:attribute ref="xml:base"/>
+ <xs:attribute ref="xml:lang"/>
+ <xs:attribute ref="xml:space"/>
+ <xs:attribute ref="xml:id"/>
+ </xs:attributeGroup>
+
+ <xs:annotation>
+ <xs:documentation>
+ <div>
+
+ <h3>Father (in any context at all)</h3>
+
+ <div class="bodytext">
+ <p>
+ denotes Jon Bosak, the chair of
+ the original XML Working Group. This name is reserved by
+ the following decision of the W3C XML Plenary and
+ XML Coordination groups:
+ </p>
+ <blockquote>
+ <p>
+ In appreciation for his vision, leadership and
+ dedication the W3C XML Plenary on this 10th day of
+ February, 2000, reserves for Jon Bosak in perpetuity
+ the XML name "xml:Father".
+ </p>
+ </blockquote>
+ </div>
+ </div>
+ </xs:documentation>
+ </xs:annotation>
+
+ <xs:annotation>
+ <xs:documentation>
+ <div xml:id="usage" id="usage">
+ <h2><a name="usage">About this schema document</a></h2>
+
+ <div class="bodytext">
+ <p>
+ This schema defines attributes and an attribute group suitable
+ for use by schemas wishing to allow <code>xml:base</code>,
+ <code>xml:lang</code>, <code>xml:space</code> or
+ <code>xml:id</code> attributes on elements they define.
+ </p>
+ <p>
+ To enable this, such a schema must import this schema for
+ the XML namespace, e.g. as follows:
+ </p>
+ <pre>
+ &lt;schema . . .>
+ . . .
+ &lt;import namespace="http://www.w3.org/XML/1998/namespace"
+ schemaLocation="http://www.w3.org/2001/xml.xsd"/>
+ </pre>
+ <p>
+ or
+ </p>
+ <pre>
+ &lt;import namespace="http://www.w3.org/XML/1998/namespace"
+ schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
+ </pre>
+ <p>
+ Subsequently, qualified reference to any of the attributes or the
+ group defined below will have the desired effect, e.g.
+ </p>
+ <pre>
+ &lt;type . . .>
+ . . .
+ &lt;attributeGroup ref="xml:specialAttrs"/>
+ </pre>
+ <p>
+ will define a type which will schema-validate an instance element
+ with any of those attributes.
+ </p>
+ </div>
+ </div>
+ </xs:documentation>
+ </xs:annotation>
+
+ <xs:annotation>
+ <xs:documentation>
+ <div id="nsversioning" xml:id="nsversioning">
+ <h2><a name="nsversioning">Versioning policy for this schema document</a></h2>
+ <div class="bodytext">
+ <p>
+ In keeping with the XML Schema WG's standard versioning
+ policy, this schema document will persist at
+ <a href="http://www.w3.org/2009/01/xml.xsd">
+ http://www.w3.org/2009/01/xml.xsd</a>.
+ </p>
+ <p>
+ At the date of issue it can also be found at
+ <a href="http://www.w3.org/2001/xml.xsd">
+ http://www.w3.org/2001/xml.xsd</a>.
+ </p>
+ <p>
+ The schema document at that URI may however change in the future,
+ in order to remain compatible with the latest version of XML
+ Schema itself, or with the XML namespace itself. In other words,
+ if the XML Schema or XML namespaces change, the version of this
+ document at <a href="http://www.w3.org/2001/xml.xsd">
+ http://www.w3.org/2001/xml.xsd
+ </a>
+ will change accordingly; the version at
+ <a href="http://www.w3.org/2009/01/xml.xsd">
+ http://www.w3.org/2009/01/xml.xsd
+ </a>
+ will not change.
+ </p>
+ <p>
+ Previous dated (and unchanging) versions of this schema
+ document are at:
+ </p>
+ <ul>
+ <li><a href="http://www.w3.org/2009/01/xml.xsd">
+ http://www.w3.org/2009/01/xml.xsd</a></li>
+ <li><a href="http://www.w3.org/2007/08/xml.xsd">
+ http://www.w3.org/2007/08/xml.xsd</a></li>
+ <li><a href="http://www.w3.org/2004/10/xml.xsd">
+ http://www.w3.org/2004/10/xml.xsd</a></li>
+ <li><a href="http://www.w3.org/2001/03/xml.xsd">
+ http://www.w3.org/2001/03/xml.xsd</a></li>
+ </ul>
+ </div>
+ </div>
+ </xs:documentation>
+ </xs:annotation>
+
+</xs:schema>
+
View
14 lib/ruby_speech.rb
@@ -1,5 +1,15 @@
-require "ruby_speech/version"
+%w{
+ active_support/dependencies/autoload
+ active_support/core_ext/object/blank
+ active_support/core_ext/numeric/time
+ niceogiri
+}.each { |f| require f }
module RubySpeech
- # Your code goes here...
+ extend ActiveSupport::Autoload
+
+ autoload :Version
+
+ autoload :SSML
+ autoload :XML
end
View
24 lib/ruby_speech/ssml.rb
@@ -0,0 +1,24 @@
+module RubySpeech
+ module SSML
+ extend ActiveSupport::Autoload
+
+ autoload :Break
+ autoload :Element
+ autoload :Emphasis
+ autoload :Prosody
+ autoload :SayAs
+ autoload :Speak
+ autoload :Voice
+
+ InvalidChildError = Class.new StandardError
+
+ def self.draw(&block)
+ Nokogiri::XML::Document.new.tap do |doc|
+ doc << Speak.new.tap do |speak|
+ block_return = speak.instance_eval(&block) if block_given?
+ speak << block_return if block_return.is_a?(String)
+ end
+ end.to_s
+ end
+ end # SSML
+end # RubySpeech
View
71 lib/ruby_speech/ssml/break.rb
@@ -0,0 +1,71 @@
+module RubySpeech
+ module SSML
+ ##
+ # The break element is an empty element that controls the pausing or other prosodic boundaries between words. The use of the break element between any pair of words is optional. If the element is not present between words, the synthesis processor is expected to automatically determine a break based on the linguistic context. In practice, the break element is most often used to override the typical automatic behavior of a synthesis processor.
+ #
+ # http://www.w3.org/TR/speech-synthesis/#S3.2.3
+ #
+ class Break < Element
+
+ VALID_STRENGTHS = [:none, :'x-weak', :weak, :medium, :strong, :'x-strong'].freeze
+
+ ##
+ # Create a new SSML break element
+ #
+ # @param [Hash] atts Key-value pairs of options mapping to setter methods
+ #
+ # @return [Break] an element for use in an SSML document
+ #
+ def self.new(atts = {}, &block)
+ super 'break', atts, &block
+ end
+
+ ##
+ # This attribute is used to indicate the strength of the prosodic break in the speech output. The value "none" indicates that no prosodic break boundary should be outputted, which can be used to prevent a prosodic break which the processor would otherwise produce. The other values indicate monotonically non-decreasing (conceptually increasing) break strength between words. The stronger boundaries are typically accompanied by pauses. "x-weak" and "x-strong" are mnemonics for "extra weak" and "extra strong", respectively.
+ #
+ # @return [Symbol]
+ #
+ def strength
+ read_attr :strength, :to_sym
+ end
+
+ ##
+ # @param [Symbol] the strength. Must be one of VALID_STRENGTHS
+ #
+ # @raises ArgumentError if s is not one of VALID_STRENGTHS
+ #
+ def strength=(s)
+ raise ArgumentError, "You must specify a valid strength (#{VALID_STRENGTHS.map(&:inspect).join ', '})" unless VALID_STRENGTHS.include? s
+ write_attr :strength, s
+ end
+
+ ##
+ # Indicates the duration of a pause to be inserted in the output in seconds or milliseconds. It follows the time value format from the Cascading Style Sheets Level 2 Recommendation [CSS2], e.g. "250ms", "3s".
+ #
+ # @return [Float]
+ #
+ def time
+ read_attr :time, :to_f
+ end
+
+ ##
+ # @param [Numeric] t the time as a positive value in seconds
+ #
+ # @raises ArgumentError if t is nota positive numeric value
+ #
+ def time=(t)
+ raise ArgumentError, "You must specify a valid time (positive float value in seconds)" unless t.is_a?(Numeric) && t >= 0
+ write_attr :time, "#{t}s"
+ end
+
+ def <<(*args)
+ raise InvalidChildError, "A Break cannot contain children"
+ super
+ end
+
+ def eql?(o)
+ super o, :strength, :time
+ end
+ end # Break
+ end # SSML
+end # RubySpeech
View
26 lib/ruby_speech/ssml/element.rb
@@ -0,0 +1,26 @@
+module RubySpeech
+ module SSML
+ class Element < Niceogiri::XML::Node
+ def self.new(element_name, atts = {}, &block)
+ super element_name do |new_node|
+ atts.each_pair { |k, v| new_node.send :"#{k}=", v }
+ block_return = new_node.instance_eval &block if block_given?
+ new_node << block_return if block_return.is_a?(String)
+ end
+ end
+
+ def method_missing(method_name, *args, &block)
+ const = SSML.const_get(method_name.to_s.titleize.gsub(' ', ''))
+ if const && self.valid_child_type?(const)
+ self << const.new(*args, &block)
+ else
+ super
+ end
+ end
+
+ def eql?(o, *args)
+ super o, :content, *args
+ end
+ end # Element
+ end # SSML
+end # RubySpeech
View
53 lib/ruby_speech/ssml/emphasis.rb
@@ -0,0 +1,53 @@
+module RubySpeech
+ module SSML
+ ##
+ # The emphasis element requests that the contained text be spoken with emphasis (also referred to as prominence or stress). The synthesis processor determines how to render emphasis since the nature of emphasis differs between languages, dialects or even voices.
+ #
+ # http://www.w3.org/TR/speech-synthesis/#S3.2.2
+ #
+ class Emphasis < Element
+
+ VALID_LEVELS = [:strong, :moderate, :none, :reduced].freeze
+ VALID_CHILD_TYPES = [String, Break, Emphasis, Prosody, SayAs, Voice].freeze
+
+ ##
+ # Create a new SSML emphasis element
+ #
+ # @param [Hash] atts Key-value pairs of options mapping to setter methods
+ #
+ # @return [Emphasis] an element for use in an SSML document
+ #
+ def self.new(atts = {}, &block)
+ super 'emphasis', atts, &block
+ end
+
+ ##
+ # Indicates the strength of emphasis to be applied. Defined values are "strong", "moderate", "none" and "reduced". The default level is "moderate". The meaning of "strong" and "moderate" emphasis is interpreted according to the language being spoken (languages indicate emphasis using a possible combination of pitch change, timing changes, loudness and other acoustic differences). The "reduced" level is effectively the opposite of emphasizing a word. For example, when the phrase "going to" is reduced it may be spoken as "gonna". The "none" level is used to prevent the synthesis processor from emphasizing words that it might typically emphasize. The values "none", "moderate", and "strong" are monotonically non-decreasing in strength.
+ #
+ # @return [Symbol]
+ #
+ def level
+ read_attr :level, :to_sym
+ end
+
+ ##
+ # @param [Symbol] l the level. Must be one of VALID_LEVELS
+ #
+ # @raises ArgumentError if l is not one of VALID_LEVELS
+ #
+ def level=(l)
+ raise ArgumentError, "You must specify a valid level (#{VALID_LEVELS.map(&:inspect).join ', '})" unless VALID_LEVELS.include? l
+ write_attr :level, l
+ end
+
+ def <<(arg)
+ raise InvalidChildError, "An Emphasis can only accept String, Audio, Break, Emphasis, Mark, Phoneme, Prosody, SayAs, Sub, Voice as children" unless VALID_CHILD_TYPES.include? arg.class
+ super
+ end
+
+ def eql?(o)
+ super o, :level
+ end
+ end # Emphasis
+ end # SSML
+end # RubySpeech
View
180 lib/ruby_speech/ssml/prosody.rb
@@ -0,0 +1,180 @@
+module RubySpeech
+ module SSML
+ ##
+ # The prosody element permits control of the pitch, speaking rate and volume of the speech output.
+ #
+ # http://www.w3.org/TR/speech-synthesis/#S3.2.4
+ #
+ # Although each attribute individually is optional, it is an error if no attributes are specified when the prosody element is used. The "x-foo" attribute value names are intended to be mnemonics for "extra foo". Note also that customary pitch levels and standard pitch ranges may vary significantly by language, as may the meanings of the labelled values for pitch targets and ranges.
+ #
+ # The duration attribute takes precedence over the rate attribute. The contour attribute takes precedence over the pitch and range attributes.
+ #
+ # The default value of all prosodic attributes is no change. For example, omitting the rate attribute means that the rate is the same within the element as outside.
+ #
+ class Prosody < Element
+
+ VALID_PITCHES = [:'x-low', :low, :medium, :high, :'x-high', :default].freeze
+ VALID_VOLUMES = [:silent, :'x-soft', :soft, :medium, :loud, :'x-loud', :default].freeze
+ VALID_RATES = [:'x-slow', :slow, :medium, :fast, :'x-fast', :default].freeze
+ VALID_CHILD_TYPES = [String, Break, Emphasis, Prosody, SayAs, Voice].freeze
+
+ ##
+ # Create a new SSML prosody element
+ #
+ # @param [Hash] atts Key-value pairs of options mapping to setter methods
+ #
+ # @return [Prosody] an element for use in an SSML document
+ #
+ def self.new(atts = {}, &block)
+ super 'prosody', atts, &block
+ end
+
+ ##
+ # The baseline pitch for the contained text. Although the exact meaning of "baseline pitch" will vary across synthesis processors, increasing/decreasing this value will typically increase/decrease the approximate pitch of the output. Legal values are: a number followed by "Hz", a relative change or "x-low", "low", "medium", "high", "x-high", or "default". Labels "x-low" through "x-high" represent a sequence of monotonically non-decreasing pitch levels.
+ #
+ # @return [Symbol, String]
+ #
+ def pitch
+ value = read_attr :pitch
+ return unless value
+ if value.include?('Hz')
+ value
+ elsif VALID_PITCHES.include?(value.to_sym)
+ value.to_sym
+ end
+ end
+
+ ##
+ # @param [Symbol, String] p
+ #
+ # @raises ArgumentError if p is not a string that contains 'Hz' or one of VALID_PITCHES
+ #
+ def pitch=(p)
+ hz = p.is_a?(String) && p.include?('Hz') && p.to_f > 0
+ raise ArgumentError, "You must specify a valid pitch (\"[positive-number]Hz\", #{VALID_PITCHES.map(&:inspect).join ', '})" unless hz || VALID_PITCHES.include?(p)
+ write_attr :pitch, p
+ end
+
+ ##
+ # The actual pitch contour for the contained text.
+ #
+ # The pitch contour is defined as a set of white space-separated targets at specified time positions in the speech output. The algorithm for interpolating between the targets is processor-specific. In each pair of the form (time position,target), the first value is a percentage of the period of the contained text (a number followed by "%") and the second value is the value of the pitch attribute (a number followed by "Hz", a relative change, or a label value). Time position values outside 0% to 100% are ignored. If a pitch value is not defined for 0% or 100% then the nearest pitch target is copied. All relative values for the pitch are relative to the pitch value just before the contained text.
+ #
+ # @return [Symbol]
+ #
+ def contour
+ read_attr :contour
+ end
+
+ ##
+ # @param [String] v
+ #
+ def contour=(v)
+ write_attr :contour, v
+ end
+
+ ##
+ # The pitch range (variability) for the contained text. Although the exact meaning of "pitch range" will vary across synthesis processors, increasing/decreasing this value will typically increase/decrease the dynamic range of the output pitch. Legal values are: a number followed by "Hz", a relative change or "x-low", "low", "medium", "high", "x-high", or "default". Labels "x-low" through "x-high" represent a sequence of monotonically non-decreasing pitch ranges.
+ #
+ # @return [Symbol]
+ #
+ def range
+ value = read_attr :range
+ return unless value
+ if value.include?('Hz')
+ value
+ elsif VALID_PITCHES.include?(value.to_sym)
+ value.to_sym
+ end
+ end
+
+ ##
+ # @param [Symbol, String] p
+ #
+ # @raises ArgumentError if p is not a string that contains 'Hz' or one of VALID_PITCHES
+ #
+ def range=(p)
+ hz = p.is_a?(String) && p.include?('Hz') && p.to_f > 0
+ raise ArgumentError, "You must specify a valid range (\"[positive-number]Hz\", #{VALID_PITCHES.map(&:inspect).join ', '})" unless hz || VALID_PITCHES.include?(p)
+ write_attr :range, p
+ end
+
+ ##
+ # A change in the speaking rate for the contained text. Legal values are: a relative change or "x-slow", "slow", "medium", "fast", "x-fast", or "default". Labels "x-slow" through "x-fast" represent a sequence of monotonically non-decreasing speaking rates. When a number is used to specify a relative change it acts as a multiplier of the default rate. For example, a value of 1 means no change in speaking rate, a value of 2 means a speaking rate twice the default rate, and a value of 0.5 means a speaking rate of half the default rate. The default rate for a voice depends on the language and dialect and on the personality of the voice. The default rate for a voice should be such that it is experienced as a normal speaking rate for the voice when reading aloud text. Since voices are processor-specific, the default rate will be as well.
+ #
+ # @return [Symbol, Float]
+ #
+ def rate
+ value = read_attr :rate
+ return unless value
+ if VALID_RATES.include?(value.to_sym)
+ value.to_sym
+ else
+ value.to_f
+ end
+ end
+
+ ##
+ # @param [Symbol, Numeric] v
+ #
+ # @raises ArgumentError if v is not either a positive Numeric or one of VALID_RATES
+ #
+ def rate=(v)
+ raise ArgumentError, "You must specify a valid rate ([positive-number](multiplier), #{VALID_RATES.map(&:inspect).join ', '})" unless (v.is_a?(Numeric) && v >= 0) || VALID_RATES.include?(v)
+ write_attr :rate, v
+ end
+
+ ##
+ # A value in seconds for the desired time to take to read the element contents.
+ #
+ # @return [Integer]
+ #
+ def duration
+ read_attr :duration, :to_i
+ end
+
+ ##
+ # @param [Numeric] t
+ #
+ # @raises ArgumentError if t is not a positive numeric value
+ #
+ def duration=(t)
+ raise ArgumentError, "You must specify a valid duration (positive float value in seconds)" unless t.is_a?(Numeric) && t >= 0
+ write_attr :duration, "#{t}s"
+ end
+
+ ##
+ # The volume for the contained text in the range 0.0 to 100.0 (higher values are louder and specifying a value of zero is equivalent to specifying "silent"). Legal values are: number, a relative change or "silent", "x-soft", "soft", "medium", "loud", "x-loud", or "default". The volume scale is linear amplitude. The default is 100.0. Labels "silent" through "x-loud" represent a sequence of monotonically non-decreasing volume levels.
+ #
+ # @return [Symbol, Float]
+ #
+ def volume
+ value = read_attr :volume
+ if VALID_VOLUMES.include?(value.to_sym)
+ value.to_sym
+ else
+ value.to_f
+ end
+ end
+
+ ##
+ # @param [Numeric, Symbol] v
+ #
+ # @raises ArgumentError if v is not one of VALID_VOLUMES or a numeric value between 0.0 and 100.0
+ #
+ def volume=(v)
+ raise ArgumentError, "You must specify a valid volume ([positive-number](0.0 -> 100.0), #{VALID_VOLUMES.map(&:inspect).join ', '})" unless (v.is_a?(Numeric) && (0..100).include?(v)) || VALID_VOLUMES.include?(v)
+ write_attr :volume, v
+ end
+
+ def <<(arg)
+ raise InvalidChildError, "A Prosody can only accept String, Audio, Break, Emphasis, Mark, P, Phoneme, Prosody, SayAs, Sub, S, Voice as children" unless VALID_CHILD_TYPES.include? arg.class
+ super
+ end
+
+ def eql?(o)
+ super o, :pitch, :contour, :range, :rate, :duration, :volume
+ end
+ end # Prosody
+ end # SSML
+end # RubySpeech
View
109 lib/ruby_speech/ssml/say_as.rb
@@ -0,0 +1,109 @@
+module RubySpeech
+ module SSML
+ ##
+ # The say-as element allows the author to indicate information on the type of text construct contained within the element and to help specify the level of detail for rendering the contained text.
+ #
+ # http://www.w3.org/TR/speech-synthesis/#S3.1.8
+ #
+ # Defining a comprehensive set of text format types is difficult because of the variety of languages that have to be considered and because of the innate flexibility of written languages. SSML only specifies the say-as element, its attributes, and their purpose. It does not enumerate the possible values for the attributes. The Working Group expects to produce a separate document that will define standard values and associated normative behavior for these values. Examples given here are only for illustrating the purpose of the element and the attributes.
+ #
+ # The say-as element has three attributes: interpret-as, format, and detail. The interpret-as attribute is always required; the other two attributes are optional. The legal values for the format attribute depend on the value of the interpret-as attribute.
+ #
+ # The say-as element can only contain text to be rendered.
+ #
+ # When specified, the interpret-as and format values are to be interpreted by the synthesis processor as hints provided by the markup document author to aid text normalization and pronunciation.
+ #
+ # In all cases, the text enclosed by any say-as element is intended to be a standard, orthographic form of the language currently in context. A synthesis processor should be able to support the common, orthographic forms of the specified language for every content type that it supports.
+ #
+ # When the content of the say-as element contains additional text next to the content that is in the indicated format and interpret-as type, then this additional text must be rendered. The processor may make the rendering of the additional text dependent on the interpret-as type of the element in which it appears.
+ # When the content of the say-as element contains no content in the indicated interpret-as type or format, the processor must render the content either as if the format attribute were not present, or as if the interpret-as attribute were not present, or as if neither the format nor interpret-as attributes were present. The processor should also notify the environment of the mismatch.
+ #
+ # Indicating the content type or format does not necessarily affect the way the information is pronounced. A synthesis processor should pronounce the contained text in a manner in which such content is normally produced for the language.
+ #
+ class SayAs < Element
+
+ VALID_CHILD_TYPES = [String].freeze
+
+ ##
+ # Create a new SSML say-as element
+ #
+ # @param [Hash] atts Key-value pairs of options mapping to setter methods
+ #
+ # @return [Prosody] an element for use in an SSML document
+ #
+ def self.new(interpret_as, atts = {}, &block)
+ super 'say-as', atts.merge(interpret_as: interpret_as), &block
+ end
+
+ ##
+ #
+ # The interpret-as attribute indicates the content type of the contained text construct. Specifying the content type helps the synthesis processor to distinguish and interpret text constructs that may be rendered in different ways depending on what type of information is intended.
+ #
+ # When the value for the interpret-as attribute is unknown or unsupported by a processor, it must render the contained text as if no interpret-as value were specified.
+ #
+ # @return [String]
+ #
+ def interpret_as
+ read_attr :'interpret-as'
+ end
+
+ ##
+ # @param [String] ia
+ #
+ def interpret_as=(ia)
+ write_attr :'interpret-as', ia
+ end
+
+ ##
+ #
+ # Can give further hints on the precise formatting of the contained text for content types that may have ambiguous formats.
+ #
+ # When the value for the format attribute is unknown or unsupported by a processor, it must render the contained text as if no format value were specified, and should render it using the interpret-as value that is specified.
+ #
+ # @return [String]
+ #
+ def format
+ read_attr :format
+ end
+
+ ##
+ # @param [String] format
+ #
+ def format=(format)
+ write_attr :format, format
+ end
+
+ ##
+ #
+ # The detail attribute is an optional attribute that indicates the level of detail to be read aloud or rendered. Every value of the detail attribute must render all of the informational content in the contained text; however, specific values for the detail attribute can be used to render content that is not usually informational in running text but may be important to render for specific purposes. For example, a synthesis processor will usually render punctuations through appropriate changes in prosody. Setting a higher level of detail may be used to speak punctuations explicitly, e.g. for reading out coded part numbers or pieces of software code.
+ #
+ # The detail attribute can be used for all interpret-as types.
+ #
+ # If the detail attribute is not specified, the level of detail that is produced by the synthesis processor depends on the text content and the language.
+ #
+ # When the value for the detail attribute is unknown or unsupported by a processor, it must render the contained text as if no value were specified for the detail attribute.
+ #
+ # @return [String]
+ #
+ def detail
+ read_attr :detail
+ end
+
+ ##
+ # @param [String] detail
+ #
+ def detail=(detail)
+ write_attr :detail, detail
+ end
+
+ def <<(arg)
+ raise InvalidChildError, "A SayAs can only accept Strings as children" unless VALID_CHILD_TYPES.include? arg.class
+ super
+ end
+
+ def eql?(o)
+ super o, :interpret_as, :format, :detail
+ end
+ end # SayAs
+ end # SSML
+end # RubySpeech
View
57 lib/ruby_speech/ssml/speak.rb
@@ -0,0 +1,57 @@
+module RubySpeech
+ module SSML
+ ##
+ # The Speech Synthesis Markup Language is an XML application. The root element is speak.
+ #
+ # http://www.w3.org/TR/speech-synthesis/#S3.1.1
+ #
+ class Speak < Element
+ include XML::Language
+
+ VALID_CHILD_TYPES = [String, Break, Emphasis, Prosody, SayAs, Voice].freeze
+
+ ##
+ # Create a new SSML speak root element
+ #
+ # @param [Hash] atts Key-value pairs of options mapping to setter methods
+ #
+ # @return [Speak] an element for use in an SSML document
+ #
+ def self.new(atts = {}, &block)
+ super('speak', atts) do
+ self[:version] = '1.0'
+ self.namespace = 'http://www.w3.org/2001/10/synthesis'
+ self.language ||= "en-US"
+ instance_eval &block if block_given?
+ end
+ end
+
+ ##
+ # @return [String] the base URI to which relative URLs are resolved
+ #
+ def base_uri
+ read_attr :base
+ end
+
+ ##
+ # @param [String] uri the base URI to which relative URLs are resolved
+ #
+ def base_uri=(uri)
+ write_attr 'xml:base', uri
+ end
+
+ def <<(arg)
+ raise InvalidChildError, "A Speak can only accept String, Audio, Break, Emphasis, Mark, P, Phoneme, Prosody, SayAs, Sub, S, Voice as children" unless VALID_CHILD_TYPES.include? arg.class
+ super
+ end
+
+ def valid_child_type?(type)
+ VALID_CHILD_TYPES.include? type
+ end
+
+ def eql?(o)
+ super o, :language, :base_uri
+ end
+ end # Speak
+ end # SSML
+end # RubySpeech
View
125 lib/ruby_speech/ssml/voice.rb
@@ -0,0 +1,125 @@
+module RubySpeech
+ module SSML
+ ##
+ # The voice element is a production element that requests a change in speaking voice.
+ #
+ # http://www.w3.org/TR/speech-synthesis/#S3.2.1
+ #
+ class Voice < Element
+ include XML::Language
+
+ VALID_GENDERS = [:male, :female, :neutral].freeze
+ VALID_CHILD_TYPES = [String, Break, Emphasis, Prosody, SayAs, Voice].freeze
+
+ ##
+ # Create a new SSML voice element
+ #
+ # @param [Hash] atts Key-value pairs of options mapping to setter methods
+ #
+ # @return [Voice] an element for use in an SSML document
+ #
+ def self.new(atts = {}, &block)
+ super 'voice', atts, &block
+ end
+
+ ##
+ # Indicates the preferred gender of the voice to speak the contained text. Enumerated values are: "male", "female", "neutral".
+ #
+ # @return [Symbol]
+ #
+ def gender
+ read_attr :gender, :to_sym
+ end
+
+ ##
+ # @param [Symbol] g the gender selected from VALID_GENDERS
+ #
+ # @raises ArgumentError if g is not one of VALID_GENDERS
+ #
+ def gender=(g)
+ raise ArgumentError, "You must specify a valid gender (#{VALID_GENDERS.map(&:inspect).join ', '})" unless VALID_GENDERS.include? g
+ write_attr :gender, g
+ end
+
+ ##
+ # Indicates the preferred age in years (since birth) of the voice to speak the contained text.
+ #
+ # @return [Integer]
+ #
+ def age
+ read_attr :age, :to_i
+ end
+
+ ##
+ # @param [Integer] i the age of the voice
+ #
+ # @raises ArgumentError if i is not a non-negative integer
+ #
+ def age=(i)
+ raise ArgumentError, "You must specify a valid age (non-negative integer)" unless i.is_a?(Integer) && i >= 0
+ write_attr :age, i
+ end
+
+ ##
+ # Indicates a preferred variant of the other voice characteristics to speak the contained text. (e.g. the second male child voice).
+ #
+ # @return [Integer]
+ #
+ def variant
+ read_attr :variant, :to_i
+ end
+
+ ##
+ # @param [Integer] i the variant of the voice
+ #
+ # @raises ArgumentError if i is not a non-negative integer
+ #
+ def variant=(i)
+ raise ArgumentError, "You must specify a valid variant (positive integer)" unless i.is_a?(Integer) && i > 0
+ write_attr :variant, i
+ end
+
+ ##
+ # A processor-specific voice name to speak the contained text.
+ #
+ # @return [String, Array, nil] the name or names of the voice
+ #
+ def name
+ names = read_attr :name
+ return unless names
+ names = names.split ' '
+ case names.count
+ when 0 then nil
+ when 1 then names.first
+ else names
+ end
+ end
+
+ ##
+ # @param [String, Array] the name or names of the voice. May be an array of names ordered from top preference down. The names must not contain any white space.
+ #
+ def name=(n)
+ # TODO: Raise ArgumentError if names contain whitespace
+ n = n.join(' ') if n.is_a? Array
+ write_attr :name, n
+ end
+
+ def valid_child_types
+ VALID_CHILD_TYPES
+ end
+
+ def <<(arg)
+ raise InvalidChildError, "A Voice can only accept String, Audio, Break, Emphasis, Mark, P, Phoneme, Prosody, SayAs, Sub, S, Voice as children" unless VALID_CHILD_TYPES.include? arg.class
+ super
+ end
+
+ def valid_child_type?(type)
+ VALID_CHILD_TYPES.include? type
+ end
+
+ def eql?(o)
+ super o, :language, :gender, :age, :variant, :name
+ end
+ end # Voice
+ end # SSML
+end # RubySpeech
View
2 lib/ruby_speech/version.rb
@@ -1,3 +1,3 @@
module RubySpeech
- VERSION = "0.0.1"
+ VERSION = "0.1.0"
end
View
7 lib/ruby_speech/xml.rb
@@ -0,0 +1,7 @@
+module RubySpeech
+ module XML
+ extend ActiveSupport::Autoload
+
+ autoload :Language
+ end # XML
+end # RubySpeech
View
13 lib/ruby_speech/xml/language.rb
@@ -0,0 +1,13 @@
+module RubySpeech
+ module XML
+ module Language
+ def language
+ read_attr :lang
+ end
+
+ def language=(l)
+ write_attr 'xml:lang', l
+ end
+ end # Language
+ end # XML
+end # RubySpeech
View
11 ruby_speech.gemspec
@@ -17,4 +17,15 @@ Gem::Specification.new do |s|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
+
+ s.add_runtime_dependency %q<niceogiri>, [">= 0.0.4"]
+ s.add_runtime_dependency %q<activesupport>, [">= 3.0.7"]
+
+ s.add_development_dependency %q<bundler>, ["~> 1.0.0"]
+ s.add_development_dependency %q<rspec>, ["~> 2.3.0"]
+ s.add_development_dependency %q<ci_reporter>, [">= 1.6.3"]
+ s.add_development_dependency %q<yard>, ["~> 0.6.0"]
+ s.add_development_dependency %q<rake>, [">= 0"]
+ s.add_development_dependency %q<mocha>, [">= 0"]
+ s.add_development_dependency %q<i18n>, [">= 0"]
end
View
85 spec/ruby_speech/ssml/break_spec.rb
@@ -0,0 +1,85 @@
+require 'spec_helper'
+
+module RubySpeech
+ module SSML
+ describe Break do
+ its(:name) { should == 'break' }
+
+ describe "setting options in initializers" do
+ subject { Break.new :strength => :strong, :time => 3.seconds }
+
+ its(:strength) { should == :strong }
+ its(:time) { should == 3.seconds }
+ end
+
+ describe "#strength" do
+ before { subject.strength = :strong }
+
+ its(:strength) { should == :strong }
+
+ it "with a valid level" do
+ lambda { subject.strength = :none }.should_not raise_error
+ lambda { subject.strength = :'x-weak' }.should_not raise_error
+ lambda { subject.strength = :weak }.should_not raise_error
+ lambda { subject.strength = :medium }.should_not raise_error
+ lambda { subject.strength = :strong }.should_not raise_error
+ lambda { subject.strength = :'x-strong' }.should_not raise_error
+ end
+
+ it "with an invalid strength" do
+ lambda { subject.strength = :something }.should raise_error(ArgumentError, "You must specify a valid strength (:none, :\"x-weak\", :weak, :medium, :strong, :\"x-strong\")")
+ end
+ end
+
+ describe "#time" do
+ context "with a valid value" do
+ before { subject.time = 3.seconds }
+
+ its(:time) { should == 3.seconds }
+ end
+
+ context "with a negative value" do
+ it do
+ lambda { subject.time = -3.seconds }.should raise_error(ArgumentError, "You must specify a valid time (positive float value in seconds)")
+ end
+ end
+
+ context "with an invalid value" do
+ it do
+ lambda { subject.time = 'blah' }.should raise_error(ArgumentError, "You must specify a valid time (positive float value in seconds)")
+ end
+ end
+ end
+
+ describe "<<" do
+ it "should always raise InvalidChildError" do
+ lambda { subject << 'anything' }.should raise_error(InvalidChildError, "A Break cannot contain children")
+ end
+ end
+
+ describe "comparing objects" do
+ it "should be equal if the content, strength and base uri are the same" do
+ Break.new(strength: :strong, time: 1.second, content: "Hello there").should == Break.new(strength: :strong, time: 1.second, content: "Hello there")
+ end
+
+ describe "when the content is different" do
+ it "should not be equal" do
+ Break.new(content: "Hello").should_not == Break.new(content: "Hello there")
+ end
+ end
+
+ describe "when the strength is different" do
+ it "should not be equal" do
+ Break.new(strength: :strong).should_not == Break.new(strength: :weak)
+ end
+ end
+
+ describe "when the time is different" do
+ it "should not be equal" do
+ Break.new(time: 1.second).should_not == Break.new(time: 2.seconds)
+ end
+ end
+ end
+ end # Break
+ end # SSML
+end # RubySpeech
View
100 spec/ruby_speech/ssml/emphasis_spec.rb
@@ -0,0 +1,100 @@
+require 'spec_helper'
+
+module RubySpeech
+ module SSML
+ describe Emphasis do
+ its(:name) { should == 'emphasis' }
+
+ describe "setting options in initializers" do
+ subject { Emphasis.new :level => :strong }
+
+ its(:level) { should == :strong }
+ end
+
+ describe "#level" do
+ before { subject.level = :strong }
+
+ its(:level) { should == :strong }
+
+ it "with a valid level" do
+ lambda { subject.level = :strong }.should_not raise_error
+ lambda { subject.level = :moderate }.should_not raise_error
+ lambda { subject.level = :none }.should_not raise_error
+ lambda { subject.level = :reduced }.should_not raise_error
+ end
+
+ it "with an invalid level" do
+ lambda { subject.level = :something }.should raise_error(ArgumentError, "You must specify a valid level (:strong, :moderate, :none, :reduced)")
+ end
+ end
+
+ describe "comparing objects" do
+ it "should be equal if the content and level are the same" do
+ Emphasis.new(level: :strong, content: "Hello there").should == Emphasis.new(level: :strong, content: "Hello there")
+ end
+
+ describe "when the content is different" do
+ it "should not be equal" do
+ Emphasis.new(content: "Hello").should_not == Emphasis.new(content: "Hello there")
+ end
+ end
+
+ describe "when the level is different" do
+ it "should not be equal" do
+ Emphasis.new(level: :strong).should_not == Emphasis.new(level: :reduced)
+ end
+ end
+ end
+
+ describe "<<" do
+ it "should accept String" do
+ lambda { subject << 'anything' }.should_not raise_error
+ end
+
+ it "should accept Audio" do
+ pending
+ lambda { subject << Audio.new }.should_not raise_error
+ end
+
+ it "should accept Break" do
+ lambda { subject << Break.new }.should_not raise_error
+ end
+
+ it "should accept Emphasis" do
+ lambda { subject << Emphasis.new }.should_not raise_error
+ end
+
+ it "should accept Mark" do
+ pending
+ lambda { subject << Mark.new }.should_not raise_error
+ end
+
+ it "should accept Phoneme" do
+ pending
+ lambda { subject << Phoneme.new }.should_not raise_error
+ end
+
+ it "should accept Prosody" do
+ lambda { subject << Prosody.new }.should_not raise_error
+ end
+
+ it "should accept SayAs" do
+ lambda { subject << SayAs.new(:interpret_as => :foo) }.should_not raise_error
+ end
+
+ it "should accept Sub" do
+ pending
+ lambda { subject << Sub.new }.should_not raise_error
+ end
+
+ it "should accept Voice" do
+ lambda { subject << Voice.new }.should_not raise_error
+ end
+
+ it "should raise InvalidChildError with non-acceptable objects" do
+ lambda { subject << 1 }.should raise_error(InvalidChildError, "An Emphasis can only accept String, Audio, Break, Emphasis, Mark, Phoneme, Prosody, SayAs, Sub, Voice as children")
+ end
+ end
+ end # Emphasis
+ end # SSML
+end # RubySpeech
View
286 spec/ruby_speech/ssml/prosody_spec.rb
@@ -0,0 +1,286 @@
+require 'spec_helper'
+
+module RubySpeech
+ module SSML
+ describe Prosody do
+ its(:name) { should == 'prosody' }
+
+ describe "setting options in initializers" do
+ subject { Prosody.new :pitch => :medium, :contour => "something", :range => '20Hz', :rate => 2, :duration => 10.seconds, :volume => :loud }
+
+ its(:pitch) { should == :medium }
+ its(:contour) { should == 'something' }
+ its(:range) { should == '20Hz' }
+ its(:rate) { should == 2 }
+ its(:duration) { should == 10.seconds }
+ its(:volume) { should == :loud }
+ end
+
+ describe "#pitch" do
+ context "with a pre-defined value" do
+ before { subject.pitch = :medium }
+
+ its(:pitch) { should == :medium }
+
+ it "with a valid value" do
+ lambda { subject.pitch = :'x-low' }.should_not raise_error
+ lambda { subject.pitch = :low }.should_not raise_error
+ lambda { subject.pitch = :medium }.should_not raise_error
+ lambda { subject.pitch = :high }.should_not raise_error
+ lambda { subject.pitch = :'x-high' }.should_not raise_error
+ lambda { subject.pitch = :default }.should_not raise_error
+ end
+
+ it "with an invalid value" do
+ lambda { subject.pitch = :something }.should raise_error(ArgumentError, "You must specify a valid pitch (\"[positive-number]Hz\", :\"x-low\", :low, :medium, :high, :\"x-high\", :default)")
+ end
+ end
+
+ context "with a Hertz value" do
+ describe "with a valid value" do
+ before { subject.pitch = '440Hz' }
+
+ its(:pitch) { should == '440Hz' }
+ end
+
+ it "with a negative value" do
+ lambda { subject.pitch = "-100Hz" }.should raise_error(ArgumentError, "You must specify a valid pitch (\"[positive-number]Hz\", :\"x-low\", :low, :medium, :high, :\"x-high\", :default)")
+ end
+
+ it "when missing 'hz'" do
+ lambda { subject.pitch = "440" }.should raise_error(ArgumentError, "You must specify a valid pitch (\"[positive-number]Hz\", :\"x-low\", :low, :medium, :high, :\"x-high\", :default)")
+ end
+ end
+ end
+
+ describe "#contour" do
+ before { subject.contour = "blah" }
+
+ its(:contour) { should == "blah" }
+ end
+
+ describe "#range" do
+ context "with a pre-defined value" do
+ before { subject.range = :medium }
+
+ its(:range) { should == :medium }
+
+ it "with a valid value" do
+ lambda { subject.range = :'x-low' }.should_not raise_error
+ lambda { subject.range = :low }.should_not raise_error
+ lambda { subject.range = :medium }.should_not raise_error
+ lambda { subject.range = :high }.should_not raise_error
+ lambda { subject.range = :'x-high' }.should_not raise_error
+ lambda { subject.range = :default }.should_not raise_error
+ end
+
+ it "with an invalid value" do
+ lambda { subject.range = :something }.should raise_error(ArgumentError, "You must specify a valid range (\"[positive-number]Hz\", :\"x-low\", :low, :medium, :high, :\"x-high\", :default)")
+ end
+ end
+
+ context "with a Hertz value" do
+ describe "with a valid value" do
+ before { subject.range = '440Hz' }
+
+ its(:range) { should == '440Hz' }
+ end
+
+ it "with a negative value" do
+ lambda { subject.range = "-100Hz" }.should raise_error(ArgumentError, "You must specify a valid range (\"[positive-number]Hz\", :\"x-low\", :low, :medium, :high, :\"x-high\", :default)")
+ end
+
+ it "when missing 'hz'" do
+ lambda { subject.range = "440" }.should raise_error(ArgumentError, "You must specify a valid range (\"[positive-number]Hz\", :\"x-low\", :low, :medium, :high, :\"x-high\", :default)")
+ end
+ end
+ end
+
+ describe "#rate" do
+ context "with a pre-defined value" do
+ before { subject.rate = :medium }
+
+ its(:rate) { should == :medium }
+
+ it "with a valid value" do
+ lambda { subject.rate = :'x-slow' }.should_not raise_error
+ lambda { subject.rate = :slow }.should_not raise_error
+ lambda { subject.rate = :medium }.should_not raise_error
+ lambda { subject.rate = :fast }.should_not raise_error
+ lambda { subject.rate = :'x-fast' }.should_not raise_error
+ lambda { subject.rate = :default }.should_not raise_error
+ end
+
+ it "with an invalid value" do
+ lambda { subject.rate = :something }.should raise_error(ArgumentError, "You must specify a valid rate ([positive-number](multiplier), :\"x-slow\", :slow, :medium, :fast, :\"x-fast\", :default)")
+ end
+ end
+
+ context "with a multiplier value" do
+ describe "with a valid value" do
+ before { subject.rate = 1.5 }
+
+ its(:rate) { should == 1.5 }
+ end
+
+ it "with a negative value" do
+ lambda { subject.rate = -100 }.should raise_error(ArgumentError, "You must specify a valid rate ([positive-number](multiplier), :\"x-slow\", :slow, :medium, :fast, :\"x-fast\", :default)")
+ end
+ end
+ end
+
+ describe "#duration" do
+ context "with a valid value" do
+ before { subject.duration = 3.seconds }
+
+ its(:duration) { should == 3.seconds }
+ end
+
+ context "with a negative value" do
+ it do
+ lambda { subject.duration = -3.seconds }.should raise_error(ArgumentError, "You must specify a valid duration (positive float value in seconds)")
+ end
+ end
+
+ context "with an invalid value" do
+ it do
+ lambda { subject.duration = 'blah' }.should raise_error(ArgumentError, "You must specify a valid duration (positive float value in seconds)")
+ end
+ end
+ end
+
+ describe "#volume" do
+ context "with a pre-defined value" do
+ before { subject.volume = :medium }
+
+ its(:volume) { should == :medium }
+
+ it "with a valid value" do
+ lambda { subject.volume = :silent }.should_not raise_error
+ lambda { subject.volume = :'x-soft' }.should_not raise_error
+ lambda { subject.volume = :soft }.should_not raise_error
+ lambda { subject.volume = :medium }.should_not raise_error
+ lambda { subject.volume = :loud }.should_not raise_error
+ lambda { subject.volume = :'x-loud' }.should_not raise_error
+ lambda { subject.volume = :default }.should_not raise_error
+ end
+
+ it "with an invalid value" do
+ lambda { subject.volume = :something }.should raise_error(ArgumentError, "You must specify a valid volume ([positive-number](0.0 -> 100.0), :silent, :\"x-soft\", :soft, :medium, :loud, :\"x-loud\", :default)")
+ end
+ end
+
+ context "with a multiplier" do
+ describe "with a valid value" do
+ before { subject.volume = 1.5 }
+
+ its(:volume) { should == 1.5 }
+ end
+
+ it "with a negative value" do
+ lambda { subject.volume = -1.5 }.should raise_error(ArgumentError, "You must specify a valid volume ([positive-number](0.0 -> 100.0), :silent, :\"x-soft\", :soft, :medium, :loud, :\"x-loud\", :default)")
+ lambda { subject.volume = 100.5 }.should raise_error(ArgumentError, "You must specify a valid volume ([positive-number](0.0 -> 100.0), :silent, :\"x-soft\", :soft, :medium, :loud, :\"x-loud\", :default)")
+ end
+ end
+ end
+
+ describe "comparing objects" do
+ it "should be equal if the content, strength and base uri are the same" do
+ Prosody.new(pitch: :medium, contour: "something", range: '20Hz', rate: 2, duration: 10.seconds, volume: :loud, content: "Hello there").should == Prosody.new(pitch: :medium, contour: "something", range: '20Hz', rate: 2, duration: 10.seconds, volume: :loud, content: "Hello there")
+ end
+
+ describe "when the content is different" do
+ it "should not be equal" do
+ Prosody.new(content: "Hello").should_not == Prosody.new(content: "Hello there")
+ end
+ end
+
+ describe "when the pitch is different" do
+ it "should not be equal" do
+ Prosody.new(pitch: :medium).should_not == Prosody.new(pitch: :high)
+ end
+ end
+
+ describe "when the contour is different" do
+ it "should not be equal" do
+ Prosody.new(contour: 'foo').should_not == Prosody.new(contour: 'bar')
+ end
+ end
+
+ describe "when the range is different" do
+ it "should not be equal" do
+ Prosody.new(range: '20Hz').should_not == Prosody.new(range: '30Hz')
+ end
+ end
+
+ describe "when the rate is different" do
+ it "should not be equal" do
+ Prosody.new(rate: 2).should_not == Prosody.new(rate: 3)
+ end
+ end
+
+ describe "when the duration is different" do
+ it "should not be equal" do
+ Prosody.new(duration: 10.seconds).should_not == Prosody.new(duration: 20.seconds)
+ end
+ end
+
+ describe "when the volume is different" do
+ it "should not be equal" do
+ Prosody.new(volume: :loud).should_not == Prosody.new(volume: :soft)
+ end
+ end
+ end
+
+ describe "<<" do
+ it "should accept String" do
+ lambda { subject << 'anything' }.should_not raise_error
+ end
+
+ it "should accept Audio" do
+ pending
+ lambda { subject << Audio.new }.should_not raise_error
+ end
+
+ it "should accept Break" do
+ lambda { subject << Break.new }.should_not raise_error
+ end
+
+ it "should accept Emphasis" do
+ lambda { subject << Emphasis.new }.should_not raise_error
+ end
+
+ it "should accept Mark" do
+ pending
+ lambda { subject << Mark.new }.should_not raise_error
+ end
+
+ it "should accept Phoneme" do
+ pending
+ lambda { subject << Phoneme.new }.should_not raise_error
+ end
+
+ it "should accept Prosody" do
+ lambda { subject << Prosody.new }.should_not raise_error
+ end
+
+ it "should accept SayAs" do
+ lambda { subject << SayAs.new(:interpret_as => :foo) }.should_not raise_error
+ end
+
+ it "should accept Sub" do
+ pending
+ lambda { subject << Sub.new }.should_not raise_error
+ end
+
+ it "should accept Voice" do
+ lambda { subject << Voice.new }.should_not raise_error
+ end
+
+ it "should raise InvalidChildError with non-acceptable objects" do
+ lambda { subject << 1 }.should raise_error(InvalidChildError, "A Prosody can only accept String, Audio, Break, Emphasis, Mark, P, Phoneme, Prosody, SayAs, Sub, S, Voice as children")
+ end
+ end
+ end # Prosody
+ end # SSML
+end # RubySpeech
View
61 spec/ruby_speech/ssml/say_as_spec.rb
@@ -0,0 +1,61 @@
+require 'spec_helper'
+
+module RubySpeech
+ module SSML
+ describe SayAs do
+ subject { SayAs.new 'one', format: 'two', detail: 'three' }
+
+ its(:name) { should == 'say-as' }
+
+ its(:interpret_as) { should == 'one' }
+ its(:format) { should == 'two' }
+ its(:detail) { should == 'three' }
+
+ describe "without interpret_as" do
+ it "should raise an ArgumentError" do
+ expect { SayAs.new }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "comparing objects" do
+ it "should be equal if the content, interpret_as, format, age, variant, name are the same" do
+ SayAs.new('jp', format: 'foo', detail: 'bar', content: "hello").should == SayAs.new('jp', format: 'foo', detail: 'bar', content: "hello")
+ end
+
+ describe "when the content is different" do
+ it "should not be equal" do
+ SayAs.new('jp', content: "Hello").should_not == SayAs.new('jp', content: "Hello there")
+ end
+ end
+
+ describe "when the interpret_as is different" do
+ it "should not be equal" do
+ SayAs.new("Hello").should_not == SayAs.new("Hello there")
+ end
+ end
+