diff --git a/.gitignore b/.gitignore index 31cafb5..da725c5 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ tmp *.o *.a mkmf.log +.DS_Store +log diff --git a/lib/pocketsphinx.rb b/lib/pocketsphinx.rb index 0ef2823..c3f41a6 100644 --- a/lib/pocketsphinx.rb +++ b/lib/pocketsphinx.rb @@ -5,6 +5,8 @@ require "pocketsphinx/api/sphinxad" require "pocketsphinx/api/pocketsphinx" +require "pocketsphinx/configuration" + module Pocketsphinx # Your code goes here... end diff --git a/lib/pocketsphinx/configuration.rb b/lib/pocketsphinx/configuration.rb new file mode 100644 index 0000000..93b1adf --- /dev/null +++ b/lib/pocketsphinx/configuration.rb @@ -0,0 +1,59 @@ +require 'pocketsphinx/configuration/setting_definition' + +module Pocketsphinx + class Configuration + private_class_method :new + + def initialize(ps_arg_defs) + @ps_arg_defs = ps_arg_defs + @setting_definitions = SettingDefinition.from_arg_defs(ps_arg_defs) + + # Sets default settings based on definitions + @ps_config = API::Sphinxbase.cmd_ln_parse_r(nil, ps_arg_defs, 0, nil, 1) + end + + def self.default + new(API::Pocketsphinx.ps_args) + end + + def [](name) + unless definition = @setting_definitions[name] + raise "Configuration setting '#{name}' does not exist" + end + + case definition.type + when :integer + API::Sphinxbase.cmd_ln_int_r(@ps_config, "-#{name}") + when :float + API::Sphinxbase.cmd_ln_float_r(@ps_config, "-#{name}") + when :string + API::Sphinxbase.cmd_ln_str_r(@ps_config, "-#{name}") + when :boolean + API::Sphinxbase.cmd_ln_int_r(@ps_config, "-#{name}") != 0 + when :string_list + raise NotImplementedException + end + end + + def []=(name, value) + unless definition = @setting_definitions[name] + raise "Configuration setting '#{name}' does not exist" + end + + case definition.type + when :integer + raise "Configuration setting '#{name}' must be a Fixnum" unless value.respond_to?(:to_i) + API::Sphinxbase.cmd_ln_set_int_r(@ps_config, "-#{name}", value.to_i) + when :float + raise "Configuration setting '#{name}' must be a Float" unless value.respond_to?(:to_i) + API::Sphinxbase.cmd_ln_set_float_r(@ps_config, "-#{name}", value.to_f) + when :string + API::Sphinxbase.cmd_ln_set_str_r(@ps_config, "-#{name}", value.to_s) + when :boolean + API::Sphinxbase.cmd_ln_set_int_r(@ps_config, "-#{name}", value ? 1 : 0) + when :string_list + raise NotImplementedException + end + end + end +end diff --git a/lib/pocketsphinx/configuration/setting_definition.rb b/lib/pocketsphinx/configuration/setting_definition.rb new file mode 100644 index 0000000..858c4bc --- /dev/null +++ b/lib/pocketsphinx/configuration/setting_definition.rb @@ -0,0 +1,40 @@ +module Pocketsphinx + class Configuration + class SettingDefinition + TYPES = [:integer, :float, :string, :boolean, :string_list] + + def initialize(name, type_code, default, doc) + @name, @type_code, @default, @doc = name, type_code, default, doc + end + + def type + # Remove the required bit if it exists and find type from log2 of code + TYPES[Math.log2(@type_code - @type_code%2) - 1] + end + + def required? + @type_code % 2 == 1 + end + + # Build setting definitions from pocketsphinx argument definitions + # + # @param [FFI::Pointer] ps_arg_defs A pointer to the Pocketsphinx argument definitions + # + # @return [Hash] A hash of setting definitions (name -> definition) + def self.from_arg_defs(ps_arg_defs) + {}.tap do |setting_defs| + arg_array = FFI::Pointer.new(API::Sphinxbase::Argument, ps_arg_defs) + + 0.upto(Float::INFINITY) do |i| + arg = API::Sphinxbase::Argument.new(arg_array[i]) + break if arg[:name].nil? + + # Remove '-' from argument name + name = arg[:name][1..-1] + setting_defs[name] = new(name, arg[:type], arg[:deflt], arg[:doc]) + end + end + end + end + end +end diff --git a/pocketsphinx-ruby.gemspec b/pocketsphinx-ruby.gemspec index 91af5f7..9794872 100644 --- a/pocketsphinx-ruby.gemspec +++ b/pocketsphinx-ruby.gemspec @@ -22,4 +22,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency "bundler", "~> 1.6" spec.add_development_dependency "rake" + spec.add_development_dependency "rspec", "~> 3.1.0" end diff --git a/spec/configuration_spec.rb b/spec/configuration_spec.rb new file mode 100644 index 0000000..9e542a0 --- /dev/null +++ b/spec/configuration_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe Pocketsphinx::Configuration do + subject { Pocketsphinx::Configuration.default } + + it "provides a default pocketsphinx configuration" do + expect(subject).to be_a(Pocketsphinx::Configuration) + end + + it "supports integer settings" do + expect(subject['frate']).to eq(100) + expect(subject['frate']).to be_a(Fixnum) + + subject['frate'] = 50 + expect(subject['frate']).to eq(50) + end + + it "supports float settings" do + expect(subject['samprate']).to eq(16000) + expect(subject['samprate']).to be_a(Float) + + subject['samprate'] = 8000 + expect(subject['samprate']).to eq(8000) + end + + it "supports getting strings" do + expect(subject['warp_type']).to eq('inverse_linear') + + subject['warp_type'] = 'different_type' + expect(subject['warp_type']).to eq('different_type') + end + + it "supports getting booleans" do + expect(subject['smoothspec']).to eq(false) + + subject['smoothspec'] = true + expect(subject['smoothspec']).to eq(true) + end + + it 'raises exceptions when setting with incorrectly typed values' do + expect { subject['frate'] = true }.to raise_exception "Configuration setting 'frate' must be a Fixnum" + end + + it 'raises exceptions when a setting is unknown' do + expect { subject['unknown'] = true }.to raise_exception "Configuration setting 'unknown' does not exist" + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 607474b..d87f7a1 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,19 +1,12 @@ -# This file was generated by the `rspec --init` command. Conventionally, all -# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. -# The generated `.rspec` file contains `--require spec_helper` which will cause this -# file to always be loaded, without a need to explicitly require it in any files. -# -# Given that it is always loaded, you are encouraged to keep this file as -# light-weight as possible. Requiring heavyweight dependencies from this file -# will add to the boot time of your test suite on EVERY test run, even for an -# individual file that may not need all of that loaded. Instead, consider making -# a separate helper file that requires the additional dependencies and performs -# the additional setup, and require it from the spec files that actually need it. -# -# The `.rspec` file also contains a few flags that are not defaults but that -# users commonly want. -# -# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +require 'pocketsphinx' + +POCKETSPHINX_LOG_FILE = "./log/pocketsphinx_test.log" + +# Set up pocketsphinx logging to a file rather than stdout +FileUtils.makedirs File.dirname(POCKETSPHINX_LOG_FILE) +FileUtils.touch POCKETSPHINX_LOG_FILE +Pocketsphinx::API::Sphinxbase.err_set_logfile POCKETSPHINX_LOG_FILE + RSpec.configure do |config| # rspec-expectations config goes here. You can use an alternate # assertion/expectation library such as wrong or the stdlib/minitest @@ -37,53 +30,4 @@ # `true` in RSpec 4. mocks.verify_partial_doubles = true end - -# The settings below are suggested to provide a good initial experience -# with RSpec, but feel free to customize to your heart's content. -=begin - # These two settings work together to allow you to limit a spec run - # to individual examples or groups you care about by tagging them with - # `:focus` metadata. When nothing is tagged with `:focus`, all examples - # get run. - config.filter_run :focus - config.run_all_when_everything_filtered = true - - # Limits the available syntax to the non-monkey patched syntax that is recommended. - # For more details, see: - # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax - # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ - # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching - config.disable_monkey_patching! - - # This setting enables warnings. It's recommended, but in some cases may - # be too noisy due to issues in dependencies. - config.warnings = true - - # Many RSpec users commonly either run the entire suite or an individual - # file, and it's useful to allow more verbose output when running an - # individual spec file. - if config.files_to_run.one? - # Use the documentation formatter for detailed output, - # unless a formatter has already been configured - # (e.g. via a command-line flag). - config.default_formatter = 'doc' - end - - # Print the 10 slowest examples and example groups at the - # end of the spec run, to help surface which specs are running - # particularly slow. - config.profile_examples = 10 - - # Run specs in random order to surface order dependencies. If you find an - # order dependency and want to debug it, you can fix the order by providing - # the seed, which is printed after each run. - # --seed 1234 - config.order = :random - - # Seed global randomization in this process using the `--seed` CLI option. - # Setting this allows you to use `--seed` to deterministically reproduce - # test failures related to randomization by passing the same `--seed` value - # as the one that triggered the failure. - Kernel.srand config.seed -=end end