pocketsphinx-ruby 0.0.3 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +45 -7
- data/lib/pocketsphinx.rb +7 -1
- data/lib/pocketsphinx/api/call_helpers.rb +16 -0
- data/lib/pocketsphinx/api/pocketsphinx.rb +4 -0
- data/lib/pocketsphinx/configuration/default.rb +10 -0
- data/lib/pocketsphinx/configuration/grammar.rb +21 -0
- data/lib/pocketsphinx/decoder.rb +58 -15
- data/lib/pocketsphinx/grammar/jsgf.rb +37 -0
- data/lib/pocketsphinx/grammar/jsgf_builder.rb +27 -0
- data/lib/pocketsphinx/microphone.rb +4 -10
- data/lib/pocketsphinx/version.rb +1 -1
- data/spec/assets/grammars/goforward.gram +15 -0
- data/spec/assets/grammars/invalid.gram +1 -0
- data/spec/assets/grammars/sentences.gram +5 -0
- data/spec/configuration_spec.rb +9 -5
- data/spec/decoder_spec.rb +77 -1
- data/spec/grammar_spec.rb +43 -0
- data/spec/integration/default_recognition_spec.rb +21 -0
- data/spec/integration/{speech_recognizer_spec.rb → grammar_recognition_spec.rb} +4 -3
- metadata +18 -4
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: d3479a20de1bf075e2a4d2efcf8721c05a650751
         | 
| 4 | 
            +
              data.tar.gz: a3ea68aaf9e0dc60149c2992dd8316233d8568f3
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: e732d04275721ed223776c7d90b97894e8a778e73e334a036561715efbf868b65e8b20809730013fcaf020f21ab5de2fa646af6b7bc7aeb2f467345b6492f33e
         | 
| 7 | 
            +
              data.tar.gz: a6670826d281b272ea29d4d609805328d2da8af4753d66349e78f222c5c99eaf05a7dde19faad8ac070b57e03aacab20396c5686c8b7365e4d586591bc958add
         | 
    
        data/README.md
    CHANGED
    
    | @@ -56,7 +56,7 @@ Or install it yourself as: | |
