pocketsphinx-ruby 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: b67faaa7d60b0ff377d160f8fd88a28ebcc1eaa4
4
+ data.tar.gz: 163809adeed0af96f876f10bb2b2e93b4ebc8ba6
5
+ SHA512:
6
+ metadata.gz: f7e109bae75aadd7a1ea053fb21cfd099e95c8e7476c08ea0d45d01991d84a93713e346e63d415e14981353f557d6c78fd11177cac726f1145ecbd1b80567f9f
7
+ data.tar.gz: a5b0af62adb929f617239f651d6929eb88b55861e5318970f14e46eaf81b49433fb1492bcfe2d176ea53f982843c3cd674d5cd7e63155ebe788618c0cbaa397b
data/.gitignore ADDED
@@ -0,0 +1,24 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ .DS_Store
24
+ log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,25 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.2
4
+ - 2.0.0
5
+ - 1.9.3
6
+ - rbx-2.2.9
7
+ - jruby-1.7.16
8
+ before_install:
9
+ - sudo apt-get update -qq
10
+ - sudo apt-get install -y swig
11
+ - git clone https://github.com/cmusphinx/sphinxbase.git
12
+ - cd sphinxbase
13
+ - ./autogen.sh
14
+ - ./configure
15
+ - make
16
+ - sudo make install
17
+ - cd ..
18
+ - git clone https://github.com/cmusphinx/pocketsphinx.git
19
+ - cd pocketsphinx
20
+ - ./autogen.sh
21
+ - ./configure
22
+ - make
23
+ - sudo make install
24
+ - cd ..
25
+ - sudo ldconfig
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pocketsphinx-ruby.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Howard Wilson
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # pocketsphinx-ruby
2
+
3
+ [![Build Status](http://img.shields.io/travis/watsonbox/pocketsphinx-ruby.svg?style=flat)](https://travis-ci.org/watsonbox/pocketsphinx-ruby)
4
+ [![Code Climate](http://img.shields.io/codeclimate/github/watsonbox/pocketsphinx-ruby/badges/gpa.svg?style=flat)](https://codeclimate.com/github/watsonbox/pocketsphinx-ruby)
5
+ [![Coverage Status](https://img.shields.io/coveralls/watsonbox/pocketsphinx-ruby.svg?style=flat)](https://coveralls.io/r/watsonbox/pocketsphinx-ruby)
6
+
7
+ This gem provides Ruby [FFI](https://github.com/ffi/ffi) bindings for [Pocketsphinx](https://github.com/cmusphinx/pocketsphinx), a lightweight speech recognition engine, specifically tuned for handheld and mobile devices, though it works equally well on the desktop. Pocketsphinx is part of the [CMU Sphinx](http://cmusphinx.sourceforge.net/) Open Source Toolkit For Speech Recognition.
8
+
9
+ I had initially looked at using Pocketsphinx's [SWIG](http://www.swig.org/) interface for this gem, but decided in favor of FFI for many of the reasons outlined [here](https://github.com/ffi/ffi/wiki/Why-use-FFI), but most importantly ease of maintenance and JRuby support.
10
+
11
+ The goal of this project is to make it as easy as possible for the Ruby community to experiment with speech recognition. Please do contribute fixes and enhancements.
12
+
13
+
14
+ ## Installation
15
+
16
+ This gem depends on [Pocketsphinx](https://github.com/cmusphinx/pocketsphinx) (libpocketsphinx), and [Sphinxbase](https://github.com/cmusphinx/sphinxbase) (libsphinxbase and libsphinxad). The current stable versions (0.8) are from late 2012 and are now outdated. Build them manually from source, or on OSX the latest development (potentially unstable) versions can be installed using [Homebrew](http://brew.sh/) as follows ([more information here](https://github.com/watsonbox/homebrew-cmu-sphinx)).
17
+
18
+ Add the Homebrew tap:
19
+
20
+ ```bash
21
+ $ brew tap watsonbox/cmu-sphinx
22
+ ```
23
+
24
+ You'll see some warnings as these formulae conflict with those in the main reponitory, but that's fine.
25
+
26
+ Install the libraries:
27
+
28
+ ```bash
29
+ $ brew install --HEAD watsonbox/cmu-sphinx/cmu-sphinxbase
30
+ $ brew install --HEAD watsonbox/cmu-sphinx/cmu-sphinxtrain # optional
31
+ $ brew install --HEAD watsonbox/cmu-sphinx/cmu-pocketsphinx
32
+ ```
33
+
34
+ You can test continuous recognition as follows:
35
+
36
+ ```bash
37
+ $ pocketsphinx_continuous -inmic yes
38
+ ```
39
+
40
+ Then add this line to your application's Gemfile:
41
+
42
+ gem 'pocketsphinx-ruby'
43
+
44
+ And then execute:
45
+
46
+ $ bundle
47
+
48
+ Or install it yourself as:
49
+
50
+ $ gem install pocketsphinx-ruby
51
+
52
+
53
+ ## Basic Usage
54
+
55
+ 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:
56
+
57
+ ```ruby
58
+ require 'pocketsphinx-ruby'
59
+
60
+ Pocketsphinx::LiveSpeechRecognizer.new.recognize do |speech|
61
+ puts speech
62
+ end
63
+ ```
64
+
65
+
66
+ ## Microphone
67
+
68
+ The `Microphone` class uses Pocketsphinx's libsphinxad to record audio for speech recognition. For desktop applications this should normally be 16bit/16kHz raw PCM audio, so these are the default settings. The exact audio backend depends on [what was selected](https://github.com/cmusphinx/sphinxbase/blob/master/configure.in#L138) when libsphinxad was built. On OSX, OpenAL is [now supported](https://github.com/cmusphinx/sphinxbase/commit/5cc55c4721273681200e1f754ff0798ac073b950) and should work just fine.
69
+
70
+ For example, to record and save a 5 second raw audio file:
71
+
72
+ ```ruby
73
+ microphone = Microphone.new
74
+
75
+ File.open("test.raw", "wb") do |file|
76
+ microphone.record do
77
+ FFI::MemoryPointer.new(:int16, 4096) do |buffer|
78
+ 50.times do
79
+ sample_count = microphone.read_audio(buffer, 4096)
80
+ file.write buffer.get_bytes(0, sample_count * 2)
81
+
82
+ sleep 0.1
83
+ end
84
+ end
85
+ end
86
+ end
87
+ ```
88
+
89
+
90
+ ## Contributing
91
+
92
+ 1. Fork it ( https://github.com/[my-github-username]/pocketsphinx-ruby/fork )
93
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
94
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
95
+ 4. Push to the branch (`git push origin my-new-feature`)
96
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/clean'
3
+
4
+ begin
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec)
7
+ task :default => [:spec]
8
+ rescue LoadError
9
+ end
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "pocketsphinx-ruby"
5
+
6
+ include Pocketsphinx
7
+
8
+ configuration = Configuration.default
9
+ recognizer = LiveSpeechRecognizer.new(configuration)
10
+
11
+ recognizer.recognize do |speech|
12
+ puts speech
13
+ end
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "pocketsphinx-ruby"
5
+
6
+ include Pocketsphinx
7
+
8
+ MAX_SAMPLES = 4096
9
+ RECORDING_INTERVAL = 0.1
10
+ RECORDING_LENGTH = 5
11
+
12
+ puts "Recording #{RECORDING_LENGTH} seconds of audio..."
13
+
14
+ microphone = Microphone.new
15
+
16
+ File.open("test_write.raw", "wb") do |file|
17
+ microphone.record do
18
+ FFI::MemoryPointer.new(:int16, MAX_SAMPLES) do |buffer|
19
+ (RECORDING_LENGTH / RECORDING_INTERVAL).times do
20
+ sample_count = microphone.read_audio(buffer, MAX_SAMPLES)
21
+
22
+ # sample_count * 2 since this is length in bytes
23
+ file.write buffer.get_bytes(0, sample_count * 2)
24
+
25
+ sleep RECORDING_INTERVAL
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1 @@
1
+ require 'pocketsphinx'
@@ -0,0 +1,16 @@
1
+ require 'ffi'
2
+
3
+ require "pocketsphinx/version"
4
+ require "pocketsphinx/api/sphinxbase"
5
+ require "pocketsphinx/api/sphinxad"
6
+ require "pocketsphinx/api/pocketsphinx"
7
+
8
+ require "pocketsphinx/configuration"
9
+ require "pocketsphinx/microphone"
10
+ require "pocketsphinx/decoder"
11
+ require "pocketsphinx/speech_recognizer"
12
+ require "pocketsphinx/live_speech_recognizer"
13
+
14
+ module Pocketsphinx
15
+
16
+ end
@@ -0,0 +1,17 @@
1
+ module Pocketsphinx
2
+ module API
3
+ module Pocketsphinx
4
+ extend FFI::Library
5
+ ffi_lib "libpocketsphinx"
6
+
7
+ attach_function :ps_init, [:pointer], :pointer
8
+ attach_function :ps_default_search_args, [:pointer], :void
9
+ attach_function :ps_args, [], :pointer
10
+ attach_function :ps_process_raw, [:pointer, :pointer, :size_t, :int, :int], :int
11
+ attach_function :ps_start_utt, [:pointer, :string], :int
12
+ attach_function :ps_end_utt, [:pointer], :int
13
+ attach_function :ps_get_in_speech, [:pointer], :uint8
14
+ attach_function :ps_get_hyp, [:pointer, :pointer, :pointer], :string
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ module Pocketsphinx
2
+ module API
3
+ module SphinxAD
4
+ extend FFI::Library
5
+ ffi_lib "libsphinxad"
6
+
7
+ attach_function :ad_open_dev, [:string, :int], :pointer
8
+ attach_function :ad_start_rec, [:pointer], :int32
9
+ attach_function :ad_stop_rec, [:pointer], :int32
10
+ attach_function :ad_read, [:pointer, :pointer, :int], :int
11
+ attach_function :ad_close, [:pointer], :void
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,26 @@
1
+ module Pocketsphinx
2
+ module API
3
+ module Sphinxbase
4
+ extend FFI::Library
5
+ ffi_lib "libsphinxbase"
6
+
7
+ class Argument < FFI::Struct
8
+ layout :name, :string,
9
+ :type, :int,
10
+ :deflt, :string,
11
+ :doc, :string
12
+ end
13
+
14
+ # TODO: Document on ruby side?
15
+ attach_function :cmd_ln_parse_r, [:pointer, :pointer, :int32, :pointer, :int], :pointer
16
+ attach_function :cmd_ln_float_r, [:pointer, :string], :double
17
+ attach_function :cmd_ln_set_float_r, [:pointer, :string, :double], :void
18
+ attach_function :cmd_ln_int_r, [:pointer, :string], :int
19
+ attach_function :cmd_ln_set_int_r, [:pointer, :string, :int], :void
20
+ attach_function :cmd_ln_str_r, [:pointer, :string], :string
21
+ attach_function :cmd_ln_set_str_r, [:pointer, :string, :string], :void
22
+ attach_function :err_set_debug_level, [:int], :int
23
+ attach_function :err_set_logfile, [:string], :int
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,65 @@
1
+ require 'pocketsphinx/configuration/setting_definition'
2
+
3
+ module Pocketsphinx
4
+ class Configuration
5
+ attr_reader :ps_config
6
+
7
+ private_class_method :new
8
+
9
+ def initialize(ps_arg_defs)
10
+ @ps_arg_defs = ps_arg_defs
11
+ @setting_definitions = SettingDefinition.from_arg_defs(ps_arg_defs)
12
+
13
+ # Sets default settings based on definitions
14
+ @ps_config = API::Sphinxbase.cmd_ln_parse_r(nil, ps_arg_defs, 0, nil, 1)
15
+
16
+ # Sets default grammar and language model if they are not set explicitly and
17
+ # are present in the default search path.
18
+ API::Pocketsphinx.ps_default_search_args(@ps_config)
19
+ end
20
+
21
+ def self.default
22
+ new(API::Pocketsphinx.ps_args)
23
+ end
24
+
25
+ def [](name)
26
+ unless definition = @setting_definitions[name]
27
+ raise "Configuration setting '#{name}' does not exist"
28
+ end
29
+
30
+ case definition.type
31
+ when :integer
32
+ API::Sphinxbase.cmd_ln_int_r(@ps_config, "-#{name}")
33
+ when :float
34
+ API::Sphinxbase.cmd_ln_float_r(@ps_config, "-#{name}")
35
+ when :string
36
+ API::Sphinxbase.cmd_ln_str_r(@ps_config, "-#{name}")
37
+ when :boolean
38
+ API::Sphinxbase.cmd_ln_int_r(@ps_config, "-#{name}") != 0
39
+ when :string_list
40
+ raise NotImplementedException
41
+ end
42
+ end
43
+
44
+ def []=(name, value)
45
+ unless definition = @setting_definitions[name]
46
+ raise "Configuration setting '#{name}' does not exist"
47
+ end
48
+
49
+ case definition.type
50
+ when :integer
51
+ raise "Configuration setting '#{name}' must be a Fixnum" unless value.respond_to?(:to_i)
52
+ API::Sphinxbase.cmd_ln_set_int_r(@ps_config, "-#{name}", value.to_i)
53
+ when :float
54
+ raise "Configuration setting '#{name}' must be a Float" unless value.respond_to?(:to_i)
55
+ API::Sphinxbase.cmd_ln_set_float_r(@ps_config, "-#{name}", value.to_f)
56
+ when :string
57
+ API::Sphinxbase.cmd_ln_set_str_r(@ps_config, "-#{name}", value.to_s)
58
+ when :boolean
59
+ API::Sphinxbase.cmd_ln_set_int_r(@ps_config, "-#{name}", value ? 1 : 0)
60
+ when :string_list
61
+ raise NotImplementedException
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,40 @@
1
+ module Pocketsphinx
2
+ class Configuration
3
+ class SettingDefinition
4
+ TYPES = [:integer, :float, :string, :boolean, :string_list]
5
+
6
+ def initialize(name, type_code, default, doc)
7
+ @name, @type_code, @default, @doc = name, type_code, default, doc
8
+ end
9
+
10
+ def type
11
+ # Remove the required bit if it exists and find type from log2 of code
12
+ TYPES[Math.log2(@type_code - @type_code%2) - 1]
13
+ end
14
+
15
+ def required?
16
+ @type_code % 2 == 1
17
+ end
18
+
19
+ # Build setting definitions from pocketsphinx argument definitions
20
+ #
21
+ # @param [FFI::Pointer] ps_arg_defs A pointer to the Pocketsphinx argument definitions
22
+ #
23
+ # @return [Hash] A hash of setting definitions (name -> definition)
24
+ def self.from_arg_defs(ps_arg_defs)
25
+ {}.tap do |setting_defs|
26
+ arg_array = FFI::Pointer.new(API::Sphinxbase::Argument, ps_arg_defs)
27
+
28
+ 0.upto(Float::INFINITY) do |i|
29
+ arg = API::Sphinxbase::Argument.new(arg_array[i])
30
+ break if arg[:name].nil?
31
+
32
+ # Remove '-' from argument name
33
+ name = arg[:name][1..-1]
34
+ setting_defs[name] = new(name, arg[:type], arg[:deflt], arg[:doc])
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,64 @@
1
+ module Pocketsphinx
2
+ class Decoder
3
+ Error = Class.new(StandardError)
4
+
5
+ attr_reader :ps_decoder
6
+ attr_writer :ps_api
7
+
8
+ def initialize(configuration)
9
+ @configuration = configuration
10
+ @ps_decoder = ps_api.ps_init(configuration.ps_config)
11
+ end
12
+
13
+ # Decode raw audio data.
14
+ #
15
+ # @param [Boolean] no_search If non-zero, perform feature extraction but don't do any
16
+ # recognition yet. This may be necessary if your processor has trouble doing recognition in
17
+ # real-time.
18
+ # @param [Boolean] full_utt If non-zero, this block of data is a full utterance
19
+ # worth of data. This may allow the recognizer to produce more accurate results.
20
+ # @return Number of frames of data searched
21
+ def process_raw(buffer, size, no_search = false, full_utt = false)
22
+ ps_api.ps_process_raw(@ps_decoder, buffer, size, no_search ? 1 : 0, full_utt ? 1 : 0).tap do |result|
23
+ raise Error, "Decoder#process_raw failed with error code #{result}" if result < 0
24
+ end
25
+ end
26
+
27
+ # Start utterance processing.
28
+ #
29
+ # This function should be called before any utterance data is passed
30
+ # to the decoder. It marks the start of a new utterance and
31
+ # reinitializes internal data structures.
32
+ #
33
+ # @param [String] name String uniquely identifying this utterance. If nil, one will be created.
34
+ def start_utterance(name = nil)
35
+ ps_api.ps_start_utt(@ps_decoder, name).tap do |result|
36
+ raise Error, "Decoder#start_utterance failed with error code #{result}" if result < 0
37
+ end
38
+ end
39
+
40
+ # End utterance processing
41
+ def end_utterance
42
+ ps_api.ps_end_utt(@ps_decoder).tap do |result|
43
+ raise Error, "Decoder#end_utterance failed with error code #{result}" if result < 0
44
+ end
45
+ end
46
+
47
+ # Checks if the last feed audio buffer contained speech
48
+ def in_speech?
49
+ ps_api.ps_get_in_speech(@ps_decoder) != 0
50
+ end
51
+
52
+ # Get hypothesis string and path score.
53
+ #
54
+ # @return [String] Hypothesis string
55
+ # @todo Expand to return path score and utterance ID
56
+ def hypothesis
57
+ ps_api.ps_get_hyp(@ps_decoder, nil, nil)
58
+ end
59
+
60
+ def ps_api
61
+ @ps_api || API::Pocketsphinx
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,48 @@
1
+ module Pocketsphinx
2
+ # High-level class for live speech recognition.
3
+ #
4
+ # Modeled on the LiveSpeechRecognizer from Sphinx4.
5
+ class LiveSpeechRecognizer < SpeechRecognizer
6
+ attr_writer :microphone
7
+
8
+ def microphone
9
+ @microphone ||= Microphone.new
10
+ end
11
+
12
+ # Recognize utterances and yield hypotheses in infinite loop
13
+ #
14
+ # @param [Float]
15
+ def recognize(recording_interval = 0.1, max_samples = 4096)
16
+ decoder.start_utterance
17
+
18
+ microphone.record do
19
+ FFI::MemoryPointer.new(:int16, max_samples) do |buffer|
20
+ loop do
21
+ if decoder.in_speech?
22
+ process_audio(buffer, max_samples, recording_interval) while decoder.in_speech?
23
+ yield get_hypothesis
24
+ else
25
+ process_audio(buffer, max_samples, recording_interval)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def process_audio(buffer, max_samples, delay)
35
+ sample_count = microphone.read_audio(buffer, max_samples)
36
+ decoder.process_raw(buffer, sample_count)
37
+ sleep delay
38
+ end
39
+
40
+ # Called on speech -> silence transition
41
+ def get_hypothesis
42
+ decoder.end_utterance
43
+ decoder.hypothesis.tap do
44
+ decoder.start_utterance
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,65 @@
1
+ module Pocketsphinx
2
+ # Provides non-blocking audio recording using libsphinxad
3
+ class Microphone
4
+ Error = Class.new(StandardError)
5
+
6
+ attr_reader :ps_audio_device
7
+ attr_writer :ps_api
8
+
9
+ # Opens an audio device for recording
10
+ #
11
+ # The device is opened in non-blocking mode and placed in idle state.
12
+ #
13
+ # @param [Fixnum] sample_rate Samples per second for recording, e.g. 16000 for 16kHz
14
+ # @param [String] default_device The device name
15
+ # @param [Object] ps_api A SphinxAD API implementation to use, API::SphinxAD if not provided
16
+ def initialize(sample_rate = 16000, default_device = nil, ps_api = nil)
17
+ @ps_api = ps_api
18
+ @ps_audio_device = ps_api.ad_open_dev(default_device, sample_rate)
19
+
20
+ # Ensure that audio device is closed when object is garbage collected
21
+ ObjectSpace.define_finalizer(self, self.class.finalize(ps_api, @ps_audio_device))
22
+ end
23
+
24
+ def self.finalize(ps_api, ps_audio_device)
25
+ proc { ps_api.ad_close(ps_audio_device) }
26
+ end
27
+
28
+ def record
29
+ start_recording
30
+ yield
31
+ stop_recording
32
+ end
33
+
34
+ def start_recording
35
+ ps_api.ad_start_rec(@ps_audio_device).tap do |result|
36
+ raise Error, "Microphone#start_recording failed with error code #{result}" if result < 0
37
+ end
38
+ end
39
+
40
+ def stop_recording
41
+ ps_api.ad_stop_rec(@ps_audio_device).tap do |result|
42
+ raise Error, "Microphone#stop_recording failed with error code #{result}" if result < 0
43
+ end
44
+ end
45
+
46
+ # Read next block of audio samples while recording; read upto max samples into buf.
47
+ #
48
+ # @param [FFI::Pointer] buffer 16bit buffer of at least max_samples in size
49
+ # @return [Fixnum] Samples actually read (could be 0 since non-blocking); -1 if not
50
+ # recording and no more samples remaining to be read from most recent recording.
51
+ def read_audio(buffer, max_samples = 4096)
52
+ ps_api.ad_read(@ps_audio_device, buffer, max_samples)
53
+ end
54
+
55
+ def close_device
56
+ ps_api.ad_close(@ps_audio_device).tap do |result|
57
+ raise Error, "Microphone#close_device failed with error code #{result}" if result < 0
58
+ end
59
+ end
60
+
61
+ def ps_api
62
+ @ps_api || API::SphinxAD
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,9 @@
1
+ module Pocketsphinx
2
+ class SpeechRecognizer
3
+ attr_reader :decoder
4
+
5
+ def initialize(configuration= nil)
6
+ @decoder = Decoder.new(configuration || Configuration.default)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module Pocketsphinx
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pocketsphinx/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "pocketsphinx-ruby"
8
+ spec.version = Pocketsphinx::VERSION
9
+ spec.authors = ["Howard Wilson"]
10
+ spec.email = ["howard@watsonbox.net"]
11
+ spec.summary = %q{Ruby FFI pocketsphinx bindings}
12
+ spec.description = %q{Ruby FFI pocketsphinx bindings}
13
+ spec.homepage = "https://github.com/watsonbox/pocketsphinx-ruby"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "ffi", ">= 1.9"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.6"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec", "~> 3.1.0"
26
+ spec.add_development_dependency "coveralls"
27
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe Configuration do
4
+ subject { Pocketsphinx::Configuration.default }
5
+
6
+ it "provides a default pocketsphinx configuration" do
7
+ expect(subject).to be_a(Pocketsphinx::Configuration)
8
+ end
9
+
10
+ it "supports integer settings" do
11
+ expect(subject['frate']).to eq(100)
12
+ expect(subject['frate']).to be_a(Fixnum)
13
+
14
+ subject['frate'] = 50
15
+ expect(subject['frate']).to eq(50)
16
+ end
17
+
18
+ it "supports float settings" do
19
+ expect(subject['samprate']).to eq(16000)
20
+ expect(subject['samprate']).to be_a(Float)
21
+
22
+ subject['samprate'] = 8000
23
+ expect(subject['samprate']).to eq(8000)
24
+ end
25
+
26
+ it "supports getting strings" do
27
+ expect(subject['warp_type']).to eq('inverse_linear')
28
+
29
+ subject['warp_type'] = 'different_type'
30
+ expect(subject['warp_type']).to eq('different_type')
31
+ end
32
+
33
+ it "supports getting booleans" do
34
+ expect(subject['smoothspec']).to eq(false)
35
+
36
+ subject['smoothspec'] = true
37
+ expect(subject['smoothspec']).to eq(true)
38
+ end
39
+
40
+ it 'raises exceptions when setting with incorrectly typed values' do
41
+ expect { subject['frate'] = true }.to raise_exception "Configuration setting 'frate' must be a Fixnum"
42
+ end
43
+
44
+ it 'raises exceptions when a setting is unknown' do
45
+ expect { subject['unknown'] = true }.to raise_exception "Configuration setting 'unknown' does not exist"
46
+ end
47
+ end
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+
3
+ describe Decoder do
4
+ subject { @decoder }
5
+ let(:ps_api) { @decoder.ps_api = double }
6
+
7
+ # Share decoder across all examples for speed
8
+ before :all do
9
+ @decoder = Decoder.new(Configuration.default)
10
+ end
11
+
12
+ describe '#process_raw' do
13
+ it 'calls libpocketsphinx' do
14
+ FFI::MemoryPointer.new(:int16, 4096) do |buffer|
15
+ expect(ps_api)
16
+ .to receive(:ps_process_raw)
17
+ .with(subject.ps_decoder, buffer, 4096, 0, 0)
18
+ .and_return(0)
19
+
20
+ subject.process_raw(buffer, 4096, false, false)
21
+ end
22
+ end
23
+
24
+ it 'raises an exception on error' do
25
+ FFI::MemoryPointer.new(:int16, 4096) do |buffer|
26
+ expect(ps_api)
27
+ .to receive(:ps_process_raw)
28
+ .with(subject.ps_decoder, buffer, 4096, 0, 0)
29
+ .and_return(-1)
30
+
31
+ expect { subject.process_raw(buffer, 4096, false, false) }
32
+ .to raise_exception "Decoder#process_raw failed with error code -1"
33
+ end
34
+ end
35
+ end
36
+
37
+ describe '#start_utterance' do
38
+ it 'calls libpocketsphinx' do
39
+ expect(ps_api)
40
+ .to receive(:ps_start_utt)
41
+ .with(subject.ps_decoder, "Utterance Name")
42
+ .and_return(0)
43
+
44
+ subject.start_utterance("Utterance Name")
45
+ end
46
+
47
+ it 'raises an exception on error' do
48
+ expect(ps_api)
49
+ .to receive(:ps_start_utt)
50
+ .with(subject.ps_decoder, "Utterance Name")
51
+ .and_return(-1)
52
+
53
+ expect { subject.start_utterance("Utterance Name") }
54
+ .to raise_exception "Decoder#start_utterance failed with error code -1"
55
+ end
56
+ end
57
+
58
+ describe '#end_utterance' do
59
+ it 'calls libpocketsphinx' do
60
+ expect(ps_api)
61
+ .to receive(:ps_end_utt)
62
+ .with(subject.ps_decoder)
63
+ .and_return(0)
64
+
65
+ subject.end_utterance
66
+ end
67
+
68
+ it 'raises an exception on error' do
69
+ expect(ps_api)
70
+ .to receive(:ps_end_utt)
71
+ .with(subject.ps_decoder)
72
+ .and_return(-1)
73
+
74
+ expect { subject.end_utterance }
75
+ .to raise_exception "Decoder#end_utterance failed with error code -1"
76
+ end
77
+ end
78
+
79
+ describe '#in_speech' do
80
+ it 'calls libpocketsphinx' do
81
+ expect(ps_api)
82
+ .to receive(:ps_get_in_speech)
83
+ .with(subject.ps_decoder)
84
+ .and_return(0)
85
+
86
+ expect(subject.in_speech?).to eq(false)
87
+ end
88
+ end
89
+
90
+ describe '#hypothesis' do
91
+ it 'calls libpocketsphinx' do
92
+ expect(ps_api)
93
+ .to receive(:ps_get_hyp)
94
+ .with(subject.ps_decoder, nil, nil)
95
+ .and_return("Hypothesis")
96
+
97
+ expect(subject.hypothesis).to eq("Hypothesis")
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+
3
+ describe Microphone do
4
+ module DummyAPI
5
+ def self.ad_open_dev(default_device, sample_rate)
6
+ :audio_device
7
+ end
8
+ end
9
+
10
+ subject { @microphone }
11
+ let!(:ps_api) { @microphone.ps_api = double }
12
+
13
+ # Share microphone across all examples for speed
14
+ before :all do
15
+ # Don't open an audio device as there isn't one on Travis CI
16
+ @microphone = Microphone.new(16000, nil, DummyAPI)
17
+ end
18
+
19
+ describe '#start_recording' do
20
+ it 'calls libsphinxad' do
21
+ expect(ps_api)
22
+ .to receive(:ad_start_rec)
23
+ .with(subject.ps_audio_device)
24
+ .and_return(0)
25
+
26
+ subject.start_recording
27
+ end
28
+
29
+ it 'raises an exception on error' do
30
+ expect(ps_api)
31
+ .to receive(:ad_start_rec)
32
+ .with(subject.ps_audio_device)
33
+ .and_return(-1)
34
+
35
+ expect { subject.start_recording }
36
+ .to raise_exception "Microphone#start_recording failed with error code -1"
37
+ end
38
+ end
39
+
40
+ describe '#stop_recording' do
41
+ it 'calls libsphinxad' do
42
+ expect(ps_api)
43
+ .to receive(:ad_stop_rec)
44
+ .with(subject.ps_audio_device)
45
+ .and_return(0)
46
+
47
+ subject.stop_recording
48
+ end
49
+
50
+ it 'raises an exception on error' do
51
+ expect(ps_api)
52
+ .to receive(:ad_stop_rec)
53
+ .with(subject.ps_audio_device)
54
+ .and_return(-1)
55
+
56
+ expect { subject.stop_recording }
57
+ .to raise_exception "Microphone#stop_recording failed with error code -1"
58
+ end
59
+ end
60
+
61
+ describe '#record' do
62
+ it 'starts and stops recording, yielding control' do
63
+ expect(subject).to receive(:start_recording).ordered
64
+
65
+ subject.record do
66
+ expect(subject).to receive(:stop_recording).ordered
67
+ end
68
+ end
69
+ end
70
+
71
+ describe '#read_audio' do
72
+ it 'calls libsphinxad' do
73
+ expect(ps_api)
74
+ .to receive(:ad_read)
75
+ .with(subject.ps_audio_device, :buffer, 4096)
76
+ .and_return(0)
77
+
78
+ subject.read_audio(:buffer, 4096)
79
+ end
80
+ end
81
+
82
+ describe '#close_device' do
83
+ it 'calls libsphinxad' do
84
+ expect(ps_api)
85
+ .to receive(:ad_close)
86
+ .with(subject.ps_audio_device)
87
+ .and_return(0)
88
+
89
+ subject.close_device
90
+ end
91
+
92
+ it 'raises an exception on error' do
93
+ expect(ps_api)
94
+ .to receive(:ad_close)
95
+ .with(subject.ps_audio_device)
96
+ .and_return(-1)
97
+
98
+ expect { subject.close_device }
99
+ .to raise_exception "Microphone#close_device failed with error code -1"
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,38 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
3
+
4
+ require 'pocketsphinx'
5
+
6
+ POCKETSPHINX_LOG_FILE = "./log/pocketsphinx_test.log"
7
+
8
+ # Set up pocketsphinx logging to a file rather than stdout
9
+ FileUtils.makedirs File.dirname(POCKETSPHINX_LOG_FILE)
10
+ FileUtils.touch POCKETSPHINX_LOG_FILE
11
+ Pocketsphinx::API::Sphinxbase.err_set_logfile POCKETSPHINX_LOG_FILE
12
+
13
+ RSpec.configure do |config|
14
+ include Pocketsphinx
15
+
16
+ # rspec-expectations config goes here. You can use an alternate
17
+ # assertion/expectation library such as wrong or the stdlib/minitest
18
+ # assertions if you prefer.
19
+ config.expect_with :rspec do |expectations|
20
+ # This option will default to `true` in RSpec 4. It makes the `description`
21
+ # and `failure_message` of custom matchers include text for helper methods
22
+ # defined using `chain`, e.g.:
23
+ # be_bigger_than(2).and_smaller_than(4).description
24
+ # # => "be bigger than 2 and smaller than 4"
25
+ # ...rather than:
26
+ # # => "be bigger than 2"
27
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
28
+ end
29
+
30
+ # rspec-mocks config goes here. You can use an alternate test double
31
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
32
+ config.mock_with :rspec do |mocks|
33
+ # Prevents you from mocking or stubbing a method that does not exist on
34
+ # a real object. This is generally recommended, and will default to
35
+ # `true` in RSpec 4.
36
+ mocks.verify_partial_doubles = true
37
+ end
38
+ end
metadata ADDED
@@ -0,0 +1,144 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pocketsphinx-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Howard Wilson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ffi
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '1.9'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '1.9'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.6'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 3.1.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 3.1.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: coveralls
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Ruby FFI pocketsphinx bindings
84
+ email:
85
+ - howard@watsonbox.net
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rspec"
92
+ - ".travis.yml"
93
+ - Gemfile
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - examples/pocketsphinx_continuous.rb
98
+ - examples/record_audio_file.rb
99
+ - lib/pocketsphinx-ruby.rb
100
+ - lib/pocketsphinx.rb
101
+ - lib/pocketsphinx/api/pocketsphinx.rb
102
+ - lib/pocketsphinx/api/sphinxad.rb
103
+ - lib/pocketsphinx/api/sphinxbase.rb
104
+ - lib/pocketsphinx/configuration.rb
105
+ - lib/pocketsphinx/configuration/setting_definition.rb
106
+ - lib/pocketsphinx/decoder.rb
107
+ - lib/pocketsphinx/live_speech_recognizer.rb
108
+ - lib/pocketsphinx/microphone.rb
109
+ - lib/pocketsphinx/speech_recognizer.rb
110
+ - lib/pocketsphinx/version.rb
111
+ - pocketsphinx-ruby.gemspec
112
+ - spec/configuration_spec.rb
113
+ - spec/decoder_spec.rb
114
+ - spec/microphone_spec.rb
115
+ - spec/spec_helper.rb
116
+ homepage: https://github.com/watsonbox/pocketsphinx-ruby
117
+ licenses:
118
+ - MIT
119
+ metadata: {}
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubyforge_project:
136
+ rubygems_version: 2.2.2
137
+ signing_key:
138
+ specification_version: 4
139
+ summary: Ruby FFI pocketsphinx bindings
140
+ test_files:
141
+ - spec/configuration_spec.rb
142
+ - spec/decoder_spec.rb
143
+ - spec/microphone_spec.rb
144
+ - spec/spec_helper.rb