From fcd5dd64d2a7538ff45c41ad3c08c627a59598b9 Mon Sep 17 00:00:00 2001
From: Erik Andresen <erik@vontaene.de>
Date: Sat, 8 Dec 2018 17:44:51 +0100
Subject: [PATCH] Added pocketsphinx script

---
 data/keywords.kws       |   1 +
 data/robot.jsgf         |  15 ++++++
 scripts/pocketsphinx.rb | 105 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 121 insertions(+)
 create mode 100644 data/keywords.kws
 create mode 100644 data/robot.jsgf
 create mode 100755 scripts/pocketsphinx.rb

diff --git a/data/keywords.kws b/data/keywords.kws
new file mode 100644
index 0000000..35c2c31
--- /dev/null
+++ b/data/keywords.kws
@@ -0,0 +1 @@
+wild thumper /1e-11/
diff --git a/data/robot.jsgf b/data/robot.jsgf
new file mode 100644
index 0000000..388d1f5
--- /dev/null
+++ b/data/robot.jsgf
@@ -0,0 +1,15 @@
+#JSGF V1.0;
+
+grammar robot;
+
+<bool> = (on | off);
+<number> = minus* (zero | one | two | three | four | five | six | seven | eight | nine | ten | eleven | twelve | thirteen | fourteen | fifteen | sixteen | seventeen | eighteen | nineteen | twenty | thirty | forty | fifty | sixty | seventy | eighty  | ninety | hundred | thousand | million);
+
+<misc_command> = (light | lights) [<bool>];
+<engine> = (stop | forward | backward | increase speed | decrease speed);
+<get> = get (temp | temperature | light | voltage | current | pressure | mute | mic | silence | speed | velocity | position | angle | compass | motion | secure | engine | odom | humidity);
+<go> = go (forward | backward) <number>+ (meter | meters | centimeter | centimeters);
+<turn> = turn (left | right | (to | by) <number>+ [(degree | degrees)]);
+<speed> = set+ speed <number>+ | set default speed;
+
+public <rules> = <misc_command> | <engine> | <get> | <go> | <turn> | <speed>;
diff --git a/scripts/pocketsphinx.rb b/scripts/pocketsphinx.rb
new file mode 100755
index 0000000..c2a87fb
--- /dev/null
+++ b/scripts/pocketsphinx.rb
@@ -0,0 +1,105 @@
+#!/usr/bin/ruby -w
+
+require 'gst'
+require 'pry'
+require 'logger'
+require 'ros'
+require 'std_msgs/String'
+
+class Speak
+	def initialize(node)
+		@logger = Logger.new(STDOUT)
+		@publisher = node.advertise('asr_result', Std_msgs::String)
+		@pipeline = Gst.parse_launch('alsasrc device="plughw:1,0" ! audio/x-raw,format=S16LE,channels=1,rate=16000 ! cutter leaky=true name=cutter'\
+					     ' ! tee name=jsgf ! queue leaky=downstream ! valve name=valve_jsgf drop=true ! pocketsphinx name=asr_jsgf ! fakesink async=false jsgf.'\
+					     ' ! pocketsphinx name=asr_kws ! fakesink async=false'\
+					    )
+		# Ignore everything below the configured volume
+		cutter = @pipeline.get_by_name('cutter')
+		cutter.set_property('threshold-dB', -20)
+		cutter.set_property('pre-length', 100000000) # pocketsphinx needs about 0.1s before start
+		cutter.set_property('run-length', 1300000000)
+
+		asr_jsgf = @pipeline.get_by_name('asr_jsgf')
+		asr_jsgf.set_property('hmm', 'pocketsphinx/adapt/cmusphinx-en-us-5.2')
+		asr_jsgf.set_property('mllr', 'pocketsphinx/adapt/mllr_matrix')
+		asr_jsgf.set_property('jsgf', 'data/robot.jsgf')
+
+		asr_kws = @pipeline.get_by_name('asr_kws')
+		asr_kws.set_property('hmm', 'pocketsphinx/adapt/cmusphinx-en-us-5.2')
+		asr_kws.set_property('mllr', 'pocketsphinx/adapt/mllr_matrix')
+		asr_kws.set_property('kws', 'data/keywords.kws')
+
+		bus = @pipeline.bus()
+		bus.add_watch do |bus, message|
+			case message.type
+			when Gst::MessageType::EOS
+				loop.quit
+			when Gst::MessageType::ERROR
+				p message.parse_error
+				binding.pry # open console
+				loop.quit
+			when Gst::MessageType::ELEMENT
+				if message.src.name == "asr_kws" and message.structure.get_value(:final).value
+					keyword_detect(message.structure.get_value(:hypothesis).value, message.structure.get_value(:confidence).value)
+				elsif message.src.name == "asr_jsgf"
+					if message.structure.get_value(:final).value
+						final_result(message.structure.get_value(:hypothesis).value, message.structure.get_value(:confidence).value)
+					end
+				elsif message.src.name == "cutter"
+					if message.structure.get_value(:above).value
+						@logger.debug "Start recording.."
+					else
+						@logger.debug "Stop recording"
+					end
+				end
+			end
+			true
+		end
+
+		@pipeline.play
+	end
+
+	# Enables/Disables the jsgf pipeline
+	def enable_jsgf(bEnable)
+		valve = @pipeline.get_by_name('valve_jsgf')
+		valve.set_property("drop", !bEnable)
+	end
+
+	# Result of jsgf pipeline
+	def final_result(hyp, confidence)
+		@logger.info "final: " + hyp + " " + confidence.to_s
+		enable_jsgf(false)
+
+		# Publish pocketsphinx result as ros message
+		msg = Std_msgs::String.new
+		msg.data = hyp
+		@publisher.publish(msg)
+	end
+
+	def keyword_detect(hyp, confidence)
+		@logger.debug "Got keyword: " + hyp
+		enable_jsgf(true)
+	end
+
+	def stop
+		@pipeline.stop
+	end
+end
+
+if __FILE__ == $0
+	node = ROS::Node.new('wild_thumper/pocketsphinx')
+	app = Speak.new(node)
+	loop = GLib::MainLoop.new(nil, false)
+	begin
+		Thread.new {
+			loop.run
+		}
+		node.spin
+	rescue Interrupt
+	ensure
+		app.stop
+		node.shutdown
+		loop.quit
+	end
+end
-- 
2.39.5