| 56 56 | 
             
            The `LiveSpeechRecognizer` is modeled on the same class in [Sphinx4](http://cmusphinx.sourceforge.net/wiki/tutorialsphinx4). It uses the `Microphone` and `Decoder` classes internally to provide a simple, high-level recognition interface:
         | 
| 57 57 |  | 
| 58 58 | 
             
            ```ruby
         | 
| 59 | 
            -
            require 'pocketsphinx-ruby'
         | 
| 59 | 
            +
            require 'pocketsphinx-ruby' # Omitted in subsequent examples
         | 
| 60 60 |  | 
| 61 61 | 
             
            Pocketsphinx::LiveSpeechRecognizer.new.recognize do |speech|
         | 
| 62 62 | 
             
              puts speech
         | 
| @@ -106,7 +106,7 @@ The `Microphone` class uses Pocketsphinx's libsphinxad to record audio for speec | |
| 106 106 | 
             
            For example, to record and save a 5 second raw audio file:
         | 
| 107 107 |  | 
| 108 108 | 
             
            ```ruby
         | 
| 109 | 
            -
            microphone = Microphone.new
         | 
| 109 | 
            +
            microphone = Pocketsphinx::Microphone.new
         | 
| 110 110 |  | 
| 111 111 | 
             
            File.open("test.raw", "wb") do |file|
         | 
| 112 112 | 
             
              microphone.record do
         | 
| @@ -130,7 +130,7 @@ To open this audio file take a look at [this wiki page](https://github.com/watso | |
| 130 130 | 
             
            The `Decoder` class uses Pocketsphinx's libpocketsphinx to decode audio data into text. For example to decode a single utterance:
         | 
| 131 131 |  | 
| 132 132 | 
             
            ```ruby
         | 
| 133 | 
            -
            decoder = Decoder.new(Configuration.default)
         | 
| 133 | 
            +
            decoder = Pocketsphinx::Decoder.new(Pocketsphinx::Configuration.default)
         | 
| 134 134 | 
             
            decoder.decode 'spec/assets/audio/goforward.raw'
         | 
| 135 135 |  | 
| 136 136 | 
             
            puts decoder.hypothesis # => "go forward ten years"
         | 
| @@ -142,16 +142,54 @@ puts decoder.hypothesis # => "go forward ten years" | |
| 142 142 | 
             
            Keyword spotting is another feature that is not in the current stable (0.8) releases of Pocketsphinx, having been [merged into trunk](https://github.com/cmusphinx/pocketsphinx/commit/f562f9356cc7f1ade4941ebdde0c377642a023e3) early in 2014. In can be useful for detecting an activation keyword in a command and control application, while ignoring all other speech. Set up a recognizer as follows:
         | 
| 143 143 |  | 
| 144 144 | 
             
            ```ruby
         | 
| 145 | 
            -
            configuration = Configuration::KeywordSpotting.new('Okay computer')
         | 
| 146 | 
            -
            recognizer = LiveSpeechRecognizer.new(configuration)
         | 
| 145 | 
            +
            configuration = Pocketsphinx::Configuration::KeywordSpotting.new('Okay computer')
         | 
| 146 | 
            +
            recognizer = Pocketsphinx::LiveSpeechRecognizer.new(configuration)
         | 
| 147 147 | 
             
            ```
         | 
| 148 148 |  | 
| 149 | 
            -
            The `KeywordSpotting` configuration accepts a second argument for adjusting the sensitivity of the keyword detection. Note that this is just a wrapper which sets the `keyphrase` and `kws_threshold` settings on the default configuration | 
| 149 | 
            +
            The `KeywordSpotting` configuration accepts a second argument for adjusting the sensitivity of the keyword detection. Note that this is just a wrapper which sets the `keyphrase` and `kws_threshold` settings on the default configuration:
         | 
| 150 | 
            +
             | 
| 151 | 
            +
            ```ruby
         | 
| 152 | 
            +
            Pocketsphinx::Configuration::KeywordSpotting.new('keyword', 2).changes
         | 
| 153 | 
            +
            # => [
         | 
| 154 | 
            +
            #   { :name => "keyphrase", :type => :string, :default => nil, :required => false, :value => "keyword", :info => "Keyphrase to spot" },
         | 
| 155 | 
            +
            #   { :name => "kws_threshold", :type => :float, :default => 1.0, :required => false, :value => 2.0, :info => "Threshold for p(hyp)/p(alternatives) ratio" },
         | 
| 156 | 
            +
            #   { :name => "lm", :type => :string, :default => "/usr/local/Cellar/cmu-pocketsphinx/HEAD/share/pocketsphinx/model/lm/en_US/hub4.5000.DMP", :required => false, :value => nil, :info => "Word trigram language model input file" }
         | 
| 157 | 
            +
            # ]
         | 
| 158 | 
            +
            ```
         | 
| 159 | 
            +
             | 
| 160 | 
            +
             | 
| 161 | 
            +
            ### Grammars
         | 
| 162 | 
            +
             | 
| 163 | 
            +
            Another way of configuring Pocketsphinx is with a grammar, which is normally used to describe very simple types of languages for command and control. Restricting the set of possible utterances in this way can greatly improve recognition accuracy for these types of application.
         | 
| 164 | 
            +
             | 
| 165 | 
            +
            Load a [JSGF](http://www.w3.org/TR/jsgf/) grammar from a file:
         | 
| 166 | 
            +
             | 
| 167 | 
            +
            ```ruby
         | 
| 168 | 
            +
            configuration = Pocketsphinx::Configuration::Grammar.new('sentences.gram')
         | 
| 169 | 
            +
            ```
         | 
| 170 | 
            +
             | 
| 171 | 
            +
            Or build one dynamically with this simple DSL (currently only supports sentence lists):
         | 
| 172 | 
            +
             | 
| 173 | 
            +
            ```ruby
         | 
| 174 | 
            +
            configuration = Pocketsphinx::Configuration::Grammar.new do
         | 
| 175 | 
            +
              sentence "Go forward ten meters"
         | 
| 176 | 
            +
              sentence "Go backward ten meters"
         | 
| 177 | 
            +
            end
         | 
| 178 | 
            +
            ```
         | 
| 179 | 
            +
             | 
| 180 | 
            +
             | 
| 181 | 
            +
            ## Troubleshooting
         | 
| 182 | 
            +
             | 
| 183 | 
            +
            This gem has been tested with a manual Pocketsphinx installation on Ubuntu 14.04 and a Homebrew Pocketsphinx installation on OSX 10.9.4 Mavericks. Take a look at the following common problems before opening an issue.
         | 
| 184 | 
            +
             | 
| 185 | 
            +
            **`attach_function': Function 'ps_default_search_args' not found in [libpocketsphinx.so] (FFI::NotFoundError)**
         | 
| 186 | 
            +
             | 
| 187 | 
            +
            An error like this probably means that you have an old version of the Pocketsphinx libraries installed. If necessary, replace them with a recent development version which supports the features available in this gem.
         | 
| 150 188 |  | 
| 151 189 |  | 
| 152 190 | 
             
            ## Contributing
         | 
| 153 191 |  | 
| 154 | 
            -
            1. Fork it ( https://github.com/ | 
| 192 | 
            +
            1. Fork it ( https://github.com/watsonbox/pocketsphinx-ruby/fork )
         | 
| 155 193 | 
             
            2. Create your feature branch (`git checkout -b my-new-feature`)
         | 
| 156 194 | 
             
            3. Commit your changes (`git commit -am 'Add some feature'`)
         | 
| 157 195 | 
             
            4. Push to the branch (`git push origin my-new-feature`)
         | 
    
        data/lib/pocketsphinx.rb
    CHANGED
    
    | @@ -6,12 +6,18 @@ require "pocketsphinx/version" | |
| 6 6 | 
             
            require "pocketsphinx/api/sphinxbase"
         | 
| 7 7 | 
             
            require "pocketsphinx/api/sphinxad"
         | 
| 8 8 | 
             
            require "pocketsphinx/api/pocketsphinx"
         | 
| 9 | 
            +
            require "pocketsphinx/api/call_helpers"
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            # Grammar
         | 
| 12 | 
            +
            require "pocketsphinx/grammar/jsgf"
         | 
| 13 | 
            +
            require "pocketsphinx/grammar/jsgf_builder"
         | 
| 9 14 |  | 
| 10 15 | 
             
            # Configuration
         | 
| 11 | 
            -
            require  | 
| 16 | 
            +
            require "pocketsphinx/configuration/setting_definition"
         | 
| 12 17 | 
             
            require "pocketsphinx/configuration/base"
         | 
| 13 18 | 
             
            require "pocketsphinx/configuration/default"
         | 
| 14 19 | 
             
            require "pocketsphinx/configuration/keyword_spotting"
         | 
| 20 | 
            +
            require "pocketsphinx/configuration/grammar"
         | 
| 15 21 |  | 
| 16 22 | 
             
            require "pocketsphinx/audio_file"
         | 
| 17 23 | 
             
            require "pocketsphinx/microphone"
         | 
| @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            module Pocketsphinx
         | 
| 2 | 
            +
              module API
         | 
| 3 | 
            +
                Error = Class.new(StandardError)
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                module CallHelpers
         | 
| 6 | 
            +
                  def api_call(method, *args)
         | 
| 7 | 
            +
                    calling_method = caller[0][/`.*'/][1..-2]
         | 
| 8 | 
            +
                    ps_api.send(method, *args).tap do |result|
         | 
| 9 | 
            +
                      if result < 0
         | 
| 10 | 
            +
                        raise Error, "#{self.class.to_s.split('::').last}##{calling_method} failed with error code #{result}"
         | 
| 11 | 
            +
                      end
         | 
| 12 | 
            +
                    end
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
            end
         | 
| @@ -17,6 +17,10 @@ module Pocketsphinx | |
| 17 17 | 
             
                  attach_function :ps_end_utt, [:decoder], :int
         | 
| 18 18 | 
             
                  attach_function :ps_get_in_speech, [:decoder], :uint8
         | 
| 19 19 | 
             
                  attach_function :ps_get_hyp, [:decoder, :pointer, :pointer], :string
         | 
| 20 | 
            +
                  attach_function :ps_set_jsgf_string, [:decoder, :string, :string], :int
         | 
| 21 | 
            +
                  attach_function :ps_unset_search, [:decoder, :string], :int
         | 
| 22 | 
            +
                  attach_function :ps_get_search, [:decoder], :string
         | 
| 23 | 
            +
                  attach_function :ps_set_search, [:decoder, :string], :int
         | 
| 20 24 | 
             
                end
         | 
| 21 25 | 
             
              end
         | 
| 22 26 | 
             
            end
         | 
| @@ -7,6 +7,16 @@ module Pocketsphinx | |
| 7 7 | 
             
                    # Sets default grammar and language model if they are not set explicitly and
         | 
| 8 8 | 
             
                    # are present in the default search path.
         | 
| 9 9 | 
             
                    API::Pocketsphinx.ps_default_search_args(@ps_config)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    # Treat ps_default_search_args settings as defaults
         | 
| 12 | 
            +
                    changes.each do |details|
         | 
| 13 | 
            +
                      setting_definitions[details[:name]].deflt = details[:value]
         | 
| 14 | 
            +
                    end
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  # Show details for settings which don't match Pocketsphinx defaults
         | 
| 18 | 
            +
                  def changes
         | 
| 19 | 
            +
                    details.reject { |d| d[:default] == d[:value] }
         | 
| 10 20 | 
             
                  end
         | 
| 11 21 | 
             
                end
         | 
| 12 22 |  | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            module Pocketsphinx
         | 
| 2 | 
            +
              module Configuration
         | 
| 3 | 
            +
                class Grammar < Default
         | 
| 4 | 
            +
                  attr_accessor :grammar
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  def initialize(*args, &block)#(grammar_path = nil)
         | 
| 7 | 
            +
                    super()
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                    @grammar = ::Grammar::Jsgf.new(*args, &block)
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  # Since JSGF strings are not supported in Pocketsphinx configuration (only files),
         | 
| 13 | 
            +
                  # we use the post_init_decoder hook to configure the JSGF
         | 
| 14 | 
            +
                  def post_init_decoder(decoder)
         | 
| 15 | 
            +
                    decoder.unset_search
         | 
| 16 | 
            +
                    decoder.set_jsgf_string(grammar.raw)
         | 
| 17 | 
            +
                    decoder.set_search
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
    
        data/lib/pocketsphinx/decoder.rb
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            module Pocketsphinx
         | 
| 2 2 | 
             
              class Decoder < Struct.new(:configuration)
         | 
| 3 | 
            -
                 | 
| 3 | 
            +
                include API::CallHelpers
         | 
| 4 4 |  | 
| 5 5 | 
             
                attr_writer :ps_api
         | 
| 6 6 |  | 
| @@ -13,10 +13,7 @@ module Pocketsphinx | |
| 13 13 | 
             
                #   nil, the previous configuration will be reloaded, with any changes applied.
         | 
| 14 14 | 
             
                def reconfigure(configuration = nil)
         | 
| 15 15 | 
             
                  self.configuration = configuration if configuration
         | 
| 16 | 
            -
             | 
| 17 | 
            -
                  ps_api.ps_reinit(ps_decoder, self.configuration.ps_config).tap do |result|
         | 
| 18 | 
            -
                    raise Error, "Decoder#reconfigure failed with error code #{result}" if result < 0
         | 
| 19 | 
            -
                  end
         | 
| 16 | 
            +
                  reinit_decoder
         | 
| 20 17 | 
             
                end
         | 
| 21 18 |  | 
| 22 19 | 
             
                # Decode a raw audio stream as a single utterance, opening a file if path given
         | 
| @@ -64,9 +61,7 @@ module Pocketsphinx | |
| 64 61 | 
             
                #   worth of data.  This may allow the recognizer to produce more accurate results.
         | 
| 65 62 | 
             
                # @return Number of frames of data searched
         | 
| 66 63 | 
             
                def process_raw(buffer, size, no_search = false, full_utt = false)
         | 
| 67 | 
            -
                   | 
| 68 | 
            -
                    raise Error, "Decoder#process_raw failed with error code #{result}" if result < 0
         | 
| 69 | 
            -
                  end
         | 
| 64 | 
            +
                  api_call :ps_process_raw, ps_decoder, buffer, size, no_search ? 1 : 0, full_utt ? 1 : 0
         | 
| 70 65 | 
             
                end
         | 
| 71 66 |  | 
| 72 67 | 
             
                # Start utterance processing.
         | 
| @@ -77,16 +72,12 @@ module Pocketsphinx | |
| 77 72 | 
             
                #
         | 
| 78 73 | 
             
                # @param [String] name String uniquely identifying this utterance. If nil, one will be created.
         | 
| 79 74 | 
             
                def start_utterance(name = nil)
         | 
| 80 | 
            -
                   | 
| 81 | 
            -
                    raise Error, "Decoder#start_utterance failed with error code #{result}" if result < 0
         | 
| 82 | 
            -
                  end
         | 
| 75 | 
            +
                  api_call :ps_start_utt, ps_decoder, name
         | 
| 83 76 | 
             
                end
         | 
| 84 77 |  | 
| 85 78 | 
             
                # End utterance processing
         | 
| 86 79 | 
             
                def end_utterance
         | 
| 87 | 
            -
                   | 
| 88 | 
            -
                    raise Error, "Decoder#end_utterance failed with error code #{result}" if result < 0
         | 
| 89 | 
            -
                  end
         | 
| 80 | 
            +
                  api_call :ps_end_utt, ps_decoder
         | 
| 90 81 | 
             
                end
         | 
| 91 82 |  | 
| 92 83 | 
             
                # Checks if the last feed audio buffer contained speech
         | 
| @@ -102,12 +93,64 @@ module Pocketsphinx | |
| 102 93 | 
             
                  ps_api.ps_get_hyp(ps_decoder, nil, nil)
         | 
| 103 94 | 
             
                end
         | 
| 104 95 |  | 
| 96 | 
            +
                # Adds new search using JSGF model.
         | 
| 97 | 
            +
                #
         | 
| 98 | 
            +
                # Convenience method to parse JSGF model from string and create a search.
         | 
| 99 | 
            +
                #
         | 
| 100 | 
            +
                # @param [String] jsgf_string The JSGF grammar
         | 
| 101 | 
            +
                # @param [String] name The search name
         | 
| 102 | 
            +
                def set_jsgf_string(jsgf_string, name = 'default')
         | 
| 103 | 
            +
                  api_call :ps_set_jsgf_string, ps_decoder, name, jsgf_string
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                # Returns name of curent search in decoder
         | 
| 107 | 
            +
                def get_search
         | 
| 108 | 
            +
                  ps_api.ps_get_search(ps_decoder)
         | 
| 109 | 
            +
                end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                # Actives search with the provided name.
         | 
| 112 | 
            +
                #
         | 
| 113 | 
            +
                # Activates search with the provided name. The search must be added before
         | 
| 114 | 
            +
                # using either ps_set_fsg(), ps_set_lm() or ps_set_kws().
         | 
| 115 | 
            +
                def set_search(name = 'default')
         | 
| 116 | 
            +
                  api_call :ps_set_search, ps_decoder, name
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                # Unsets the search and releases related resources.
         | 
| 120 | 
            +
                #
         | 
| 121 | 
            +
                # Unsets the search previously added with
         | 
| 122 | 
            +
                # using either ps_set_fsg(), ps_set_lm() or ps_set_kws().
         | 
| 123 | 
            +
                def unset_search(name = 'default')
         | 
| 124 | 
            +
                  api_call :ps_unset_search, ps_decoder, name
         | 
| 125 | 
            +
                end
         | 
| 126 | 
            +
             | 
| 105 127 | 
             
                def ps_api
         | 
| 106 128 | 
             
                  @ps_api || API::Pocketsphinx
         | 
| 107 129 | 
             
                end
         | 
| 108 130 |  | 
| 109 131 | 
             
                def ps_decoder
         | 
| 110 | 
            -
                  @ps_decoder | 
| 132 | 
            +
                  init_decoder if @ps_decoder.nil?
         | 
| 133 | 
            +
                  @ps_decoder
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                private
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                def init_decoder
         | 
| 139 | 
            +
                  @ps_decoder = ps_api.ps_init(configuration.ps_config)
         | 
| 140 | 
            +
                  post_init_decoder
         | 
| 141 | 
            +
                end
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                def reinit_decoder
         | 
| 144 | 
            +
                  ps_api.ps_reinit(ps_decoder, configuration.ps_config).tap do |result|
         | 
| 145 | 
            +
                    raise API::Error, "Decoder#reconfigure failed with error code #{result}" if result < 0
         | 
| 146 | 
            +
                    post_init_decoder
         | 
| 147 | 
            +
                  end
         | 
| 148 | 
            +
                end
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                def post_init_decoder
         | 
| 151 | 
            +
                  if configuration.respond_to?(:post_init_decoder)
         | 
| 152 | 
            +
                    configuration.post_init_decoder(self)
         | 
| 153 | 
            +
                  end
         | 
| 111 154 | 
             
                end
         | 
| 112 155 | 
             
              end
         | 
| 113 156 | 
             
            end
         | 
| @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            module Pocketsphinx
         | 
| 2 | 
            +
              module Grammar
         | 
| 3 | 
            +
                class Jsgf
         | 
| 4 | 
            +
                  attr_reader :raw
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  def initialize(path = nil, &block)
         | 
| 7 | 
            +
                    if path.nil? && !block_given?
         | 
| 8 | 
            +
                      raise "Either a path or block is required to create a JSGF grammar"
         | 
| 9 | 
            +
                    end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    if block_given?
         | 
| 12 | 
            +
                      @raw = grammar_from_block(&block)
         | 
| 13 | 
            +
                    else
         | 
| 14 | 
            +
                      @raw = grammar_from_file(path)
         | 
| 15 | 
            +
                      check_grammar
         | 
| 16 | 
            +
                    end
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def grammar_from_file(path)
         | 
| 20 | 
            +
                    File.read path
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def grammar_from_block(&block)
         | 
| 24 | 
            +
                    builder = JsgfBuilder.new
         | 
| 25 | 
            +
                    builder.instance_eval(&block)
         | 
| 26 | 
            +
                    builder.jsgf
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  private
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def check_grammar
         | 
| 32 | 
            +
                    # Simple header check for now
         | 
| 33 | 
            +
                    raise 'Invalid JSGF grammar' unless raw.lines.first.strip == "#JSGF V1.0;"
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
            end
         | 
| @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            module Pocketsphinx
         | 
| 2 | 
            +
              module Grammar
         | 
| 3 | 
            +
                class JsgfBuilder
         | 
| 4 | 
            +
                  def initialize
         | 
| 5 | 
            +
                    @sentences = []
         | 
| 6 | 
            +
                  end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def sentence(sentence)
         | 
| 9 | 
            +
                    @sentences << sentence
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def jsgf
         | 
| 13 | 
            +
                    header + sentences_rule
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  private
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def header
         | 
| 19 | 
            +
                    "#JSGF V1.0;\n\ngrammar default;\n\n"
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def sentences_rule
         | 
| 23 | 
            +
                    "public <sentence> = #{@sentences.map(&:downcase).join(' | ')};"
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
            end
         | 
| @@ -3,7 +3,7 @@ module Pocketsphinx | |
| 3 3 | 
             
              #
         | 
| 4 4 | 
             
              # Implements Recordable interface (#record and #read_audio)
         | 
| 5 5 | 
             
              class Microphone
         | 
| 6 | 
            -
                 | 
| 6 | 
            +
                include API::CallHelpers
         | 
| 7 7 |  | 
| 8 8 | 
             
                attr_reader :ps_audio_device
         | 
| 9 9 | 
             
                attr_writer :ps_api
         | 
| @@ -36,15 +36,11 @@ module Pocketsphinx | |
| 36 36 | 
             
                end
         | 
| 37 37 |  | 
| 38 38 | 
             
                def start_recording
         | 
| 39 | 
            -
                   | 
| 40 | 
            -
                    raise Error, "Microphone#start_recording failed with error code #{result}" if result < 0
         | 
| 41 | 
            -
                  end
         | 
| 39 | 
            +
                  api_call :ad_start_rec, @ps_audio_device
         | 
| 42 40 | 
             
                end
         | 
| 43 41 |  | 
| 44 42 | 
             
                def stop_recording
         | 
| 45 | 
            -
                   | 
| 46 | 
            -
                    raise Error, "Microphone#stop_recording failed with error code #{result}" if result < 0
         | 
| 47 | 
            -
                  end
         | 
| 43 | 
            +
                  api_call :ad_stop_rec, @ps_audio_device
         | 
| 48 44 | 
             
                end
         | 
| 49 45 |  | 
| 50 46 | 
             
                # Read next block of audio samples while recording; read upto max samples into buf.
         | 
| @@ -69,9 +65,7 @@ module Pocketsphinx | |
| 69 65 | 
             
                end
         | 
| 70 66 |  | 
| 71 67 | 
             
                def close_device
         | 
| 72 | 
            -
                   | 
| 73 | 
            -
                    raise Error, "Microphone#close_device failed with error code #{result}" if result < 0
         | 
| 74 | 
            -
                  end
         | 
| 68 | 
            +
                  api_call :ad_close, @ps_audio_device
         | 
| 75 69 | 
             
                end
         | 
| 76 70 |  | 
| 77 71 | 
             
                def ps_api
         | 
    
        data/lib/pocketsphinx/version.rb
    CHANGED
    
    
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            #JSGF V1.0;
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            /**
         | 
| 4 | 
            +
             * JSGF Grammar for Turtle example
         | 
| 5 | 
            +
             */
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            grammar goforward;
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            public <move> = go forward ten meters;
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            public <move2> = go <direction> <distance> [meter | meters];
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            <direction> = forward | backward;
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            <distance> = one | two | three | four | five | six | seven | eight | nine | ten;
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            This isn't a valid grammar
         | 
    
        data/spec/configuration_spec.rb
    CHANGED
    
    | @@ -90,12 +90,16 @@ describe Configuration do | |
| 90 90 | 
             
              context 'keyword spotting configuration' do
         | 
| 91 91 | 
             
                subject { Configuration::KeywordSpotting.new('Okay computer') }
         | 
| 92 92 |  | 
| 93 | 
            -
                it ' | 
| 94 | 
            -
                   | 
| 95 | 
            -
             | 
| 93 | 
            +
                it 'modifies the default configuration keyphrase and language model' do
         | 
| 94 | 
            +
                  changes = subject.changes
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                  expect(changes.count).to be(2)
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                  expect(changes[0][:name]).to eq('keyphrase')
         | 
| 99 | 
            +
                  expect(changes[0][:value]).to eq('okay computer')
         | 
| 96 100 |  | 
| 97 | 
            -
             | 
| 98 | 
            -
                  expect( | 
| 101 | 
            +
                  expect(changes[1][:name]).to eq('lm')
         | 
| 102 | 
            +
                  expect(changes[1][:value]).to be_nil
         | 
| 99 103 | 
             
                end
         | 
| 100 104 |  | 
| 101 105 | 
             
                it 'exposes the keyphrase setting as #keyword' do
         | 
    
        data/spec/decoder_spec.rb
    CHANGED
    
    | @@ -12,12 +12,18 @@ describe Decoder do | |
| 12 12 | 
             
              end
         | 
| 13 13 |  | 
| 14 14 | 
             
              describe '#reconfigure' do
         | 
| 15 | 
            -
                it 'calls libpocketsphinx' do
         | 
| 15 | 
            +
                it 'calls libpocketsphinx and the configuration post initialize hook' do
         | 
| 16 16 | 
             
                  expect(ps_api)
         | 
| 17 17 | 
             
                    .to receive(:ps_reinit)
         | 
| 18 18 | 
             
                    .with(subject.ps_decoder, configuration.ps_config)
         | 
| 19 19 | 
             
                    .and_return(0)
         | 
| 20 20 |  | 
| 21 | 
            +
                  configuration.define_singleton_method(:post_init_decoder) { |decoder| }
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  expect(configuration)
         | 
| 24 | 
            +
                    .to receive(:post_init_decoder)
         | 
| 25 | 
            +
                    .with(subject)
         | 
| 26 | 
            +
             | 
| 21 27 | 
             
                  subject.reconfigure
         | 
| 22 28 | 
             
                end
         | 
| 23 29 |  | 
| @@ -133,4 +139,74 @@ describe Decoder do | |
| 133 139 | 
             
                  expect(subject.hypothesis).to eq("Hypothesis")
         | 
| 134 140 | 
             
                end
         | 
| 135 141 | 
             
              end
         | 
| 142 | 
            +
             | 
| 143 | 
            +
              describe '#set_jsgf_string' do
         | 
| 144 | 
            +
                it 'calls libpocketsphinx' do
         | 
| 145 | 
            +
                  expect(ps_api)
         | 
| 146 | 
            +
                    .to receive(:ps_set_jsgf_string)
         | 
| 147 | 
            +
                    .with(subject.ps_decoder, 'default', 'JSGF')
         | 
| 148 | 
            +
                    .and_return(0)
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                  subject.set_jsgf_string('JSGF')
         | 
| 151 | 
            +
                end
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                it 'raises an exception on error' do
         | 
| 154 | 
            +
                  expect(ps_api)
         | 
| 155 | 
            +
                    .to receive(:ps_set_jsgf_string)
         | 
| 156 | 
            +
                    .and_return(-1)
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                  expect { subject.set_jsgf_string('JSGF') }
         | 
| 159 | 
            +
                    .to raise_exception "Decoder#set_jsgf_string failed with error code -1"
         | 
| 160 | 
            +
                end
         | 
| 161 | 
            +
              end
         | 
| 162 | 
            +
             | 
| 163 | 
            +
              describe '#set_search' do
         | 
| 164 | 
            +
                it 'calls libpocketsphinx' do
         | 
| 165 | 
            +
                  expect(ps_api)
         | 
| 166 | 
            +
                    .to receive(:ps_set_search)
         | 
| 167 | 
            +
                    .with(subject.ps_decoder, 'search')
         | 
| 168 | 
            +
                    .and_return(0)
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                  subject.set_search('search')
         | 
| 171 | 
            +
                end
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                it 'raises an exception on error' do
         | 
| 174 | 
            +
                  expect(ps_api)
         | 
| 175 | 
            +
                    .to receive(:ps_set_search)
         | 
| 176 | 
            +
                    .and_return(-1)
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                  expect { subject.set_search('search') }
         | 
| 179 | 
            +
                    .to raise_exception "Decoder#set_search failed with error code -1"
         | 
| 180 | 
            +
                end
         | 
| 181 | 
            +
              end
         | 
| 182 | 
            +
             | 
| 183 | 
            +
              describe '#unset_search' do
         | 
| 184 | 
            +
                it 'calls libpocketsphinx' do
         | 
| 185 | 
            +
                  expect(ps_api)
         | 
| 186 | 
            +
                    .to receive(:ps_unset_search)
         | 
| 187 | 
            +
                    .with(subject.ps_decoder, 'search')
         | 
| 188 | 
            +
                    .and_return(0)
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                  subject.unset_search('search')
         | 
| 191 | 
            +
                end
         | 
| 192 | 
            +
             | 
| 193 | 
            +
                it 'raises an exception on error' do
         | 
| 194 | 
            +
                  expect(ps_api)
         | 
| 195 | 
            +
                    .to receive(:ps_unset_search)
         | 
| 196 | 
            +
                    .and_return(-1)
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                  expect { subject.unset_search('search') }
         | 
| 199 | 
            +
                    .to raise_exception "Decoder#unset_search failed with error code -1"
         | 
| 200 | 
            +
                end
         | 
| 201 | 
            +
              end
         | 
| 202 | 
            +
             | 
| 203 | 
            +
              describe '#get_search' do
         | 
| 204 | 
            +
                it 'calls libpocketsphinx' do
         | 
| 205 | 
            +
                  expect(ps_api)
         | 
| 206 | 
            +
                    .to receive(:ps_get_search)
         | 
| 207 | 
            +
                    .and_return(:search)
         | 
| 208 | 
            +
             | 
| 209 | 
            +
                  expect(subject.get_search).to eq(:search)
         | 
| 210 | 
            +
                end
         | 
| 211 | 
            +
              end
         | 
| 136 212 | 
             
            end
         | 
| @@ -0,0 +1,43 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Grammar::Jsgf do
         | 
| 4 | 
            +
              it "raises an exception when neither a file or block are given" do
         | 
| 5 | 
            +
                expect { Grammar::Jsgf.new }.to raise_exception "Either a path or block is required to create a JSGF grammar"
         | 
| 6 | 
            +
              end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              context "reading a grammar from a file" do
         | 
| 9 | 
            +
                let(:grammar_path) { grammar :goforward }
         | 
| 10 | 
            +
                subject { Grammar::Jsgf.new(grammar_path) }
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                it "reads a grammar from a file" do
         | 
| 13 | 
            +
                  expect(subject.raw.lines.count).to eq(15)
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                context "the grammar file is invalid" do
         | 
| 17 | 
            +
                  let(:grammar_path) { grammar :invalid }
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  it "raises an exception" do
         | 
| 20 | 
            +
                    expect { subject }.to raise_exception "Invalid JSGF grammar"
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              context "building a grammer from a block" do
         | 
| 26 | 
            +
                subject do
         | 
| 27 | 
            +
                  Grammar::Jsgf.new do
         | 
| 28 | 
            +
                    sentence "Go forward ten meters"
         | 
| 29 | 
            +
                    sentence "Go backward ten meters"
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                it "builds a grammar from a block" do
         | 
| 34 | 
            +
                  expect(subject.raw).to eq(File.read grammar(:sentences))
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              private
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              def grammar(name)
         | 
| 41 | 
            +
                "spec/assets/grammars/#{name}.gram"
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
            end
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe 'speech recognition with default configuration' do
         | 
| 4 | 
            +
              subject do
         | 
| 5 | 
            +
                AudioFileSpeechRecognizer.new.tap do |speech_recognizer|
         | 
| 6 | 
            +
                  speech_recognizer.decoder = @decoder
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              # Share decoder across all examples for speed
         | 
| 11 | 
            +
              before :all do
         | 
| 12 | 
            +
                @decoder = Decoder.new(Configuration.default)
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              describe '#recognize' do
         | 
| 16 | 
            +
                it 'should decode speech in raw audio' do
         | 
| 17 | 
            +
                  expect { |b| subject.recognize('spec/assets/audio/goforward.raw', 4096, &b) }.
         | 
| 18 | 
            +
                    to yield_with_args("go forward ten years")
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            require 'spec_helper'
         | 
| 2 2 |  | 
| 3 | 
            -
            describe  | 
| 3 | 
            +
            describe 'speech recognition with a grammar' do
         | 
| 4 4 | 
             
              let(:recordable) { AudioFile.new('spec/assets/audio/goforward.raw') }
         | 
| 5 5 |  | 
| 6 6 | 
             
              subject do
         | 
| @@ -12,12 +12,13 @@ describe SpeechRecognizer do | |
| 12 12 |  | 
| 13 13 | 
             
              # Share decoder across all examples for speed
         | 
| 14 14 | 
             
              before :all do
         | 
| 15 | 
            -
                @ | 
| 15 | 
            +
                @configuration = Configuration::Grammar.new('spec/assets/grammars/goforward.gram')
         | 
| 16 | 
            +
                @decoder = Decoder.new(@configuration)
         | 
| 16 17 | 
             
              end
         | 
| 17 18 |  | 
| 18 19 | 
             
              describe '#recognize' do
         | 
| 19 20 | 
             
                it 'should decode speech in raw audio' do
         | 
| 20 | 
            -
                  expect { |b| subject.recognize(4096, &b) }.to yield_with_args("go forward ten  | 
| 21 | 
            +
                  expect { |b| subject.recognize(4096, &b) }.to yield_with_args("go forward ten meters")
         | 
| 21 22 | 
             
                end
         | 
| 22 23 | 
             
              end
         | 
| 23 24 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: pocketsphinx-ruby
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.0 | 
| 4 | 
            +
              version: 0.1.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Howard Wilson
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2014-10- | 
| 11 | 
            +
            date: 2014-10-24 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: ffi
         | 
| @@ -100,6 +100,7 @@ files: | |
| 100 100 | 
             
            - examples/record_audio_file.rb
         | 
| 101 101 | 
             
            - lib/pocketsphinx-ruby.rb
         | 
| 102 102 | 
             
            - lib/pocketsphinx.rb
         | 
| 103 | 
            +
            - lib/pocketsphinx/api/call_helpers.rb
         | 
| 103 104 | 
             
            - lib/pocketsphinx/api/pocketsphinx.rb
         | 
| 104 105 | 
             
            - lib/pocketsphinx/api/sphinxad.rb
         | 
| 105 106 | 
             
            - lib/pocketsphinx/api/sphinxbase.rb
         | 
| @@ -107,19 +108,27 @@ files: | |
| 107 108 | 
             
            - lib/pocketsphinx/audio_file_speech_recognizer.rb
         | 
| 108 109 | 
             
            - lib/pocketsphinx/configuration/base.rb
         | 
| 109 110 | 
             
            - lib/pocketsphinx/configuration/default.rb
         | 
| 111 | 
            +
            - lib/pocketsphinx/configuration/grammar.rb
         | 
| 110 112 | 
             
            - lib/pocketsphinx/configuration/keyword_spotting.rb
         | 
| 111 113 | 
             
            - lib/pocketsphinx/configuration/setting_definition.rb
         | 
| 112 114 | 
             
            - lib/pocketsphinx/decoder.rb
         | 
| 115 | 
            +
            - lib/pocketsphinx/grammar/jsgf.rb
         | 
| 116 | 
            +
            - lib/pocketsphinx/grammar/jsgf_builder.rb
         | 
| 113 117 | 
             
            - lib/pocketsphinx/live_speech_recognizer.rb
         | 
| 114 118 | 
             
            - lib/pocketsphinx/microphone.rb
         | 
| 115 119 | 
             
            - lib/pocketsphinx/speech_recognizer.rb
         | 
| 116 120 | 
             
            - lib/pocketsphinx/version.rb
         | 
| 117 121 | 
             
            - pocketsphinx-ruby.gemspec
         | 
| 118 122 | 
             
            - spec/assets/audio/goforward.raw
         | 
| 123 | 
            +
            - spec/assets/grammars/goforward.gram
         | 
| 124 | 
            +
            - spec/assets/grammars/invalid.gram
         | 
| 125 | 
            +
            - spec/assets/grammars/sentences.gram
         | 
| 119 126 | 
             
            - spec/configuration_spec.rb
         | 
| 120 127 | 
             
            - spec/decoder_spec.rb
         | 
| 128 | 
            +
            - spec/grammar_spec.rb
         | 
| 121 129 | 
             
            - spec/integration/decoder_spec.rb
         | 
| 122 | 
            -
            - spec/integration/ | 
| 130 | 
            +
            - spec/integration/default_recognition_spec.rb
         | 
| 131 | 
            +
            - spec/integration/grammar_recognition_spec.rb
         | 
| 123 132 | 
             
            - spec/microphone_spec.rb
         | 
| 124 133 | 
             
            - spec/spec_helper.rb
         | 
| 125 134 | 
             
            - spec/speech_recognizer_spec.rb
         | 
| @@ -149,10 +158,15 @@ specification_version: 4 | |
| 149 158 | 
             
            summary: Ruby FFI pocketsphinx bindings
         | 
| 150 159 | 
             
            test_files:
         | 
| 151 160 | 
             
            - spec/assets/audio/goforward.raw
         | 
| 161 | 
            +
            - spec/assets/grammars/goforward.gram
         | 
| 162 | 
            +
            - spec/assets/grammars/invalid.gram
         | 
| 163 | 
            +
            - spec/assets/grammars/sentences.gram
         | 
| 152 164 | 
             
            - spec/configuration_spec.rb
         | 
| 153 165 | 
             
            - spec/decoder_spec.rb
         | 
| 166 | 
            +
            - spec/grammar_spec.rb
         | 
| 154 167 | 
             
            - spec/integration/decoder_spec.rb
         | 
| 155 | 
            -
            - spec/integration/ | 
| 168 | 
            +
            - spec/integration/default_recognition_spec.rb
         | 
| 169 | 
            +
            - spec/integration/grammar_recognition_spec.rb
         | 
| 156 170 | 
             
            - spec/microphone_spec.rb
         | 
| 157 171 | 
             
            - spec/spec_helper.rb
         | 
| 158 172 | 
             
            - spec/speech_recognizer_spec.rb
         |