motion-speech 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3d7d171c14e64dd90873710ad94277b660dd6c33
4
+ data.tar.gz: 4e6522282418bc358c673fc365a3ac9ed1eada5f
5
+ SHA512:
6
+ metadata.gz: 6547b64dde2bb648db4fc586b495c019381fd4069e660114d7acd176ee6010ebeec06d53c8ed500a0f538aac0006257367b7c230e70cd3361f0e9402dd9d5bfd
7
+ data.tar.gz: b6344702f82cf2570820623067cf0480f1f09dc24065dafd7e8ff3293b29983331db3a127e4d8e5562d3ef2fb1e55f4f3ae73873bb89ff37b7e015048d7e37fb
data/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # motion-speech
2
+ Provides a simpler interface to using the AVSpeechSynthesizer related classes available natively in iOS 7.
3
+
4
+ ## Installation
5
+
6
+ Add the following to your project's Gemfile to work with bundler:
7
+
8
+ ```ruby
9
+ gem 'motion-speech'
10
+ ```
11
+
12
+ Install with bundler:
13
+
14
+ ```shell
15
+ bundle install
16
+ ```
17
+
18
+ ### AVFoundation
19
+ This gem includes the `AVFoundation` framework into your project automatically for you.
20
+
21
+ ## Usage
22
+ Some basic usage examples are listed below.
23
+
24
+ ```ruby
25
+ # Speak a sentence
26
+ Motion::Speech::Speaker.speak "Getting started with speech"
27
+
28
+ # Control the rate of speech
29
+ Motion::Speech::Speaker.speak "Getting started with speech", rate: 1
30
+
31
+ # Pass a block to be called when the speech is completed
32
+ Motion::Speech::Speaker.speak "Getting started with speech" do
33
+ puts "completed the utterance"
34
+ end
35
+ ```
36
+
37
+ ## Advanced Usage
38
+ There are several more advanced examples that you can follow below, allowing more customization of the utterance playback including voices (coming soon) as well as contriving arbitrary objects for speech.
39
+
40
+ ### Speakable
41
+
42
+ ```ruby
43
+ class Name < String
44
+ def to_speakable
45
+ "My name is #{self}"
46
+ end
47
+ end
48
+
49
+ my_name = Name.new("Matt Brewer")
50
+ Motion::Speech::Speaker.speak my_name
51
+ # => "My name is Matt Brewer" spoken
52
+ ```
53
+
54
+ ### Callbacks as blocks
55
+ This will look somewhat familiar to Rails developers, can work off a system of block callbacks for further control.
56
+
57
+ ```ruby
58
+ Motion::Speech::Speaker.speak "lorem" do |events|
59
+ events.start do |speaker|
60
+ puts "started speaking: '#{speaker.message}'"
61
+ end
62
+
63
+ events.finish do |speaker|
64
+ puts "finished speaking: '#{speaker.message}'"
65
+ end
66
+
67
+ events.pause do |speaker|
68
+ puts "paused while speaking: '#{speaker.message}'"
69
+ end
70
+
71
+ events.cancel do |speaker|
72
+ puts "canceled while speaking: '#{speaker.message}'"
73
+ end
74
+
75
+ events.resume do |speaker|
76
+ puts "resumed speaking: '#{speaker.message}'"
77
+ end
78
+ end
79
+ ```
80
+
81
+ ### Using methods for callbacks
82
+ This is not unique to RubyMotion, but you can easily grab a block from a method on your class to use as a callback here too.
83
+
84
+ ```ruby
85
+ class SomeController < UIViewController
86
+
87
+ def tapped_button(*args)
88
+ Motion::Speech::Speaker.speak "lorem" do |events|
89
+ events.start &method(:lock_ui)
90
+ events.finish &method(:unlock_ui)
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def lock_iu(speaker)
97
+ self.view.userInteractionEnabled = false
98
+ end
99
+
100
+ def unlock_ui(speaker)
101
+ self.view.userInteractionEnabled = true
102
+ end
103
+ end
104
+ ```
105
+
106
+ ### Controlling playback
107
+
108
+ ```ruby
109
+ speaker = Motion::Speech::Speaker.speak "lorem"
110
+
111
+ # pausing playback accepts symbols or actual structs
112
+ speaker.pause :word
113
+ speaker.pause :immediate
114
+ speaker.pause AVSpeechBoundaryImmediate
115
+
116
+ speaker.paused?
117
+ => true
118
+
119
+ speaker.speaking?
120
+ => false
121
+
122
+ # stopping playback accepts symbols or actual structs
123
+ speaker.stop :word
124
+ speaker.stop :immediate
125
+ speaker.stop AVSpeechBoundaryImmediate
126
+
127
+ # resume playback
128
+ speaker.resume
129
+ ```
@@ -0,0 +1,15 @@
1
+ unless defined?(Motion::Project::Config)
2
+ raise "This file must be required within a RubyMotion project Rakefile."
3
+ end
4
+
5
+ lib_dir_path = File.dirname(File.expand_path(__FILE__))
6
+ Motion::Project::App.setup do |app|
7
+ gem_files = Dir.glob(File.join(lib_dir_path, "motion/**/*.rb"))
8
+ app.files.unshift(gem_files).flatten!
9
+
10
+ if app.deployment_target.to_f < 7.0
11
+ warn "AVSpeechSynthesizer and friends are only available in iOS 7.0+"
12
+ end
13
+
14
+ app.frameworks += %w(AVFoundation)
15
+ end
@@ -0,0 +1,23 @@
1
+ module Motion
2
+ module Speech
3
+ class EventBlock
4
+
5
+ Events = %w(start finish cancel pause resume).freeze
6
+
7
+ Events.each do |method|
8
+ define_method method do |*args, &block|
9
+ if !block.nil?
10
+ instance_variable_set("@#{method}_block", block)
11
+ else
12
+ instance_variable_get("@#{method}_block")
13
+ end
14
+ end
15
+ end
16
+
17
+ def call(event, speaker)
18
+ block = send(event)
19
+ block.call(speaker) unless block.nil?
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,114 @@
1
+ module Motion
2
+ module Speech
3
+ class Speaker
4
+ attr_reader :message, :options
5
+
6
+ MultipleCallsToSpeakError = Class.new(StandardError)
7
+
8
+ def self.speak(*args, &block)
9
+ new(*args, &block).speak
10
+ end
11
+
12
+ def initialize(speakable, options={}, &block)
13
+ @message = string_from_speakable(speakable)
14
+ @options = options
15
+ @spoken = false
16
+
17
+ if block_given?
18
+ if block.arity == 0
19
+ events.finish &block
20
+ elsif block.arity == 1
21
+ block.call events
22
+ else
23
+ raise ArgumentError, 'block must accept either 0 or 1 arguments'
24
+ end
25
+ end
26
+ end
27
+
28
+ def speak
29
+ raise MultipleCallsToSpeakError if @spoken
30
+
31
+ synthesizer.speakUtterance utterance
32
+ @spoken = true
33
+ self
34
+ end
35
+
36
+ def pause(boundary)
37
+ synthesizer.pauseSpeakingAtBoundary boundary_from_symbol(boundary)
38
+ end
39
+
40
+ def stop(boundary)
41
+ synthesizer.stopSpeakingAtBoundary boundary_from_symbol(boundary)
42
+ end
43
+
44
+ def resume
45
+ synthesizer.continueSpeaking
46
+ end
47
+
48
+ def paused?
49
+ synthesizer.paused?
50
+ end
51
+
52
+ def speaking?
53
+ synthesizer.speaking?
54
+ end
55
+
56
+ def utterance
57
+ return @utterance unless @utterance.nil?
58
+
59
+ @utterance = AVSpeechUtterance.speechUtteranceWithString(message)
60
+ @utterance.rate = options.fetch(:rate, 0.15)
61
+ @utterance
62
+ end
63
+
64
+ def synthesizer
65
+ @synthesizer ||= AVSpeechSynthesizer.new.tap { |s| s.delegate = self }
66
+ end
67
+
68
+ private
69
+
70
+ def speechSynthesizer(s, didFinishSpeechUtterance: utterance)
71
+ events.call :finish, self
72
+ end
73
+
74
+ def speechSynthesizer(s, didStartSpeechUtterance: utterance)
75
+ events.call :start, self
76
+ end
77
+
78
+ def speechSynthesizer(s, didCancelSpeechUtterance: utterance)
79
+ events.call :cancel, self
80
+ end
81
+
82
+ def speechSynthesizer(s, didPauseSpeechUtterance: utterance)
83
+ events.call :pause, self
84
+ end
85
+
86
+ def speechSynthesizer(s, didContinueSpeechUtterance: utterance)
87
+ events.call :resume, self
88
+ end
89
+
90
+ def events
91
+ @events ||= EventBlock.new
92
+ end
93
+
94
+ def string_from_speakable(speakable)
95
+ if speakable.respond_to?(:to_speakable)
96
+ speakable.to_speakable
97
+ else
98
+ speakable
99
+ end
100
+ end
101
+
102
+ def boundary_from_symbol(sym)
103
+ case sym
104
+ when :word
105
+ AVSpeechBoundaryWord
106
+ when :immediate
107
+ AVSpeechBoundaryImmediate
108
+ when Fixnum
109
+ sym
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,5 @@
1
+ module Motion
2
+ module Speech
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,27 @@
1
+ describe Motion::Speech::EventBlock do
2
+ before do
3
+ @event = Motion::Speech::EventBlock.new
4
+ end
5
+
6
+ it "has has all the events we need" do
7
+ %w(start finish cancel pause resume).each do |e|
8
+ Motion::Speech::EventBlock::Events.should.include e
9
+ end
10
+ end
11
+
12
+ it "exposes methods for 'start' and 'finish'" do
13
+ @event.should.respond_to "start"
14
+ @event.should.respond_to "finish"
15
+ end
16
+
17
+ it "exposes methods to retrieve the stored block" do
18
+ @event.start { true }
19
+ @event.start.should.be.instance_of Proc
20
+ @event.start.call.should.be.true
21
+ end
22
+
23
+ it "allows you to call an event" do
24
+ @event.start { true }
25
+ @event.call(:start, nil).should.be.true
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ class Speakable < String
2
+ def to_speakable
3
+ "speak: #{self}"
4
+ end
5
+ end
@@ -0,0 +1,166 @@
1
+ describe Motion::Speech::Speaker do
2
+
3
+ describe 'initialization' do
4
+
5
+ it "creates new instance when calling #speak" do
6
+ speaker = Motion::Speech::Speaker.speak "lorem"
7
+ speaker.should.be.instance_of Motion::Speech::Speaker
8
+ end
9
+
10
+ it "stores message" do
11
+ speaker = Motion::Speech::Speaker.new "lorem"
12
+ speaker.message.should.be.equal "lorem"
13
+ end
14
+
15
+ it "accepts an options hash" do
16
+ speaker = Motion::Speech::Speaker.new "lorem", key: :value
17
+ speaker.options.should.be.equal key: :value
18
+ end
19
+
20
+ it "calls #to_speakable on sentence if supported" do
21
+ sentence = Speakable.new("lorem")
22
+ speaker = Motion::Speech::Speaker.new sentence
23
+ speaker.message.should.be.equal sentence.to_speakable
24
+ end
25
+
26
+ it "raises exception if you make multiple calls to #speak" do
27
+ speaker = Motion::Speech::Speaker.new "lorem"
28
+ speaker.speak
29
+
30
+ should.raise(Motion::Speech::Speaker::MultipleCallsToSpeakError) do
31
+ speaker.speak
32
+ end
33
+ end
34
+ end
35
+
36
+ describe '#utterance' do
37
+ before do
38
+ @speaker = Motion::Speech::Speaker.new "lorem"
39
+ end
40
+
41
+ it "returns an AVSpeechUtterance instance" do
42
+ @speaker.utterance.should.be.instance_of AVSpeechUtterance
43
+ end
44
+
45
+ describe '#rate' do
46
+
47
+ it "sets the speech rate to a reasonable default" do
48
+ @speaker.utterance.rate.should.be.equal 0.15
49
+ end
50
+
51
+ it "allows me to override the rate" do
52
+ speaker = Motion::Speech::Speaker.new "lorem", rate: 0.75
53
+ speaker.utterance.rate.should.be.equal 0.75
54
+ end
55
+ end
56
+ end
57
+
58
+ describe '#synthesizer' do
59
+ before do
60
+ @speaker = Motion::Speech::Speaker.new "lorem"
61
+ end
62
+
63
+ it "returns an AVSpeechSynthesizer" do
64
+ @speaker.synthesizer.should.be.instance_of AVSpeechSynthesizer
65
+ end
66
+
67
+ it "is the synthesizer delegate" do
68
+ @speaker.synthesizer.delegate.should.be.equal @speaker
69
+ end
70
+ end
71
+
72
+ describe 'events' do
73
+
74
+ describe 'block without arguments' do
75
+ before do
76
+ @speaker = Motion::Speech::Speaker.new("lorem") { @called_block = true }
77
+ end
78
+
79
+ it "calls the block on completion" do
80
+ @called_block.should.be.nil
81
+
82
+ # Send delegate message immediately, AVFoundation is tested
83
+ @speaker.send 'speechSynthesizer:didFinishSpeechUtterance:', @speaker.synthesizer, @speaker.utterance
84
+ @called_block.should.be.true
85
+ end
86
+ end
87
+
88
+ describe 'block with too many arguments' do
89
+ it "raises an exception" do
90
+ should.raise(ArgumentError) do
91
+ Motion::Speech::Speaker.new("lorem") do |arg1, arg2|
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ describe 'block with 1 argument' do
98
+
99
+ it "calls the start block" do
100
+ speaker = Motion::Speech::Speaker.new "lorem" do |events|
101
+ events.start { @called_block = true }
102
+ end
103
+
104
+ @called_block = nil
105
+ @called_block.should.be.nil
106
+
107
+ # Send delegate message immediately, AVFoundation is tested
108
+ speaker.send 'speechSynthesizer:didStartSpeechUtterance:', speaker.synthesizer, speaker.utterance
109
+ @called_block.should.be.true
110
+ end
111
+
112
+ it "calls the finish block" do
113
+ speaker = Motion::Speech::Speaker.new "lorem" do |events|
114
+ events.finish { @called_block = true }
115
+ end
116
+
117
+ @called_block = nil
118
+ @called_block.should.be.nil
119
+
120
+ # Send delegate message immediately, AVFoundation is tested
121
+ speaker.send 'speechSynthesizer:didFinishSpeechUtterance:', speaker.synthesizer, speaker.utterance
122
+ @called_block.should.be.true
123
+ end
124
+
125
+ it "calls the paused block" do
126
+ speaker = Motion::Speech::Speaker.new "lorem" do |events|
127
+ events.pause { @called_block = true }
128
+ end
129
+
130
+ @called_block = nil
131
+ @called_block.should.be.nil
132
+
133
+ # Send delegate message immediately, AVFoundation is tested
134
+ speaker.send 'speechSynthesizer:didPauseSpeechUtterance:', speaker.synthesizer, speaker.utterance
135
+ @called_block.should.be.true
136
+ end
137
+
138
+ it "calls the cancelled block" do
139
+ speaker = Motion::Speech::Speaker.new "lorem" do |events|
140
+ events.cancel { @called_block = true }
141
+ end
142
+
143
+ @called_block = nil
144
+ @called_block.should.be.nil
145
+
146
+ # Send delegate message immediately, AVFoundation is tested
147
+ speaker.send 'speechSynthesizer:didCancelSpeechUtterance:', speaker.synthesizer, speaker.utterance
148
+ @called_block.should.be.true
149
+ end
150
+
151
+ it "calls the resumed block" do
152
+ speaker = Motion::Speech::Speaker.new "lorem" do |events|
153
+ events.resume { @called_block = true }
154
+ end
155
+
156
+ @called_block = nil
157
+ @called_block.should.be.nil
158
+
159
+ # Send delegate message immediately, AVFoundation is tested
160
+ speaker.send 'speechSynthesizer:didContinueSpeechUtterance:', speaker.synthesizer, speaker.utterance
161
+ @called_block.should.be.true
162
+ end
163
+ end
164
+
165
+ end
166
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: motion-speech
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Matt Brewer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Provides a simple interface for using the AVSpeechSynthesizer related
28
+ classes available natively in iOS 7.
29
+ email:
30
+ - matt.brewer@me.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - README.md
36
+ - lib/motion-speech.rb
37
+ - lib/motion/speech/event_block.rb
38
+ - lib/motion/speech/speaker.rb
39
+ - lib/motion/speech/version.rb
40
+ - spec/event_block_spec.rb
41
+ - spec/helpers/speakable.rb
42
+ - spec/speaker_spec.rb
43
+ homepage: https://github.com//motion-speech
44
+ licenses:
45
+ - MIT
46
+ metadata: {}
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project: "[none]"
63
+ rubygems_version: 2.2.2
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: Get your iOS app to talk the easy way.
67
+ test_files:
68
+ - spec/event_block_spec.rb
69
+ - spec/helpers/speakable.rb
70
+ - spec/speaker_spec.rb