From daed7e28909ba31757e072a25ae9a6eb115de3b2 Mon Sep 17 00:00:00 2001 From: ppibburr Date: Wed, 21 Mar 2018 22:18:54 -0400 Subject: [PATCH 01/13] ... --- examples/Ruby/Makefile | 20 +++ examples/Ruby/bin/sample | 20 +++ examples/Ruby/install_portaudio.sh | 36 ++++++ examples/Ruby/lib/snowboy.rb | 121 ++++++++++++++++++ examples/Ruby/patches/portaudio.patch | 11 ++ examples/Ruby/port_audio_detect.c | 149 ++++++++++++++++++++++ examples/Ruby/resources | 1 + examples/Ruby/ruby.mk | 58 +++++++++ examples/Ruby/snowboy-detect-c-wrapper.cc | 82 ++++++++++++ examples/Ruby/snowboy-detect-c-wrapper.h | 51 ++++++++ 10 files changed, 549 insertions(+) create mode 100644 examples/Ruby/Makefile create mode 100755 examples/Ruby/bin/sample create mode 100755 examples/Ruby/install_portaudio.sh create mode 100644 examples/Ruby/lib/snowboy.rb create mode 100644 examples/Ruby/patches/portaudio.patch create mode 100644 examples/Ruby/port_audio_detect.c create mode 120000 examples/Ruby/resources create mode 100644 examples/Ruby/ruby.mk create mode 100644 examples/Ruby/snowboy-detect-c-wrapper.cc create mode 100644 examples/Ruby/snowboy-detect-c-wrapper.h diff --git a/examples/Ruby/Makefile b/examples/Ruby/Makefile new file mode 100644 index 00000000..547409a9 --- /dev/null +++ b/examples/Ruby/Makefile @@ -0,0 +1,20 @@ +include ruby.mk + +SHAREDLIB = lib/snowboy/ext/libsnowboydetect.so + +OBJFILES = port_audio_detect.o snowboy-detect-c-wrapper.o + +all: $(SHAREDLIB) + +%.a: + $(MAKE) -C ${@D} ${@F} + +# We have to use the C++ compiler to link. +$(SHAREDLIB): $(PORTAUDIOLIBS) $(SNOWBOYDETECTLIBFILE) $(OBJFILES) + $(CXX) $(OBJFILES) $(SNOWBOYDETECTLIBFILE) $(PORTAUDIOLIBS) $(LDLIBS) -shared -o $(SHAREDLIB) + +$(PORTAUDIOLIBS): + @-./install_portaudio.sh + +clean: + -rm -f *.o *.a $(SHAREDLIB) $(OBJFILES) diff --git a/examples/Ruby/bin/sample b/examples/Ruby/bin/sample new file mode 100755 index 00000000..06852370 --- /dev/null +++ b/examples/Ruby/bin/sample @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby + +$: << File.join(File.dirname(__FILE__), '..', 'lib') + +require 'snowboy' + +resource_filename = "./resources/common.res"; +model_filename = "./resources/models/snowboy.umdl"; + +detector = Snowboy::Detector.new(resource: resource_filename, model: model_filename, gain: 0.5, sensitivity: 1); + +puts "Listening... Press Ctrl+C to exit" + +detector.run do |result| + if (result > 0) + puts "Hotword %d detected!" % result + end +end + +while true; end diff --git a/examples/Ruby/install_portaudio.sh b/examples/Ruby/install_portaudio.sh new file mode 100755 index 00000000..ceecd2e0 --- /dev/null +++ b/examples/Ruby/install_portaudio.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +# This script attempts to install PortAudio, which can grap a live audio stream +# from the soundcard. +# +# On linux systems, we only build with ALSA, so make sure you install it using +# e.g.: +# sudo apt-get -y install libasound2-dev + +echo "Installing portaudio" + +if [ ! -e pa_stable_v190600_20161030.tgz ]; then + wget -T 10 -t 3 \ + http://www.portaudio.com/archives/pa_stable_v190600_20161030.tgz || exit 1; +fi + +tar -xovzf pa_stable_v190600_20161030.tgz || exit 1 + +cd portaudio +patch < ../patches/portaudio.patch + +MACOS=`uname 2>/dev/null | grep Darwin` +if [ -z "$MACOS" ]; then + ./configure --without-jack --without-oss \ + --with-alsa --prefix=`pwd`/install --with-pic || exit 1; + sed -i '40s:src/common/pa_ringbuffer.o::g' Makefile + sed -i '40s:$: src/common/pa_ringbuffer.o:' Makefile +else + # People may have changed OSX's default configuration -- we use clang++. + CC=clang CXX=clang++ ./configure --prefix=`pwd`/install --with-pic +fi + +make +make install + +cd .. diff --git a/examples/Ruby/lib/snowboy.rb b/examples/Ruby/lib/snowboy.rb new file mode 100644 index 00000000..7923b502 --- /dev/null +++ b/examples/Ruby/lib/snowboy.rb @@ -0,0 +1,121 @@ +$: << BASE_PATH=File.dirname(__FILE__) + +require 'ffi' + +module Snowboy + module Lib + extend FFI::Library + ffi_lib File.join(BASE_PATH, 'snowboy', 'ext', 'libsnowboydetect.so') + + attach_function :SnowboyDetectConstructor, [:string, :string], :pointer + attach_function :StartAudioCapturing, [:int, :int, :int], :void + attach_function :SnowboyDetectBitsPerSample, [:pointer], :int + attach_function :SnowboyDetectSampleRate, [:pointer], :int + attach_function :SnowboyDetectNumChannels, [:pointer], :int + attach_function :SnowboyDetectNumHotwords, [:pointer], :int + attach_function :SnowboyDetectReset, [:pointer], :bool + attach_function :StopAudioCapturing, [], :void + attach_function :SnowboyDetectRunDetection, [:pointer, :pointer, :int, :bool], :int + attach_function :LoadAudioData, [], :int + attach_function :SnowboyDetectSetAudioGain, [:pointer, :float], :void + attach_function :SnowboyDetectSetSensitivity, [:pointer, :string], :void + attach_variable :g_data, :g_data, :pointer + end + + class Detector + attr_reader :resource, :model, :sensitivity, :audio_gain, :ptr + def initialize resource: nil, model: nil, sensitivity: 0.5, gain: 1 + @ptr = Lib::SnowboyDetectConstructor(resource, model) + + self.sensitivity = sensitivity; + self.audio_gain = gain; + + # Initializes PortAudio + capture(); + end + + def run &b + capture + + @run = true + + @thread = Thread.new do + begin + while @run + if (length = load_audio_data) > 0 + b.call run_detection(g_data, length, false) + end + end + rescue => e + puts e + puts e.backtrace.join("\n") + end + end + end + + def stop + @run = false + @thread.kill + stop_capture + end + + def audio_gain= gain + @audio_gain = gain + + Lib::SnowboyDetectSetAudioGain(ptr, audio_gain) + end + + def sensitivity= lvl + Lib::SnowboyDetectSetSensitivity(ptr, lvl.to_s) + end + + def sample_rate + Lib::SnowboyDetectSampleRate(ptr) + end + + def bits_per_sample + Lib::SnowboyDetectBitsPerSample(ptr) + end + + def n_channels + Lib::SnowboyDetectNumChannels(ptr) + end + + def n_hotwords + Lib::SnowboyDetectNumHotwords(ptr) + end + + def reset + Lib::SnowboyDetectReset(ptr) + end + + private + def capture + rate = sample_rate + channels = n_channels + bps = bits_per_sample + + Lib::StartAudioCapturing(rate, channels, bps) + end + + private + def stop_capture + Lib::StopAudioCapturing() + end + + private + def load_audio_data + Lib::LoadAudioData() + end + + private + def g_data + Lib.g_data; + end + + private + def run_detection *o + Lib::SnowboyDetectRunDetection(ptr, *o) + end + end +end diff --git a/examples/Ruby/patches/portaudio.patch b/examples/Ruby/patches/portaudio.patch new file mode 100644 index 00000000..1c618329 --- /dev/null +++ b/examples/Ruby/patches/portaudio.patch @@ -0,0 +1,11 @@ +--- Makefile.in 2017-05-31 16:42:16.000000000 -0700 ++++ Makefile_new.in 2017-05-31 16:44:02.000000000 -0700 +@@ -193,6 +193,8 @@ + for include in $(INCLUDES); do \ + $(INSTALL_DATA) -m 644 $(top_srcdir)/include/$$include $(DESTDIR)$(includedir)/$$include; \ + done ++ $(INSTALL_DATA) -m 644 $(top_srcdir)/src/common/pa_ringbuffer.h $(DESTDIR)$(includedir)/$$include ++ $(INSTALL_DATA) -m 644 $(top_srcdir)/src/common/pa_util.h $(DESTDIR)$(includedir)/$$include + $(INSTALL) -d $(DESTDIR)$(libdir)/pkgconfig + $(INSTALL) -m 644 portaudio-2.0.pc $(DESTDIR)$(libdir)/pkgconfig/portaudio-2.0.pc + @echo "" diff --git a/examples/Ruby/port_audio_detect.c b/examples/Ruby/port_audio_detect.c new file mode 100644 index 00000000..f1815c28 --- /dev/null +++ b/examples/Ruby/port_audio_detect.c @@ -0,0 +1,149 @@ +// example/Ruby/port_audio_detect.c + +// Copyright 2017 KITT.AI (author: Guoguo Chen, PpiBbuRr) + +#include +#include +#include +#include +#include +#include +#include + +#include "snowboy-detect-c-wrapper.h" + +// Pointer to the ring buffer memory. +char* g_ringbuffer; +// Ring buffer wrapper used in PortAudio. +PaUtilRingBuffer g_pa_ringbuffer; +// Pointer to PortAudio stream. +PaStream* g_pa_stream; +// Number of lost samples at each LoadAudioData() due to ring buffer overflow. +int g_num_lost_samples; +// Wait for this number of samples in each LoadAudioData() call. +int g_min_read_samples; +// Pointer to the audio data. +int16_t* g_data; + +int PortAudioCallback(const void* input, + void* output, + unsigned long frame_count, + const PaStreamCallbackTimeInfo* time_info, + PaStreamCallbackFlags status_flags, + void* user_data) { + ring_buffer_size_t num_written_samples = + PaUtil_WriteRingBuffer(&g_pa_ringbuffer, input, frame_count); + g_num_lost_samples += frame_count - num_written_samples; + return paContinue; +} + +void StartAudioCapturing(int sample_rate, + int num_channels, int bits_per_sample) { + g_data = NULL; + g_num_lost_samples = 0; + g_min_read_samples = sample_rate * 0.1; + + // Allocates ring buffer memory. + int ringbuffer_size = 16384; + g_ringbuffer = (char*)( + PaUtil_AllocateMemory(bits_per_sample / 8 * ringbuffer_size)); + if (g_ringbuffer == NULL) { + fprintf(stderr, "Fail to allocate memory for ring buffer.\n"); + exit(1); + } + + // Initializes PortAudio ring buffer. + ring_buffer_size_t rb_init_ans = + PaUtil_InitializeRingBuffer(&g_pa_ringbuffer, bits_per_sample / 8, + ringbuffer_size, g_ringbuffer); + if (rb_init_ans == -1) { + fprintf(stderr, "Ring buffer size is not power of 2.\n"); + exit(1); + } + + // Initializes PortAudio. + PaError pa_init_ans = Pa_Initialize(); + if (pa_init_ans != paNoError) { + fprintf(stderr, "Fail to initialize PortAudio, error message is %s.\n", + Pa_GetErrorText(pa_init_ans)); + exit(1); + } + + PaError pa_open_ans; + if (bits_per_sample == 8) { + pa_open_ans = Pa_OpenDefaultStream( + &g_pa_stream, num_channels, 0, paUInt8, sample_rate, + paFramesPerBufferUnspecified, PortAudioCallback, NULL); + } else if (bits_per_sample == 16) { + pa_open_ans = Pa_OpenDefaultStream( + &g_pa_stream, num_channels, 0, paInt16, sample_rate, + paFramesPerBufferUnspecified, PortAudioCallback, NULL); + } else if (bits_per_sample == 32) { + pa_open_ans = Pa_OpenDefaultStream( + &g_pa_stream, num_channels, 0, paInt32, sample_rate, + paFramesPerBufferUnspecified, PortAudioCallback, NULL); + } else { + fprintf(stderr, "Unsupported BitsPerSample: %d.\n", bits_per_sample); + exit(1); + } + if (pa_open_ans != paNoError) { + fprintf(stderr, "Fail to open PortAudio stream, error message is %s.\n", + Pa_GetErrorText(pa_open_ans)); + exit(1); + } + + PaError pa_stream_start_ans = Pa_StartStream(g_pa_stream); + if (pa_stream_start_ans != paNoError) { + fprintf(stderr, "Fail to start PortAudio stream, error message is %s.\n", + Pa_GetErrorText(pa_stream_start_ans)); + exit(1); + } +} + +void StopAudioCapturing() { + if (g_data != NULL) { + free(g_data); + g_data = NULL; + } + Pa_StopStream(g_pa_stream); + Pa_CloseStream(g_pa_stream); + Pa_Terminate(); + PaUtil_FreeMemory(g_ringbuffer); +} + +int LoadAudioData() { + if (g_data != NULL) { + free(g_data); + g_data = NULL; + } + + // Checks ring buffer overflow. + if (g_num_lost_samples > 0) { + fprintf(stderr, "Lost %d samples due to ring buffer overflow.\n", + g_num_lost_samples); + g_num_lost_samples = 0; + } + + ring_buffer_size_t num_available_samples = 0; + while (true) { + num_available_samples = + PaUtil_GetRingBufferReadAvailable(&g_pa_ringbuffer); + if (num_available_samples >= g_min_read_samples) { + break; + } + Pa_Sleep(5); + } + + // Reads data. + num_available_samples = PaUtil_GetRingBufferReadAvailable(&g_pa_ringbuffer); + g_data = malloc(num_available_samples * sizeof(int16_t)); + ring_buffer_size_t num_read_samples = PaUtil_ReadRingBuffer( + &g_pa_ringbuffer, g_data, num_available_samples); + if (num_read_samples != num_available_samples) { + fprintf(stderr, "%d samples were available, but only %d samples were read" + ".\n", num_available_samples, num_read_samples); + } + return num_read_samples; +} + + diff --git a/examples/Ruby/resources b/examples/Ruby/resources new file mode 120000 index 00000000..bc764151 --- /dev/null +++ b/examples/Ruby/resources @@ -0,0 +1 @@ +../../resources \ No newline at end of file diff --git a/examples/Ruby/ruby.mk b/examples/Ruby/ruby.mk new file mode 100644 index 00000000..73d1140c --- /dev/null +++ b/examples/Ruby/ruby.mk @@ -0,0 +1,58 @@ +TOPDIR := ../../ +DYNAMIC := True +CC := +CXX := +LDFLAGS := +LDLIBS := +PORTAUDIOINC := portaudio/install/include +PORTAUDIOLIBS := portaudio/install/lib/libportaudio.a + +CFLAGS := +CXXFLAGS += -D_GLIBCXX_USE_CXX11_ABI=0 + +ifeq ($(DYNAMIC), True) + CFLAGS += -fPIC + CXXFLAGS += -fPIC +endif + +ifeq ($(shell uname -m | cut -c 1-3), x86) + CFLAGS += -msse -msse2 + CXXFLAGS += -msse -msse2 +endif + +ifeq ($(shell uname), Darwin) + # By default Mac uses clang++ as g++, but people may have changed their + # default configuration. + CC := clang + CXX := clang++ + CFLAGS += -I$(TOPDIR) -Wall -I$(PORTAUDIOINC) + CXXFLAGS += -I$(TOPDIR) -Wall -Wno-sign-compare -Winit-self \ + -DHAVE_POSIX_MEMALIGN -DHAVE_CLAPACK -I$(PORTAUDIOINC) + LDLIBS += -ldl -lm -framework Accelerate -framework CoreAudio \ + -framework AudioToolbox -framework AudioUnit -framework CoreServices \ + $(PORTAUDIOLIBS) + SNOWBOYDETECTLIBFILE := $(TOPDIR)/lib/osx/libsnowboy-detect.a +else ifeq ($(shell uname), Linux) + CC := gcc + CXX := g++ + CFLAGS += -I$(TOPDIR) -Wall -I$(PORTAUDIOINC) + CXXFLAGS += -I$(TOPDIR) -std=c++0x -Wall -Wno-sign-compare \ + -Wno-unused-local-typedefs -Winit-self -rdynamic \ + -DHAVE_POSIX_MEMALIGN -I$(PORTAUDIOINC) + LDLIBS += -ldl -lm -Wl,-Bstatic -Wl,-Bdynamic -lrt -lpthread $(PORTAUDIOLIBS)\ + -L/usr/lib/atlas-base -lf77blas -lcblas -llapack_atlas -latlas -lasound + SNOWBOYDETECTLIBFILE := $(TOPDIR)/lib/ubuntu64/libsnowboy-detect.a + ifneq (,$(findstring arm,$(shell uname -m))) + SNOWBOYDETECTLIBFILE := $(TOPDIR)/lib/rpi/libsnowboy-detect.a + endif +endif + +# Suppress clang warnings... +COMPILER = $(shell $(CXX) -v 2>&1 ) +ifeq ($(findstring clang,$(COMPILER)), clang) + CXXFLAGS += -Wno-mismatched-tags -Wno-c++11-extensions +endif + +# Set optimization level. +CFLAGS += -O3 +CXXFLAGS += -O3 diff --git a/examples/Ruby/snowboy-detect-c-wrapper.cc b/examples/Ruby/snowboy-detect-c-wrapper.cc new file mode 100644 index 00000000..9129271a --- /dev/null +++ b/examples/Ruby/snowboy-detect-c-wrapper.cc @@ -0,0 +1,82 @@ +// snowboy-detect-c-wrapper.cc + +// Copyright 2017 KITT.AI (author: Guoguo Chen) + +#include + +#include "snowboy-detect-c-wrapper.h" +#include "include/snowboy-detect.h" + +extern "C" { + SnowboyDetect* SnowboyDetectConstructor(const char* const resource_filename, + const char* const model_str) { + return reinterpret_cast( + new snowboy::SnowboyDetect(resource_filename, model_str)); + } + + bool SnowboyDetectReset(SnowboyDetect* detector) { + assert(detector != NULL); + return reinterpret_cast(detector)->Reset(); + } + + int SnowboyDetectRunDetection(SnowboyDetect* detector, + const int16_t* const data, + const int array_length, bool is_end) { + assert(detector != NULL); + assert(data != NULL); + return reinterpret_cast( + detector)->RunDetection(data, array_length, is_end); + } + + void SnowboyDetectSetSensitivity(SnowboyDetect* detector, + const char* const sensitivity_str) { + assert(detector != NULL); + reinterpret_cast( + detector)->SetSensitivity(sensitivity_str); + } + + void SnowboyDetectSetAudioGain(SnowboyDetect* detector, + const float audio_gain) { + assert(detector != NULL); + reinterpret_cast( + detector)->SetAudioGain(audio_gain); + } + + void SnowboyDetectUpdateModel(SnowboyDetect* detector) { + assert(detector != NULL); + reinterpret_cast(detector)->UpdateModel(); + } + + void SnowboyDetectApplyFrontend(SnowboyDetect* detector, + const bool apply_frontend) { + assert(detector != NULL); + reinterpret_cast( + detector)->ApplyFrontend(apply_frontend); + } + + int SnowboyDetectNumHotwords(SnowboyDetect* detector) { + assert(detector != NULL); + return reinterpret_cast(detector)->NumHotwords(); + } + + int SnowboyDetectSampleRate(SnowboyDetect* detector) { + assert(detector != NULL); + return reinterpret_cast(detector)->SampleRate(); + } + + int SnowboyDetectNumChannels(SnowboyDetect* detector) { + assert(detector != NULL); + return reinterpret_cast(detector)->NumChannels(); + } + + int SnowboyDetectBitsPerSample(SnowboyDetect* detector) { + assert(detector != NULL); + return reinterpret_cast(detector)->BitsPerSample(); + } + + void SnowboyDetectDestructor(SnowboyDetect* detector) { + assert(detector != NULL); + delete reinterpret_cast(detector); + detector = NULL; + } +} diff --git a/examples/Ruby/snowboy-detect-c-wrapper.h b/examples/Ruby/snowboy-detect-c-wrapper.h new file mode 100644 index 00000000..99a4e02a --- /dev/null +++ b/examples/Ruby/snowboy-detect-c-wrapper.h @@ -0,0 +1,51 @@ +// snowboy-detect-c-wrapper.h + +// Copyright 2017 KITT.AI (author: Guoguo Chen) + +#ifndef SNOWBOY_DETECT_C_WRAPPER_H_ +#define SNOWBOY_DETECT_C_WRAPPER_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + typedef struct SnowboyDetect SnowboyDetect; + + SnowboyDetect* SnowboyDetectConstructor(const char* const resource_filename, + const char* const model_str); + + bool SnowboyDetectReset(SnowboyDetect* detector); + + int SnowboyDetectRunDetection(SnowboyDetect* detector, + const int16_t* const data, + const int array_length, bool is_end); + + void SnowboyDetectSetSensitivity(SnowboyDetect* detector, + const char* const sensitivity_str); + + void SnowboyDetectSetAudioGain(SnowboyDetect* detector, + const float audio_gain); + + void SnowboyDetectUpdateModel(SnowboyDetect* detector); + + void SnowboyDetectApplyFrontend(SnowboyDetect* detector, + const bool apply_frontend); + + int SnowboyDetectNumHotwords(SnowboyDetect* detector); + + int SnowboyDetectSampleRate(SnowboyDetect* detector); + + int SnowboyDetectNumChannels(SnowboyDetect* detector); + + int SnowboyDetectBitsPerSample(SnowboyDetect* detector); + + void SnowboyDetectDestructor(SnowboyDetect* detector); + +#ifdef __cplusplus +} +#endif + +#endif // SNOWBOY_DETECT_C_WRAPPER_H_ From 053ac59d5347820ae3d2b39fd230628c6c0ce059 Mon Sep 17 00:00:00 2001 From: ppibburr Date: Wed, 21 Mar 2018 22:49:51 -0400 Subject: [PATCH 02/13] code cleanup --- examples/Ruby/bin/sample | 2 +- examples/Ruby/lib/snowboy.rb | 24 +++++++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/examples/Ruby/bin/sample b/examples/Ruby/bin/sample index 06852370..1030868d 100755 --- a/examples/Ruby/bin/sample +++ b/examples/Ruby/bin/sample @@ -17,4 +17,4 @@ detector.run do |result| end end -while true; end +while detector.running?; end diff --git a/examples/Ruby/lib/snowboy.rb b/examples/Ruby/lib/snowboy.rb index 7923b502..d7dfa102 100644 --- a/examples/Ruby/lib/snowboy.rb +++ b/examples/Ruby/lib/snowboy.rb @@ -27,11 +27,11 @@ class Detector def initialize resource: nil, model: nil, sensitivity: 0.5, gain: 1 @ptr = Lib::SnowboyDetectConstructor(resource, model) + @resource = resource + @model = model + self.sensitivity = sensitivity; - self.audio_gain = gain; - - # Initializes PortAudio - capture(); + self.audio_gain = gain; end def run &b @@ -41,21 +41,23 @@ def run &b @thread = Thread.new do begin - while @run - if (length = load_audio_data) > 0 - b.call run_detection(g_data, length, false) + while @run + if (length = load_audio_data) > 0 + b.call run_detection(g_data, length, false) + end end - end rescue => e puts e puts e.backtrace.join("\n") end + + @run = false end end def stop @run = false - @thread.kill + @thread.kill if @thread stop_capture end @@ -89,6 +91,10 @@ def reset Lib::SnowboyDetectReset(ptr) end + def running? + @run + end + private def capture rate = sample_rate From 7c9bb88d5049552537b454640c398f0a7af76e79 Mon Sep 17 00:00:00 2001 From: ppibburr Date: Thu, 22 Mar 2018 10:07:27 -0400 Subject: [PATCH 03/13] makefile --- examples/Ruby/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/Ruby/Makefile b/examples/Ruby/Makefile index 547409a9..3234ca7a 100644 --- a/examples/Ruby/Makefile +++ b/examples/Ruby/Makefile @@ -11,6 +11,7 @@ all: $(SHAREDLIB) # We have to use the C++ compiler to link. $(SHAREDLIB): $(PORTAUDIOLIBS) $(SNOWBOYDETECTLIBFILE) $(OBJFILES) + -mkdir -p lib/snowboy/ext $(CXX) $(OBJFILES) $(SNOWBOYDETECTLIBFILE) $(PORTAUDIOLIBS) $(LDLIBS) -shared -o $(SHAREDLIB) $(PORTAUDIOLIBS): From 8bb42138c799d2aca07cc6cbcaf966b477ba2e55 Mon Sep 17 00:00:00 2001 From: ppibburr Date: Fri, 23 Mar 2018 23:29:36 -0400 Subject: [PATCH 04/13] ... --- examples/Ruby/Makefile | 21 ---- examples/Ruby/README.md | 13 +++ examples/Ruby/bin/sample | 20 ---- examples/Ruby/install_portaudio.sh | 36 ------ examples/Ruby/lib/snowboy.rb | 127 ---------------------- examples/Ruby/patches/portaudio.patch | 11 -- examples/Ruby/port-audio-sample.rb | 23 ++++ examples/Ruby/resources | 1 - examples/Ruby/snowboy-detect-c-wrapper.cc | 82 -------------- examples/Ruby/snowboy-detect-c-wrapper.h | 51 --------- 10 files changed, 36 insertions(+), 349 deletions(-) delete mode 100644 examples/Ruby/Makefile create mode 100644 examples/Ruby/README.md delete mode 100755 examples/Ruby/bin/sample delete mode 100755 examples/Ruby/install_portaudio.sh delete mode 100644 examples/Ruby/lib/snowboy.rb delete mode 100644 examples/Ruby/patches/portaudio.patch create mode 100644 examples/Ruby/port-audio-sample.rb delete mode 120000 examples/Ruby/resources delete mode 100644 examples/Ruby/snowboy-detect-c-wrapper.cc delete mode 100644 examples/Ruby/snowboy-detect-c-wrapper.h diff --git a/examples/Ruby/Makefile b/examples/Ruby/Makefile deleted file mode 100644 index 3234ca7a..00000000 --- a/examples/Ruby/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -include ruby.mk - -SHAREDLIB = lib/snowboy/ext/libsnowboydetect.so - -OBJFILES = port_audio_detect.o snowboy-detect-c-wrapper.o - -all: $(SHAREDLIB) - -%.a: - $(MAKE) -C ${@D} ${@F} - -# We have to use the C++ compiler to link. -$(SHAREDLIB): $(PORTAUDIOLIBS) $(SNOWBOYDETECTLIBFILE) $(OBJFILES) - -mkdir -p lib/snowboy/ext - $(CXX) $(OBJFILES) $(SNOWBOYDETECTLIBFILE) $(PORTAUDIOLIBS) $(LDLIBS) -shared -o $(SHAREDLIB) - -$(PORTAUDIOLIBS): - @-./install_portaudio.sh - -clean: - -rm -f *.o *.a $(SHAREDLIB) $(OBJFILES) diff --git a/examples/Ruby/README.md b/examples/Ruby/README.md new file mode 100644 index 00000000..179312f8 --- /dev/null +++ b/examples/Ruby/README.md @@ -0,0 +1,13 @@ +Sample program to detect hotword. + +Dependencies +=== +snowboy shared lib +`cd ../../bindings/C && make` + +This sample uses portaudio to capture from a library is provied in `../../bindings/Ruby/ext/capture/port-audio` +`cd ../../bindings/Ruby/ext/capture/port-audio && make` + +Usage +=== +`ruby port-audio-sample.rb` diff --git a/examples/Ruby/bin/sample b/examples/Ruby/bin/sample deleted file mode 100755 index 1030868d..00000000 --- a/examples/Ruby/bin/sample +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env ruby - -$: << File.join(File.dirname(__FILE__), '..', 'lib') - -require 'snowboy' - -resource_filename = "./resources/common.res"; -model_filename = "./resources/models/snowboy.umdl"; - -detector = Snowboy::Detector.new(resource: resource_filename, model: model_filename, gain: 0.5, sensitivity: 1); - -puts "Listening... Press Ctrl+C to exit" - -detector.run do |result| - if (result > 0) - puts "Hotword %d detected!" % result - end -end - -while detector.running?; end diff --git a/examples/Ruby/install_portaudio.sh b/examples/Ruby/install_portaudio.sh deleted file mode 100755 index ceecd2e0..00000000 --- a/examples/Ruby/install_portaudio.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash - -# This script attempts to install PortAudio, which can grap a live audio stream -# from the soundcard. -# -# On linux systems, we only build with ALSA, so make sure you install it using -# e.g.: -# sudo apt-get -y install libasound2-dev - -echo "Installing portaudio" - -if [ ! -e pa_stable_v190600_20161030.tgz ]; then - wget -T 10 -t 3 \ - http://www.portaudio.com/archives/pa_stable_v190600_20161030.tgz || exit 1; -fi - -tar -xovzf pa_stable_v190600_20161030.tgz || exit 1 - -cd portaudio -patch < ../patches/portaudio.patch - -MACOS=`uname 2>/dev/null | grep Darwin` -if [ -z "$MACOS" ]; then - ./configure --without-jack --without-oss \ - --with-alsa --prefix=`pwd`/install --with-pic || exit 1; - sed -i '40s:src/common/pa_ringbuffer.o::g' Makefile - sed -i '40s:$: src/common/pa_ringbuffer.o:' Makefile -else - # People may have changed OSX's default configuration -- we use clang++. - CC=clang CXX=clang++ ./configure --prefix=`pwd`/install --with-pic -fi - -make -make install - -cd .. diff --git a/examples/Ruby/lib/snowboy.rb b/examples/Ruby/lib/snowboy.rb deleted file mode 100644 index d7dfa102..00000000 --- a/examples/Ruby/lib/snowboy.rb +++ /dev/null @@ -1,127 +0,0 @@ -$: << BASE_PATH=File.dirname(__FILE__) - -require 'ffi' - -module Snowboy - module Lib - extend FFI::Library - ffi_lib File.join(BASE_PATH, 'snowboy', 'ext', 'libsnowboydetect.so') - - attach_function :SnowboyDetectConstructor, [:string, :string], :pointer - attach_function :StartAudioCapturing, [:int, :int, :int], :void - attach_function :SnowboyDetectBitsPerSample, [:pointer], :int - attach_function :SnowboyDetectSampleRate, [:pointer], :int - attach_function :SnowboyDetectNumChannels, [:pointer], :int - attach_function :SnowboyDetectNumHotwords, [:pointer], :int - attach_function :SnowboyDetectReset, [:pointer], :bool - attach_function :StopAudioCapturing, [], :void - attach_function :SnowboyDetectRunDetection, [:pointer, :pointer, :int, :bool], :int - attach_function :LoadAudioData, [], :int - attach_function :SnowboyDetectSetAudioGain, [:pointer, :float], :void - attach_function :SnowboyDetectSetSensitivity, [:pointer, :string], :void - attach_variable :g_data, :g_data, :pointer - end - - class Detector - attr_reader :resource, :model, :sensitivity, :audio_gain, :ptr - def initialize resource: nil, model: nil, sensitivity: 0.5, gain: 1 - @ptr = Lib::SnowboyDetectConstructor(resource, model) - - @resource = resource - @model = model - - self.sensitivity = sensitivity; - self.audio_gain = gain; - end - - def run &b - capture - - @run = true - - @thread = Thread.new do - begin - while @run - if (length = load_audio_data) > 0 - b.call run_detection(g_data, length, false) - end - end - rescue => e - puts e - puts e.backtrace.join("\n") - end - - @run = false - end - end - - def stop - @run = false - @thread.kill if @thread - stop_capture - end - - def audio_gain= gain - @audio_gain = gain - - Lib::SnowboyDetectSetAudioGain(ptr, audio_gain) - end - - def sensitivity= lvl - Lib::SnowboyDetectSetSensitivity(ptr, lvl.to_s) - end - - def sample_rate - Lib::SnowboyDetectSampleRate(ptr) - end - - def bits_per_sample - Lib::SnowboyDetectBitsPerSample(ptr) - end - - def n_channels - Lib::SnowboyDetectNumChannels(ptr) - end - - def n_hotwords - Lib::SnowboyDetectNumHotwords(ptr) - end - - def reset - Lib::SnowboyDetectReset(ptr) - end - - def running? - @run - end - - private - def capture - rate = sample_rate - channels = n_channels - bps = bits_per_sample - - Lib::StartAudioCapturing(rate, channels, bps) - end - - private - def stop_capture - Lib::StopAudioCapturing() - end - - private - def load_audio_data - Lib::LoadAudioData() - end - - private - def g_data - Lib.g_data; - end - - private - def run_detection *o - Lib::SnowboyDetectRunDetection(ptr, *o) - end - end -end diff --git a/examples/Ruby/patches/portaudio.patch b/examples/Ruby/patches/portaudio.patch deleted file mode 100644 index 1c618329..00000000 --- a/examples/Ruby/patches/portaudio.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- Makefile.in 2017-05-31 16:42:16.000000000 -0700 -+++ Makefile_new.in 2017-05-31 16:44:02.000000000 -0700 -@@ -193,6 +193,8 @@ - for include in $(INCLUDES); do \ - $(INSTALL_DATA) -m 644 $(top_srcdir)/include/$$include $(DESTDIR)$(includedir)/$$include; \ - done -+ $(INSTALL_DATA) -m 644 $(top_srcdir)/src/common/pa_ringbuffer.h $(DESTDIR)$(includedir)/$$include -+ $(INSTALL_DATA) -m 644 $(top_srcdir)/src/common/pa_util.h $(DESTDIR)$(includedir)/$$include - $(INSTALL) -d $(DESTDIR)$(libdir)/pkgconfig - $(INSTALL) -m 644 portaudio-2.0.pc $(DESTDIR)$(libdir)/pkgconfig/portaudio-2.0.pc - @echo "" diff --git a/examples/Ruby/port-audio-sample.rb b/examples/Ruby/port-audio-sample.rb new file mode 100644 index 00000000..d481090e --- /dev/null +++ b/examples/Ruby/port-audio-sample.rb @@ -0,0 +1,23 @@ +$: << File.join(File.dirname(__FILE__), "..", "..", "bindings", "Ruby", "lib") + + +require "snowboy" +require "snowboy/capture/port-audio/port-audio-capture" + +resource_path = "../../resources/common.res" +model_path = "../../resources/models/snowboy.umdl" + +snowboy = Snowboy::Detector.new(model: model_path, resource: resource_path, gain: 1, sensitivity: 0.5) +pac = Snowboy::Capture::PortAudio.new + +puts "Listening... press Ctrl-c to exit." + +pac.run(snowboy.sample_rate, snowboy.n_channels, snowboy.bits_per_sample) do |data, length| + result = snowboy.run_detection(data, length, false) + + if result > 0 + puts "Hotword %d detected." % result + end +end + +while true; end diff --git a/examples/Ruby/resources b/examples/Ruby/resources deleted file mode 120000 index bc764151..00000000 --- a/examples/Ruby/resources +++ /dev/null @@ -1 +0,0 @@ -../../resources \ No newline at end of file diff --git a/examples/Ruby/snowboy-detect-c-wrapper.cc b/examples/Ruby/snowboy-detect-c-wrapper.cc deleted file mode 100644 index 9129271a..00000000 --- a/examples/Ruby/snowboy-detect-c-wrapper.cc +++ /dev/null @@ -1,82 +0,0 @@ -// snowboy-detect-c-wrapper.cc - -// Copyright 2017 KITT.AI (author: Guoguo Chen) - -#include - -#include "snowboy-detect-c-wrapper.h" -#include "include/snowboy-detect.h" - -extern "C" { - SnowboyDetect* SnowboyDetectConstructor(const char* const resource_filename, - const char* const model_str) { - return reinterpret_cast( - new snowboy::SnowboyDetect(resource_filename, model_str)); - } - - bool SnowboyDetectReset(SnowboyDetect* detector) { - assert(detector != NULL); - return reinterpret_cast(detector)->Reset(); - } - - int SnowboyDetectRunDetection(SnowboyDetect* detector, - const int16_t* const data, - const int array_length, bool is_end) { - assert(detector != NULL); - assert(data != NULL); - return reinterpret_cast( - detector)->RunDetection(data, array_length, is_end); - } - - void SnowboyDetectSetSensitivity(SnowboyDetect* detector, - const char* const sensitivity_str) { - assert(detector != NULL); - reinterpret_cast( - detector)->SetSensitivity(sensitivity_str); - } - - void SnowboyDetectSetAudioGain(SnowboyDetect* detector, - const float audio_gain) { - assert(detector != NULL); - reinterpret_cast( - detector)->SetAudioGain(audio_gain); - } - - void SnowboyDetectUpdateModel(SnowboyDetect* detector) { - assert(detector != NULL); - reinterpret_cast(detector)->UpdateModel(); - } - - void SnowboyDetectApplyFrontend(SnowboyDetect* detector, - const bool apply_frontend) { - assert(detector != NULL); - reinterpret_cast( - detector)->ApplyFrontend(apply_frontend); - } - - int SnowboyDetectNumHotwords(SnowboyDetect* detector) { - assert(detector != NULL); - return reinterpret_cast(detector)->NumHotwords(); - } - - int SnowboyDetectSampleRate(SnowboyDetect* detector) { - assert(detector != NULL); - return reinterpret_cast(detector)->SampleRate(); - } - - int SnowboyDetectNumChannels(SnowboyDetect* detector) { - assert(detector != NULL); - return reinterpret_cast(detector)->NumChannels(); - } - - int SnowboyDetectBitsPerSample(SnowboyDetect* detector) { - assert(detector != NULL); - return reinterpret_cast(detector)->BitsPerSample(); - } - - void SnowboyDetectDestructor(SnowboyDetect* detector) { - assert(detector != NULL); - delete reinterpret_cast(detector); - detector = NULL; - } -} diff --git a/examples/Ruby/snowboy-detect-c-wrapper.h b/examples/Ruby/snowboy-detect-c-wrapper.h deleted file mode 100644 index 99a4e02a..00000000 --- a/examples/Ruby/snowboy-detect-c-wrapper.h +++ /dev/null @@ -1,51 +0,0 @@ -// snowboy-detect-c-wrapper.h - -// Copyright 2017 KITT.AI (author: Guoguo Chen) - -#ifndef SNOWBOY_DETECT_C_WRAPPER_H_ -#define SNOWBOY_DETECT_C_WRAPPER_H_ - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - - typedef struct SnowboyDetect SnowboyDetect; - - SnowboyDetect* SnowboyDetectConstructor(const char* const resource_filename, - const char* const model_str); - - bool SnowboyDetectReset(SnowboyDetect* detector); - - int SnowboyDetectRunDetection(SnowboyDetect* detector, - const int16_t* const data, - const int array_length, bool is_end); - - void SnowboyDetectSetSensitivity(SnowboyDetect* detector, - const char* const sensitivity_str); - - void SnowboyDetectSetAudioGain(SnowboyDetect* detector, - const float audio_gain); - - void SnowboyDetectUpdateModel(SnowboyDetect* detector); - - void SnowboyDetectApplyFrontend(SnowboyDetect* detector, - const bool apply_frontend); - - int SnowboyDetectNumHotwords(SnowboyDetect* detector); - - int SnowboyDetectSampleRate(SnowboyDetect* detector); - - int SnowboyDetectNumChannels(SnowboyDetect* detector); - - int SnowboyDetectBitsPerSample(SnowboyDetect* detector); - - void SnowboyDetectDestructor(SnowboyDetect* detector); - -#ifdef __cplusplus -} -#endif - -#endif // SNOWBOY_DETECT_C_WRAPPER_H_ From 56380eda4d8fc515f2d366b0e2256fe733ed93bf Mon Sep 17 00:00:00 2001 From: ppibburr Date: Fri, 23 Mar 2018 23:30:45 -0400 Subject: [PATCH 05/13] make c shared lib --- bindings/C/Makefile | 17 ++++++ bindings/C/libsnowboydetect.mk | 55 +++++++++++++++++ bindings/C/snowboy-detect-c-wrapper.cc | 82 ++++++++++++++++++++++++++ bindings/C/snowboy-detect-c-wrapper.h | 51 ++++++++++++++++ 4 files changed, 205 insertions(+) create mode 100644 bindings/C/Makefile create mode 100644 bindings/C/libsnowboydetect.mk create mode 100644 bindings/C/snowboy-detect-c-wrapper.cc create mode 100644 bindings/C/snowboy-detect-c-wrapper.h diff --git a/bindings/C/Makefile b/bindings/C/Makefile new file mode 100644 index 00000000..fe7e4667 --- /dev/null +++ b/bindings/C/Makefile @@ -0,0 +1,17 @@ +include libsnowboydetect.mk + +SHAREDLIB = libsnowboydetect.so + +OBJFILES = snowboy-detect-c-wrapper.o + +all: $(SHAREDLIB) + +%.a: + $(MAKE) -C ${@D} ${@F} + +# We have to use the C++ compiler to link. +$(SHAREDLIB): $(SNOWBOYDETECTLIBFILE) $(OBJFILES) + $(CXX) $(OBJFILES) $(SNOWBOYDETECTLIBFILE) $(LDLIBS) -shared -o $(SHAREDLIB) + +clean: + -rm -f *.o *.a $(SHAREDLIB) $(OBJFILES) diff --git a/bindings/C/libsnowboydetect.mk b/bindings/C/libsnowboydetect.mk new file mode 100644 index 00000000..cc2450b6 --- /dev/null +++ b/bindings/C/libsnowboydetect.mk @@ -0,0 +1,55 @@ +TOPDIR := ../../ +DYNAMIC := True +CC := +CXX := +LDFLAGS := +LDLIBS := + +CFLAGS := +CXXFLAGS += -D_GLIBCXX_USE_CXX11_ABI=0 + +ifeq ($(DYNAMIC), True) + CFLAGS += -fPIC + CXXFLAGS += -fPIC +endif + +ifeq ($(shell uname -m | cut -c 1-3), x86) + CFLAGS += -msse -msse2 + CXXFLAGS += -msse -msse2 +endif + +ifeq ($(shell uname), Darwin) + # By default Mac uses clang++ as g++, but people may have changed their + # default configuration. + CC := clang + CXX := clang++ + CFLAGS += -I$(TOPDIR) -Wall -I$(PORTAUDIOINC) + CXXFLAGS += -I$(TOPDIR) -Wall -Wno-sign-compare -Winit-self \ + -DHAVE_POSIX_MEMALIGN -DHAVE_CLAPACK + LDLIBS += -ldl -lm -framework Accelerate -framework CoreAudio \ + -framework AudioToolbox -framework AudioUnit -framework CoreServices + SNOWBOYDETECTLIBFILE := $(TOPDIR)/lib/osx/libsnowboy-detect.a +else ifeq ($(shell uname), Linux) + CC := gcc + CXX := g++ + CFLAGS += -I$(TOPDIR) -Wall + CXXFLAGS += -I$(TOPDIR) -std=c++0x -Wall -Wno-sign-compare \ + -Wno-unused-local-typedefs -Winit-self -rdynamic \ + -DHAVE_POSIX_MEMALIGN + LDLIBS += -ldl -lm -Wl,-Bstatic -Wl,-Bdynamic -lrt -lpthread\ + -L/usr/lib/atlas-base -lf77blas -lcblas -llapack_atlas -latlas + SNOWBOYDETECTLIBFILE := $(TOPDIR)/lib/ubuntu64/libsnowboy-detect.a + ifneq (,$(findstring arm,$(shell uname -m))) + SNOWBOYDETECTLIBFILE := $(TOPDIR)/lib/rpi/libsnowboy-detect.a + endif +endif + +# Suppress clang warnings... +COMPILER = $(shell $(CXX) -v 2>&1 ) +ifeq ($(findstring clang,$(COMPILER)), clang) + CXXFLAGS += -Wno-mismatched-tags -Wno-c++11-extensions +endif + +# Set optimization level. +CFLAGS += -O3 +CXXFLAGS += -O3 diff --git a/bindings/C/snowboy-detect-c-wrapper.cc b/bindings/C/snowboy-detect-c-wrapper.cc new file mode 100644 index 00000000..9129271a --- /dev/null +++ b/bindings/C/snowboy-detect-c-wrapper.cc @@ -0,0 +1,82 @@ +// snowboy-detect-c-wrapper.cc + +// Copyright 2017 KITT.AI (author: Guoguo Chen) + +#include + +#include "snowboy-detect-c-wrapper.h" +#include "include/snowboy-detect.h" + +extern "C" { + SnowboyDetect* SnowboyDetectConstructor(const char* const resource_filename, + const char* const model_str) { + return reinterpret_cast( + new snowboy::SnowboyDetect(resource_filename, model_str)); + } + + bool SnowboyDetectReset(SnowboyDetect* detector) { + assert(detector != NULL); + return reinterpret_cast(detector)->Reset(); + } + + int SnowboyDetectRunDetection(SnowboyDetect* detector, + const int16_t* const data, + const int array_length, bool is_end) { + assert(detector != NULL); + assert(data != NULL); + return reinterpret_cast( + detector)->RunDetection(data, array_length, is_end); + } + + void SnowboyDetectSetSensitivity(SnowboyDetect* detector, + const char* const sensitivity_str) { + assert(detector != NULL); + reinterpret_cast( + detector)->SetSensitivity(sensitivity_str); + } + + void SnowboyDetectSetAudioGain(SnowboyDetect* detector, + const float audio_gain) { + assert(detector != NULL); + reinterpret_cast( + detector)->SetAudioGain(audio_gain); + } + + void SnowboyDetectUpdateModel(SnowboyDetect* detector) { + assert(detector != NULL); + reinterpret_cast(detector)->UpdateModel(); + } + + void SnowboyDetectApplyFrontend(SnowboyDetect* detector, + const bool apply_frontend) { + assert(detector != NULL); + reinterpret_cast( + detector)->ApplyFrontend(apply_frontend); + } + + int SnowboyDetectNumHotwords(SnowboyDetect* detector) { + assert(detector != NULL); + return reinterpret_cast(detector)->NumHotwords(); + } + + int SnowboyDetectSampleRate(SnowboyDetect* detector) { + assert(detector != NULL); + return reinterpret_cast(detector)->SampleRate(); + } + + int SnowboyDetectNumChannels(SnowboyDetect* detector) { + assert(detector != NULL); + return reinterpret_cast(detector)->NumChannels(); + } + + int SnowboyDetectBitsPerSample(SnowboyDetect* detector) { + assert(detector != NULL); + return reinterpret_cast(detector)->BitsPerSample(); + } + + void SnowboyDetectDestructor(SnowboyDetect* detector) { + assert(detector != NULL); + delete reinterpret_cast(detector); + detector = NULL; + } +} diff --git a/bindings/C/snowboy-detect-c-wrapper.h b/bindings/C/snowboy-detect-c-wrapper.h new file mode 100644 index 00000000..99a4e02a --- /dev/null +++ b/bindings/C/snowboy-detect-c-wrapper.h @@ -0,0 +1,51 @@ +// snowboy-detect-c-wrapper.h + +// Copyright 2017 KITT.AI (author: Guoguo Chen) + +#ifndef SNOWBOY_DETECT_C_WRAPPER_H_ +#define SNOWBOY_DETECT_C_WRAPPER_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + typedef struct SnowboyDetect SnowboyDetect; + + SnowboyDetect* SnowboyDetectConstructor(const char* const resource_filename, + const char* const model_str); + + bool SnowboyDetectReset(SnowboyDetect* detector); + + int SnowboyDetectRunDetection(SnowboyDetect* detector, + const int16_t* const data, + const int array_length, bool is_end); + + void SnowboyDetectSetSensitivity(SnowboyDetect* detector, + const char* const sensitivity_str); + + void SnowboyDetectSetAudioGain(SnowboyDetect* detector, + const float audio_gain); + + void SnowboyDetectUpdateModel(SnowboyDetect* detector); + + void SnowboyDetectApplyFrontend(SnowboyDetect* detector, + const bool apply_frontend); + + int SnowboyDetectNumHotwords(SnowboyDetect* detector); + + int SnowboyDetectSampleRate(SnowboyDetect* detector); + + int SnowboyDetectNumChannels(SnowboyDetect* detector); + + int SnowboyDetectBitsPerSample(SnowboyDetect* detector); + + void SnowboyDetectDestructor(SnowboyDetect* detector); + +#ifdef __cplusplus +} +#endif + +#endif // SNOWBOY_DETECT_C_WRAPPER_H_ From 8c570c51ddafac07f048743e47838a8c6a5ec9e1 Mon Sep 17 00:00:00 2001 From: ppibburr Date: Fri, 23 Mar 2018 23:31:51 -0400 Subject: [PATCH 06/13] ruby binding --- bindings/Ruby/README.md | 29 ++++ bindings/Ruby/ext/capture/port-audio/Makefile | 20 +++ .../capture/port-audio/install_portaudio.sh | 36 ++++ .../port-audio/patches/portaudio.patch | 11 ++ .../capture/port-audio/port-audio-capture.c | 160 ++++++++++++++++++ .../capture/port-audio/port-audio-capture.mk | 53 ++++++ bindings/Ruby/lib/snowboy.rb | 75 ++++++++ .../capture/port-audio/port-audio-capture.rb | 68 ++++++++ 8 files changed, 452 insertions(+) create mode 100644 bindings/Ruby/README.md create mode 100644 bindings/Ruby/ext/capture/port-audio/Makefile create mode 100755 bindings/Ruby/ext/capture/port-audio/install_portaudio.sh create mode 100644 bindings/Ruby/ext/capture/port-audio/patches/portaudio.patch create mode 100644 bindings/Ruby/ext/capture/port-audio/port-audio-capture.c create mode 100644 bindings/Ruby/ext/capture/port-audio/port-audio-capture.mk create mode 100644 bindings/Ruby/lib/snowboy.rb create mode 100644 bindings/Ruby/lib/snowboy/capture/port-audio/port-audio-capture.rb diff --git a/bindings/Ruby/README.md b/bindings/Ruby/README.md new file mode 100644 index 00000000..c96dcb73 --- /dev/null +++ b/bindings/Ruby/README.md @@ -0,0 +1,29 @@ +Ruby bindings for `snowboy` + +Dependencies +=== +snowboy shared lib +`cd ../C && make` + +Extra +=== +A simple audio capture tool is provided in `./ext/capture/port-audio` +`cd ./ext/capture/port-audio && make` + +Usage +=== +```ruby +require "./lib/snowboy" + +snowboy = Snowboy::Detect.new(resource: resource_path, model: model_path) + +# get audio data +# ... + +result = snowboy.run_detection(data, data_length, false) + +if result > 0 + # handle result +end +``` + diff --git a/bindings/Ruby/ext/capture/port-audio/Makefile b/bindings/Ruby/ext/capture/port-audio/Makefile new file mode 100644 index 00000000..a96633a7 --- /dev/null +++ b/bindings/Ruby/ext/capture/port-audio/Makefile @@ -0,0 +1,20 @@ +include port-audio-capture.mk + +SHAREDLIB = port-audio-capture.so + +OBJFILES = port-audio-capture.o + +all: $(SHAREDLIB) + +%.a: + $(MAKE) -C ${@D} ${@F} + +# We have to use the C++ compiler to link. +$(SHAREDLIB): $(PORTAUDIOLIBS) $(OBJFILES) + $(CXX) $(OBJFILES) $(PORTAUDIOLIBS) $(LDLIBS) -shared -o $(SHAREDLIB) + +$(PORTAUDIOLIBS): + @-./install_portaudio.sh + +clean: + -rm -f *.o *.a $(SHAREDLIB) $(OBJFILES) diff --git a/bindings/Ruby/ext/capture/port-audio/install_portaudio.sh b/bindings/Ruby/ext/capture/port-audio/install_portaudio.sh new file mode 100755 index 00000000..ceecd2e0 --- /dev/null +++ b/bindings/Ruby/ext/capture/port-audio/install_portaudio.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +# This script attempts to install PortAudio, which can grap a live audio stream +# from the soundcard. +# +# On linux systems, we only build with ALSA, so make sure you install it using +# e.g.: +# sudo apt-get -y install libasound2-dev + +echo "Installing portaudio" + +if [ ! -e pa_stable_v190600_20161030.tgz ]; then + wget -T 10 -t 3 \ + http://www.portaudio.com/archives/pa_stable_v190600_20161030.tgz || exit 1; +fi + +tar -xovzf pa_stable_v190600_20161030.tgz || exit 1 + +cd portaudio +patch < ../patches/portaudio.patch + +MACOS=`uname 2>/dev/null | grep Darwin` +if [ -z "$MACOS" ]; then + ./configure --without-jack --without-oss \ + --with-alsa --prefix=`pwd`/install --with-pic || exit 1; + sed -i '40s:src/common/pa_ringbuffer.o::g' Makefile + sed -i '40s:$: src/common/pa_ringbuffer.o:' Makefile +else + # People may have changed OSX's default configuration -- we use clang++. + CC=clang CXX=clang++ ./configure --prefix=`pwd`/install --with-pic +fi + +make +make install + +cd .. diff --git a/bindings/Ruby/ext/capture/port-audio/patches/portaudio.patch b/bindings/Ruby/ext/capture/port-audio/patches/portaudio.patch new file mode 100644 index 00000000..1c618329 --- /dev/null +++ b/bindings/Ruby/ext/capture/port-audio/patches/portaudio.patch @@ -0,0 +1,11 @@ +--- Makefile.in 2017-05-31 16:42:16.000000000 -0700 ++++ Makefile_new.in 2017-05-31 16:44:02.000000000 -0700 +@@ -193,6 +193,8 @@ + for include in $(INCLUDES); do \ + $(INSTALL_DATA) -m 644 $(top_srcdir)/include/$$include $(DESTDIR)$(includedir)/$$include; \ + done ++ $(INSTALL_DATA) -m 644 $(top_srcdir)/src/common/pa_ringbuffer.h $(DESTDIR)$(includedir)/$$include ++ $(INSTALL_DATA) -m 644 $(top_srcdir)/src/common/pa_util.h $(DESTDIR)$(includedir)/$$include + $(INSTALL) -d $(DESTDIR)$(libdir)/pkgconfig + $(INSTALL) -m 644 portaudio-2.0.pc $(DESTDIR)$(libdir)/pkgconfig/portaudio-2.0.pc + @echo "" diff --git a/bindings/Ruby/ext/capture/port-audio/port-audio-capture.c b/bindings/Ruby/ext/capture/port-audio/port-audio-capture.c new file mode 100644 index 00000000..4495d7df --- /dev/null +++ b/bindings/Ruby/ext/capture/port-audio/port-audio-capture.c @@ -0,0 +1,160 @@ +// bindings/Ruby/ext/capture/port-audio/port-audio-capture.c + +// Copyright 2017 KITT.AI (author: Guoguo Chen, PpiBbuRr) + +#include +#include +#include +#include +#include +#include +#include + +typedef struct { + // Pointer to the ring buffer memory. + char* ringbuffer; + // Ring buffer wrapper used in PortAudio. + PaUtilRingBuffer pa_ringbuffer; + // Pointer to PortAudio stream. + PaStream* pa_stream; + // Number of lost samples at each LoadAudioData() due to ring buffer overflow. + int num_lost_samples; + // Wait for this number of samples in each LoadAudioData() call. + int min_read_samples; + // Pointer to the audio data. + int16_t* audio_data; +} rb_snowboy_port_audio_capture_t; + +int rb_snowboy_port_audio_capture_callback(const void* input, + void* output, + unsigned long frame_count, + const PaStreamCallbackTimeInfo* time_info, + PaStreamCallbackFlags status_flags, + void* user_data) { + + rb_snowboy_port_audio_capture_t* capture = (rb_snowboy_port_audio_capture_t*) user_data; + ring_buffer_size_t num_written_samples = PaUtil_WriteRingBuffer(&capture->pa_ringbuffer, input, frame_count); + capture->num_lost_samples += frame_count - num_written_samples; + return paContinue; +} + +void rb_snowboy_port_audio_capture_start_audio_capturing(rb_snowboy_port_audio_capture_t* capture, int sample_rate, + int num_channels, int bits_per_sample) { + capture->audio_data = NULL; + capture->num_lost_samples = 0; + capture->min_read_samples = sample_rate * 0.1; + + // Allocates ring buffer memory. + int ringbuffer_size = 16384; + capture->ringbuffer = (char*)( + PaUtil_AllocateMemory(bits_per_sample / 8 * ringbuffer_size)); + if (capture->ringbuffer == NULL) { + fprintf(stderr, "Fail to allocate memory for ring buffer.\n"); + exit(1); + } + + // Initializes PortAudio ring buffer. + ring_buffer_size_t rb_init_ans = + PaUtil_InitializeRingBuffer(&capture->pa_ringbuffer, bits_per_sample / 8, + ringbuffer_size, capture->ringbuffer); + if (rb_init_ans == -1) { + fprintf(stderr, "Ring buffer size is not power of 2.\n"); + exit(1); + } + + // Initializes PortAudio. + PaError pa_init_ans = Pa_Initialize(); + if (pa_init_ans != paNoError) { + fprintf(stderr, "Fail to initialize PortAudio, error message is %s.\n", + Pa_GetErrorText(pa_init_ans)); + exit(1); + } + + PaError pa_open_ans; + if (bits_per_sample == 8) { + pa_open_ans = Pa_OpenDefaultStream( + &capture->pa_stream, num_channels, 0, paUInt8, sample_rate, + paFramesPerBufferUnspecified, rb_snowboy_port_audio_capture_callback, capture); + } else if (bits_per_sample == 16) { + pa_open_ans = Pa_OpenDefaultStream( + &capture->pa_stream, num_channels, 0, paInt16, sample_rate, + paFramesPerBufferUnspecified, rb_snowboy_port_audio_capture_callback, capture); + } else if (bits_per_sample == 32) { + pa_open_ans = Pa_OpenDefaultStream( + &capture->pa_stream, num_channels, 0, paInt32, sample_rate, + paFramesPerBufferUnspecified, rb_snowboy_port_audio_capture_callback, capture); + } else { + fprintf(stderr, "Unsupported BitsPerSample: %d.\n", bits_per_sample); + exit(1); + } + if (pa_open_ans != paNoError) { + fprintf(stderr, "Fail to open PortAudio stream, error message is %s.\n", + Pa_GetErrorText(pa_open_ans)); + exit(1); + } + + PaError pa_stream_start_ans = Pa_StartStream(capture->pa_stream); + if (pa_stream_start_ans != paNoError) { + fprintf(stderr, "Fail to start PortAudio stream, error message is %s.\n", + Pa_GetErrorText(pa_stream_start_ans)); + exit(1); + } +} + +void rb_snowboy_port_audio_capture_stop_audio_capturing(rb_snowboy_port_audio_capture_t* capture) { + if (capture->audio_data != NULL) { + free(capture->audio_data); + capture->audio_data = NULL; + } + Pa_StopStream(capture->pa_stream); + Pa_CloseStream(capture->pa_stream); + Pa_Terminate(); + PaUtil_FreeMemory(capture->ringbuffer); +} + +int rb_snowboy_port_audio_capture_load_audio_data(rb_snowboy_port_audio_capture_t* capture) { + if (capture->audio_data != NULL) { + free(capture->audio_data); + capture->audio_data = NULL; + } + + // Checks ring buffer overflow. + if (capture->num_lost_samples > 0) { + fprintf(stderr, "Lost %d samples due to ring buffer overflow.\n", + capture->num_lost_samples); + capture->num_lost_samples = 0; + } + + ring_buffer_size_t num_available_samples = 0; + while (true) { + num_available_samples = + PaUtil_GetRingBufferReadAvailable(&capture->pa_ringbuffer); + if (num_available_samples >= capture->min_read_samples) { + break; + } + Pa_Sleep(5); + } + + // Reads data. + num_available_samples = PaUtil_GetRingBufferReadAvailable(&capture->pa_ringbuffer); + capture->audio_data = malloc(num_available_samples * sizeof(int16_t)); + ring_buffer_size_t num_read_samples = PaUtil_ReadRingBuffer( + &capture->pa_ringbuffer, capture->audio_data, num_available_samples); + if (num_read_samples != num_available_samples) { + fprintf(stderr, "%d samples were available, but only %d samples were read" + ".\n", num_available_samples, num_read_samples); + } + return num_read_samples; +} + +rb_snowboy_port_audio_capture_t* rb_snowboy_port_audio_capture_new() { + rb_snowboy_port_audio_capture_t* capture = malloc(sizeof(rb_snowboy_port_audio_capture_t)); + + memset(capture, 0, sizeof(rb_snowboy_port_audio_capture_t)); + + return capture; +} + +int16_t* rb_snowboy_port_audio_capture_get_audio_data(rb_snowboy_port_audio_capture_t* capture) { + return capture->audio_data; +} diff --git a/bindings/Ruby/ext/capture/port-audio/port-audio-capture.mk b/bindings/Ruby/ext/capture/port-audio/port-audio-capture.mk new file mode 100644 index 00000000..79a6154d --- /dev/null +++ b/bindings/Ruby/ext/capture/port-audio/port-audio-capture.mk @@ -0,0 +1,53 @@ +DYNAMIC := True +CC := +CXX := +LDFLAGS := +LDLIBS := +PORTAUDIOINC := portaudio/install/include +PORTAUDIOLIBS := portaudio/install/lib/libportaudio.a + +CFLAGS := +CXXFLAGS += -D_GLIBCXX_USE_CXX11_ABI=0 + +ifeq ($(DYNAMIC), True) + CFLAGS += -fPIC + CXXFLAGS += -fPIC +endif + +ifeq ($(shell uname -m | cut -c 1-3), x86) + CFLAGS += -msse -msse2 + CXXFLAGS += -msse -msse2 +endif + +ifeq ($(shell uname), Darwin) + # By default Mac uses clang++ as g++, but people may have changed their + # default configuration. + CC := clang + CXX := clang++ + CFLAGS += -I$(TOPDIR) -Wall -I$(PORTAUDIOINC) + CXXFLAGS += -I$(TOPDIR) -Wall -Wno-sign-compare -Winit-self \ + -DHAVE_POSIX_MEMALIGN -DHAVE_CLAPACK -I$(PORTAUDIOINC) + LDLIBS += -ldl -lm -framework Accelerate -framework CoreAudio \ + -framework AudioToolbox -framework AudioUnit -framework CoreServices \ + $(PORTAUDIOLIBS) +else ifeq ($(shell uname), Linux) + CC := gcc + CXX := g++ + CFLAGS += -I$(TOPDIR) -Wall -I$(PORTAUDIOINC) + CXXFLAGS += -I$(TOPDIR) -std=c++0x -Wall -Wno-sign-compare \ + -Wno-unused-local-typedefs -Winit-self -rdynamic \ + -DHAVE_POSIX_MEMALIGN -I$(PORTAUDIOINC) + LDLIBS += -ldl -lm -Wl,-Bstatic -Wl,-Bdynamic -lrt -lpthread $(PORTAUDIOLIBS) -lasound\ + #ifneq (,$(findstring arm,$(shell uname -m))) + #endif +endif + +# Suppress clang warnings... +COMPILER = $(shell $(CXX) -v 2>&1 ) +ifeq ($(findstring clang,$(COMPILER)), clang) + CXXFLAGS += -Wno-mismatched-tags -Wno-c++11-extensions +endif + +# Set optimization level. +CFLAGS += -O3 +CXXFLAGS += -O3 diff --git a/bindings/Ruby/lib/snowboy.rb b/bindings/Ruby/lib/snowboy.rb new file mode 100644 index 00000000..6e8bc9e5 --- /dev/null +++ b/bindings/Ruby/lib/snowboy.rb @@ -0,0 +1,75 @@ +$: << BASE_PATH=File.dirname(__FILE__) + +require 'ffi' + +module Snowboy + module Lib + extend FFI::Library + + so_path = File.expand_path(File.join(BASE_PATH, '..', '..', 'C', 'libsnowboydetect.so')) + + if File.exist?(so_path) + ffi_lib so_path + else + ffi_lib "libsnowboydetect" + end + + attach_function :SnowboyDetectConstructor, [:string, :string], :pointer + attach_function :SnowboyDetectBitsPerSample, [:pointer], :int + attach_function :SnowboyDetectSampleRate, [:pointer], :int + attach_function :SnowboyDetectNumChannels, [:pointer], :int + attach_function :SnowboyDetectNumHotwords, [:pointer], :int + attach_function :SnowboyDetectReset, [:pointer], :bool + attach_function :SnowboyDetectRunDetection, [:pointer, :pointer, :int, :bool], :int + attach_function :SnowboyDetectSetAudioGain, [:pointer, :float], :void + attach_function :SnowboyDetectSetSensitivity, [:pointer, :string], :void + end + + class Detector + attr_reader :resource, :model, :sensitivity, :audio_gain, :ptr + def initialize resource: nil, model: nil, sensitivity: 0.5, gain: 1 + @ptr = Lib::SnowboyDetectConstructor(resource, model) + + @resource = resource + @model = model + + self.sensitivity = sensitivity; + self.audio_gain = gain; + end + + def audio_gain= gain + @audio_gain = gain + + Lib::SnowboyDetectSetAudioGain(ptr, audio_gain) + end + + def sensitivity= lvl + Lib::SnowboyDetectSetSensitivity(ptr, lvl.to_s) + end + + def sample_rate + Lib::SnowboyDetectSampleRate(ptr) + end + + def bits_per_sample + Lib::SnowboyDetectBitsPerSample(ptr) + end + + def n_channels + Lib::SnowboyDetectNumChannels(ptr) + end + + def n_hotwords + Lib::SnowboyDetectNumHotwords(ptr) + end + + def reset + Lib::SnowboyDetectReset(ptr) + end + + def run_detection *o + Lib::SnowboyDetectRunDetection(ptr, *o) + end + end +end + diff --git a/bindings/Ruby/lib/snowboy/capture/port-audio/port-audio-capture.rb b/bindings/Ruby/lib/snowboy/capture/port-audio/port-audio-capture.rb new file mode 100644 index 00000000..07ce722a --- /dev/null +++ b/bindings/Ruby/lib/snowboy/capture/port-audio/port-audio-capture.rb @@ -0,0 +1,68 @@ +module Snowboy + module Capture + class PortAudio + module Lib + extend FFI::Library + + ffi_lib File.expand_path(File.join(File.dirname(__FILE__), "..", '..', '..', '..', 'ext', 'capture', 'port-audio', 'port-audio-capture.so')) + + attach_function :rb_snowboy_port_audio_capture_start_audio_capturing, [:pointer, :int, :int, :int], :void + attach_function :rb_snowboy_port_audio_capture_stop_audio_capturing, [:pointer], :void + attach_function :rb_snowboy_port_audio_capture_load_audio_data, [:pointer], :int + attach_function :rb_snowboy_port_audio_capture_get_audio_data, [:pointer], :pointer + attach_function :rb_snowboy_port_audio_capture_new, [], :pointer + end + + attr_reader :ptr + def initialize + @ptr = Lib.rb_snowboy_port_audio_capture_new() + end + + def start_capture(rate, channels, bps) + Lib.rb_snowboy_port_audio_capture_start_audio_capturing(@ptr, rate, channels, bps) + end + + def stop_capture + @running = false + Lib.rb_snowboy_port_audio_capture_stop_audio_capturing(@ptr) + end + + def load_audio_data + Lib.rb_snowboy_port_audio_capture_load_audio_data(@ptr) + end + + def get_audio_data + Lib.rb_snowboy_port_audio_capture_get_audio_data(@ptr) + end + + attr_accessor :thread + def run(rate, channels, bps, &b) + raise ArgumentError.new("No Block passed") unless b + + @running = true + + start_capture(rate, channels, bps) + + @thread = Thread.new do + begin + while running? + if (len=load_audio_data) > 0 + b.call(get_audio_data, len) + end + end + + stop_capture + rescue => e + puts e + puts e.backtrace.join("\n") + stop_capture + end + end + end + + def running? + @running + end + end + end +end From 39ef8bce2ca3127b3ce461473f3206e134c579f8 Mon Sep 17 00:00:00 2001 From: ppibburr Date: Fri, 23 Mar 2018 23:36:59 -0400 Subject: [PATCH 07/13] ruby readme format --- bindings/Ruby/README.md | 4 ++-- examples/Ruby/README.md | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/bindings/Ruby/README.md b/bindings/Ruby/README.md index c96dcb73..8176a23a 100644 --- a/bindings/Ruby/README.md +++ b/bindings/Ruby/README.md @@ -2,12 +2,12 @@ Ruby bindings for `snowboy` Dependencies === -snowboy shared lib +snowboy shared lib `cd ../C && make` Extra === -A simple audio capture tool is provided in `./ext/capture/port-audio` +A simple audio capture tool is provided in `./ext/capture/port-audio` `cd ./ext/capture/port-audio && make` Usage diff --git a/examples/Ruby/README.md b/examples/Ruby/README.md index 179312f8..1a7c902e 100644 --- a/examples/Ruby/README.md +++ b/examples/Ruby/README.md @@ -2,10 +2,11 @@ Sample program to detect hotword. Dependencies === -snowboy shared lib +snowboy shared lib `cd ../../bindings/C && make` -This sample uses portaudio to capture from a library is provied in `../../bindings/Ruby/ext/capture/port-audio` + +This sample uses portaudio to capture from a library is provied in `../../bindings/Ruby/ext/capture/port-audio` `cd ../../bindings/Ruby/ext/capture/port-audio && make` Usage From 1e30c174fdc6fc89d249226891cb1a002026eafe Mon Sep 17 00:00:00 2001 From: ppibburr Date: Fri, 23 Mar 2018 23:41:49 -0400 Subject: [PATCH 08/13] remove old files --- examples/Ruby/port_audio_detect.c | 149 ------------------------------ examples/Ruby/ruby.mk | 58 ------------ 2 files changed, 207 deletions(-) delete mode 100644 examples/Ruby/port_audio_detect.c delete mode 100644 examples/Ruby/ruby.mk diff --git a/examples/Ruby/port_audio_detect.c b/examples/Ruby/port_audio_detect.c deleted file mode 100644 index f1815c28..00000000 --- a/examples/Ruby/port_audio_detect.c +++ /dev/null @@ -1,149 +0,0 @@ -// example/Ruby/port_audio_detect.c - -// Copyright 2017 KITT.AI (author: Guoguo Chen, PpiBbuRr) - -#include -#include -#include -#include -#include -#include -#include - -#include "snowboy-detect-c-wrapper.h" - -// Pointer to the ring buffer memory. -char* g_ringbuffer; -// Ring buffer wrapper used in PortAudio. -PaUtilRingBuffer g_pa_ringbuffer; -// Pointer to PortAudio stream. -PaStream* g_pa_stream; -// Number of lost samples at each LoadAudioData() due to ring buffer overflow. -int g_num_lost_samples; -// Wait for this number of samples in each LoadAudioData() call. -int g_min_read_samples; -// Pointer to the audio data. -int16_t* g_data; - -int PortAudioCallback(const void* input, - void* output, - unsigned long frame_count, - const PaStreamCallbackTimeInfo* time_info, - PaStreamCallbackFlags status_flags, - void* user_data) { - ring_buffer_size_t num_written_samples = - PaUtil_WriteRingBuffer(&g_pa_ringbuffer, input, frame_count); - g_num_lost_samples += frame_count - num_written_samples; - return paContinue; -} - -void StartAudioCapturing(int sample_rate, - int num_channels, int bits_per_sample) { - g_data = NULL; - g_num_lost_samples = 0; - g_min_read_samples = sample_rate * 0.1; - - // Allocates ring buffer memory. - int ringbuffer_size = 16384; - g_ringbuffer = (char*)( - PaUtil_AllocateMemory(bits_per_sample / 8 * ringbuffer_size)); - if (g_ringbuffer == NULL) { - fprintf(stderr, "Fail to allocate memory for ring buffer.\n"); - exit(1); - } - - // Initializes PortAudio ring buffer. - ring_buffer_size_t rb_init_ans = - PaUtil_InitializeRingBuffer(&g_pa_ringbuffer, bits_per_sample / 8, - ringbuffer_size, g_ringbuffer); - if (rb_init_ans == -1) { - fprintf(stderr, "Ring buffer size is not power of 2.\n"); - exit(1); - } - - // Initializes PortAudio. - PaError pa_init_ans = Pa_Initialize(); - if (pa_init_ans != paNoError) { - fprintf(stderr, "Fail to initialize PortAudio, error message is %s.\n", - Pa_GetErrorText(pa_init_ans)); - exit(1); - } - - PaError pa_open_ans; - if (bits_per_sample == 8) { - pa_open_ans = Pa_OpenDefaultStream( - &g_pa_stream, num_channels, 0, paUInt8, sample_rate, - paFramesPerBufferUnspecified, PortAudioCallback, NULL); - } else if (bits_per_sample == 16) { - pa_open_ans = Pa_OpenDefaultStream( - &g_pa_stream, num_channels, 0, paInt16, sample_rate, - paFramesPerBufferUnspecified, PortAudioCallback, NULL); - } else if (bits_per_sample == 32) { - pa_open_ans = Pa_OpenDefaultStream( - &g_pa_stream, num_channels, 0, paInt32, sample_rate, - paFramesPerBufferUnspecified, PortAudioCallback, NULL); - } else { - fprintf(stderr, "Unsupported BitsPerSample: %d.\n", bits_per_sample); - exit(1); - } - if (pa_open_ans != paNoError) { - fprintf(stderr, "Fail to open PortAudio stream, error message is %s.\n", - Pa_GetErrorText(pa_open_ans)); - exit(1); - } - - PaError pa_stream_start_ans = Pa_StartStream(g_pa_stream); - if (pa_stream_start_ans != paNoError) { - fprintf(stderr, "Fail to start PortAudio stream, error message is %s.\n", - Pa_GetErrorText(pa_stream_start_ans)); - exit(1); - } -} - -void StopAudioCapturing() { - if (g_data != NULL) { - free(g_data); - g_data = NULL; - } - Pa_StopStream(g_pa_stream); - Pa_CloseStream(g_pa_stream); - Pa_Terminate(); - PaUtil_FreeMemory(g_ringbuffer); -} - -int LoadAudioData() { - if (g_data != NULL) { - free(g_data); - g_data = NULL; - } - - // Checks ring buffer overflow. - if (g_num_lost_samples > 0) { - fprintf(stderr, "Lost %d samples due to ring buffer overflow.\n", - g_num_lost_samples); - g_num_lost_samples = 0; - } - - ring_buffer_size_t num_available_samples = 0; - while (true) { - num_available_samples = - PaUtil_GetRingBufferReadAvailable(&g_pa_ringbuffer); - if (num_available_samples >= g_min_read_samples) { - break; - } - Pa_Sleep(5); - } - - // Reads data. - num_available_samples = PaUtil_GetRingBufferReadAvailable(&g_pa_ringbuffer); - g_data = malloc(num_available_samples * sizeof(int16_t)); - ring_buffer_size_t num_read_samples = PaUtil_ReadRingBuffer( - &g_pa_ringbuffer, g_data, num_available_samples); - if (num_read_samples != num_available_samples) { - fprintf(stderr, "%d samples were available, but only %d samples were read" - ".\n", num_available_samples, num_read_samples); - } - return num_read_samples; -} - - diff --git a/examples/Ruby/ruby.mk b/examples/Ruby/ruby.mk deleted file mode 100644 index 73d1140c..00000000 --- a/examples/Ruby/ruby.mk +++ /dev/null @@ -1,58 +0,0 @@ -TOPDIR := ../../ -DYNAMIC := True -CC := -CXX := -LDFLAGS := -LDLIBS := -PORTAUDIOINC := portaudio/install/include -PORTAUDIOLIBS := portaudio/install/lib/libportaudio.a - -CFLAGS := -CXXFLAGS += -D_GLIBCXX_USE_CXX11_ABI=0 - -ifeq ($(DYNAMIC), True) - CFLAGS += -fPIC - CXXFLAGS += -fPIC -endif - -ifeq ($(shell uname -m | cut -c 1-3), x86) - CFLAGS += -msse -msse2 - CXXFLAGS += -msse -msse2 -endif - -ifeq ($(shell uname), Darwin) - # By default Mac uses clang++ as g++, but people may have changed their - # default configuration. - CC := clang - CXX := clang++ - CFLAGS += -I$(TOPDIR) -Wall -I$(PORTAUDIOINC) - CXXFLAGS += -I$(TOPDIR) -Wall -Wno-sign-compare -Winit-self \ - -DHAVE_POSIX_MEMALIGN -DHAVE_CLAPACK -I$(PORTAUDIOINC) - LDLIBS += -ldl -lm -framework Accelerate -framework CoreAudio \ - -framework AudioToolbox -framework AudioUnit -framework CoreServices \ - $(PORTAUDIOLIBS) - SNOWBOYDETECTLIBFILE := $(TOPDIR)/lib/osx/libsnowboy-detect.a -else ifeq ($(shell uname), Linux) - CC := gcc - CXX := g++ - CFLAGS += -I$(TOPDIR) -Wall -I$(PORTAUDIOINC) - CXXFLAGS += -I$(TOPDIR) -std=c++0x -Wall -Wno-sign-compare \ - -Wno-unused-local-typedefs -Winit-self -rdynamic \ - -DHAVE_POSIX_MEMALIGN -I$(PORTAUDIOINC) - LDLIBS += -ldl -lm -Wl,-Bstatic -Wl,-Bdynamic -lrt -lpthread $(PORTAUDIOLIBS)\ - -L/usr/lib/atlas-base -lf77blas -lcblas -llapack_atlas -latlas -lasound - SNOWBOYDETECTLIBFILE := $(TOPDIR)/lib/ubuntu64/libsnowboy-detect.a - ifneq (,$(findstring arm,$(shell uname -m))) - SNOWBOYDETECTLIBFILE := $(TOPDIR)/lib/rpi/libsnowboy-detect.a - endif -endif - -# Suppress clang warnings... -COMPILER = $(shell $(CXX) -v 2>&1 ) -ifeq ($(findstring clang,$(COMPILER)), clang) - CXXFLAGS += -Wno-mismatched-tags -Wno-c++11-extensions -endif - -# Set optimization level. -CFLAGS += -O3 -CXXFLAGS += -O3 From 1967d8da1e55edc59d8c60f5c1b9dc26da9efec2 Mon Sep 17 00:00:00 2001 From: ppibburr Date: Mon, 26 Mar 2018 18:21:08 -0400 Subject: [PATCH 09/13] add ffi gem as dependency to ruby binding --- bindings/Ruby/README.md | 3 +++ examples/Ruby/README.md | 3 +++ 2 files changed, 6 insertions(+) diff --git a/bindings/Ruby/README.md b/bindings/Ruby/README.md index 8176a23a..3704d591 100644 --- a/bindings/Ruby/README.md +++ b/bindings/Ruby/README.md @@ -5,6 +5,9 @@ Dependencies snowboy shared lib `cd ../C && make` +FFI for ruby. +`sudo gem i ffi` + Extra === A simple audio capture tool is provided in `./ext/capture/port-audio` diff --git a/examples/Ruby/README.md b/examples/Ruby/README.md index 1a7c902e..cb99750b 100644 --- a/examples/Ruby/README.md +++ b/examples/Ruby/README.md @@ -5,6 +5,9 @@ Dependencies snowboy shared lib `cd ../../bindings/C && make` +FFI for ruby. +`sudo gem i ffi` + This sample uses portaudio to capture from a library is provied in `../../bindings/Ruby/ext/capture/port-audio` `cd ../../bindings/Ruby/ext/capture/port-audio && make` From 2289c929a2480ce99603a7444ec4b767bc41aa7c Mon Sep 17 00:00:00 2001 From: ppibburr Date: Thu, 29 Mar 2018 02:03:09 -0400 Subject: [PATCH 10/13] add missing apply_frontend & update_model methods to ruby binding --- bindings/Ruby/lib/snowboy.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bindings/Ruby/lib/snowboy.rb b/bindings/Ruby/lib/snowboy.rb index 6e8bc9e5..43618855 100644 --- a/bindings/Ruby/lib/snowboy.rb +++ b/bindings/Ruby/lib/snowboy.rb @@ -20,6 +20,8 @@ module Lib attach_function :SnowboyDetectNumChannels, [:pointer], :int attach_function :SnowboyDetectNumHotwords, [:pointer], :int attach_function :SnowboyDetectReset, [:pointer], :bool + attach_function :SnowboyDetectUpdateModel, [:pointer], :void + attach_function :SnowboyDetectApplyFrontend, [:pointer, :bool], :void attach_function :SnowboyDetectRunDetection, [:pointer, :pointer, :int, :bool], :int attach_function :SnowboyDetectSetAudioGain, [:pointer, :float], :void attach_function :SnowboyDetectSetSensitivity, [:pointer, :string], :void @@ -67,6 +69,14 @@ def reset Lib::SnowboyDetectReset(ptr) end + def apply_frontend bool + Lib::SnowboyDetectApplyFrontend(ptr, bool) + end + + def update_model + Lib::SnowboyDetectUpdateModel(ptr) + end + def run_detection *o Lib::SnowboyDetectRunDetection(ptr, *o) end From 6803173408621a872a5f4b2d4e2bcd02ba466ee8 Mon Sep 17 00:00:00 2001 From: ppibburr Date: Mon, 2 Apr 2018 19:51:12 -0400 Subject: [PATCH 11/13] Support Multiple models in ruby binding --- bindings/Ruby/lib/snowboy.rb | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/bindings/Ruby/lib/snowboy.rb b/bindings/Ruby/lib/snowboy.rb index 43618855..557618ee 100644 --- a/bindings/Ruby/lib/snowboy.rb +++ b/bindings/Ruby/lib/snowboy.rb @@ -29,7 +29,12 @@ module Lib class Detector attr_reader :resource, :model, :sensitivity, :audio_gain, :ptr + + # @param +model+ pointing to the model file(s) + # def initialize resource: nil, model: nil, sensitivity: 0.5, gain: 1 + model = value2str(model) + @ptr = Lib::SnowboyDetectConstructor(resource, model) @resource = resource @@ -45,8 +50,12 @@ def audio_gain= gain Lib::SnowboyDetectSetAudioGain(ptr, audio_gain) end + # @param +lvl+ specifying levels per model + # def sensitivity= lvl - Lib::SnowboyDetectSetSensitivity(ptr, lvl.to_s) + @sensitivity = o=value2str(lvl) + + Lib::SnowboyDetectSetSensitivity(ptr, o) end def sample_rate @@ -80,6 +89,21 @@ def update_model def run_detection *o Lib::SnowboyDetectRunDetection(ptr, *o) end + + private + def value2str obj + if obj.is_a?(String) + obj + elsif obj.is_a?(Array) + obj.map do |o| + o.to_s + end.join(",") + elsif obj.is_a?(Numeric) + obj.to_s + else + obj.to_s + end + end end end From 9703b301973de344aab3122a1d53e7fe64f9bba0 Mon Sep 17 00:00:00 2001 From: ppibburr Date: Fri, 6 Apr 2018 21:38:16 -0400 Subject: [PATCH 12/13] move ruby bindings to 'swig', use symlinks --- bindings/C/Makefile | 17 -- bindings/C/libsnowboydetect.mk | 55 ------ bindings/C/snowboy-detect-c-wrapper.cc | 82 --------- bindings/C/snowboy-detect-c-wrapper.h | 51 ------ bindings/Ruby/README.md | 32 ---- bindings/Ruby/ext/capture/port-audio/Makefile | 20 --- .../capture/port-audio/install_portaudio.sh | 36 ---- .../port-audio/patches/portaudio.patch | 11 -- .../capture/port-audio/port-audio-capture.c | 160 ------------------ .../capture/port-audio/port-audio-capture.mk | 53 ------ bindings/Ruby/lib/snowboy.rb | 109 ------------ .../capture/port-audio/port-audio-capture.rb | 68 -------- examples/Ruby/README.md | 17 -- examples/Ruby/port-audio-sample.rb | 23 --- 14 files changed, 734 deletions(-) delete mode 100644 bindings/C/Makefile delete mode 100644 bindings/C/libsnowboydetect.mk delete mode 100644 bindings/C/snowboy-detect-c-wrapper.cc delete mode 100644 bindings/C/snowboy-detect-c-wrapper.h delete mode 100644 bindings/Ruby/README.md delete mode 100644 bindings/Ruby/ext/capture/port-audio/Makefile delete mode 100755 bindings/Ruby/ext/capture/port-audio/install_portaudio.sh delete mode 100644 bindings/Ruby/ext/capture/port-audio/patches/portaudio.patch delete mode 100644 bindings/Ruby/ext/capture/port-audio/port-audio-capture.c delete mode 100644 bindings/Ruby/ext/capture/port-audio/port-audio-capture.mk delete mode 100644 bindings/Ruby/lib/snowboy.rb delete mode 100644 bindings/Ruby/lib/snowboy/capture/port-audio/port-audio-capture.rb delete mode 100644 examples/Ruby/README.md delete mode 100644 examples/Ruby/port-audio-sample.rb diff --git a/bindings/C/Makefile b/bindings/C/Makefile deleted file mode 100644 index fe7e4667..00000000 --- a/bindings/C/Makefile +++ /dev/null @@ -1,17 +0,0 @@ -include libsnowboydetect.mk - -SHAREDLIB = libsnowboydetect.so - -OBJFILES = snowboy-detect-c-wrapper.o - -all: $(SHAREDLIB) - -%.a: - $(MAKE) -C ${@D} ${@F} - -# We have to use the C++ compiler to link. -$(SHAREDLIB): $(SNOWBOYDETECTLIBFILE) $(OBJFILES) - $(CXX) $(OBJFILES) $(SNOWBOYDETECTLIBFILE) $(LDLIBS) -shared -o $(SHAREDLIB) - -clean: - -rm -f *.o *.a $(SHAREDLIB) $(OBJFILES) diff --git a/bindings/C/libsnowboydetect.mk b/bindings/C/libsnowboydetect.mk deleted file mode 100644 index cc2450b6..00000000 --- a/bindings/C/libsnowboydetect.mk +++ /dev/null @@ -1,55 +0,0 @@ -TOPDIR := ../../ -DYNAMIC := True -CC := -CXX := -LDFLAGS := -LDLIBS := - -CFLAGS := -CXXFLAGS += -D_GLIBCXX_USE_CXX11_ABI=0 - -ifeq ($(DYNAMIC), True) - CFLAGS += -fPIC - CXXFLAGS += -fPIC -endif - -ifeq ($(shell uname -m | cut -c 1-3), x86) - CFLAGS += -msse -msse2 - CXXFLAGS += -msse -msse2 -endif - -ifeq ($(shell uname), Darwin) - # By default Mac uses clang++ as g++, but people may have changed their - # default configuration. - CC := clang - CXX := clang++ - CFLAGS += -I$(TOPDIR) -Wall -I$(PORTAUDIOINC) - CXXFLAGS += -I$(TOPDIR) -Wall -Wno-sign-compare -Winit-self \ - -DHAVE_POSIX_MEMALIGN -DHAVE_CLAPACK - LDLIBS += -ldl -lm -framework Accelerate -framework CoreAudio \ - -framework AudioToolbox -framework AudioUnit -framework CoreServices - SNOWBOYDETECTLIBFILE := $(TOPDIR)/lib/osx/libsnowboy-detect.a -else ifeq ($(shell uname), Linux) - CC := gcc - CXX := g++ - CFLAGS += -I$(TOPDIR) -Wall - CXXFLAGS += -I$(TOPDIR) -std=c++0x -Wall -Wno-sign-compare \ - -Wno-unused-local-typedefs -Winit-self -rdynamic \ - -DHAVE_POSIX_MEMALIGN - LDLIBS += -ldl -lm -Wl,-Bstatic -Wl,-Bdynamic -lrt -lpthread\ - -L/usr/lib/atlas-base -lf77blas -lcblas -llapack_atlas -latlas - SNOWBOYDETECTLIBFILE := $(TOPDIR)/lib/ubuntu64/libsnowboy-detect.a - ifneq (,$(findstring arm,$(shell uname -m))) - SNOWBOYDETECTLIBFILE := $(TOPDIR)/lib/rpi/libsnowboy-detect.a - endif -endif - -# Suppress clang warnings... -COMPILER = $(shell $(CXX) -v 2>&1 ) -ifeq ($(findstring clang,$(COMPILER)), clang) - CXXFLAGS += -Wno-mismatched-tags -Wno-c++11-extensions -endif - -# Set optimization level. -CFLAGS += -O3 -CXXFLAGS += -O3 diff --git a/bindings/C/snowboy-detect-c-wrapper.cc b/bindings/C/snowboy-detect-c-wrapper.cc deleted file mode 100644 index 9129271a..00000000 --- a/bindings/C/snowboy-detect-c-wrapper.cc +++ /dev/null @@ -1,82 +0,0 @@ -// snowboy-detect-c-wrapper.cc - -// Copyright 2017 KITT.AI (author: Guoguo Chen) - -#include - -#include "snowboy-detect-c-wrapper.h" -#include "include/snowboy-detect.h" - -extern "C" { - SnowboyDetect* SnowboyDetectConstructor(const char* const resource_filename, - const char* const model_str) { - return reinterpret_cast( - new snowboy::SnowboyDetect(resource_filename, model_str)); - } - - bool SnowboyDetectReset(SnowboyDetect* detector) { - assert(detector != NULL); - return reinterpret_cast(detector)->Reset(); - } - - int SnowboyDetectRunDetection(SnowboyDetect* detector, - const int16_t* const data, - const int array_length, bool is_end) { - assert(detector != NULL); - assert(data != NULL); - return reinterpret_cast( - detector)->RunDetection(data, array_length, is_end); - } - - void SnowboyDetectSetSensitivity(SnowboyDetect* detector, - const char* const sensitivity_str) { - assert(detector != NULL); - reinterpret_cast( - detector)->SetSensitivity(sensitivity_str); - } - - void SnowboyDetectSetAudioGain(SnowboyDetect* detector, - const float audio_gain) { - assert(detector != NULL); - reinterpret_cast( - detector)->SetAudioGain(audio_gain); - } - - void SnowboyDetectUpdateModel(SnowboyDetect* detector) { - assert(detector != NULL); - reinterpret_cast(detector)->UpdateModel(); - } - - void SnowboyDetectApplyFrontend(SnowboyDetect* detector, - const bool apply_frontend) { - assert(detector != NULL); - reinterpret_cast( - detector)->ApplyFrontend(apply_frontend); - } - - int SnowboyDetectNumHotwords(SnowboyDetect* detector) { - assert(detector != NULL); - return reinterpret_cast(detector)->NumHotwords(); - } - - int SnowboyDetectSampleRate(SnowboyDetect* detector) { - assert(detector != NULL); - return reinterpret_cast(detector)->SampleRate(); - } - - int SnowboyDetectNumChannels(SnowboyDetect* detector) { - assert(detector != NULL); - return reinterpret_cast(detector)->NumChannels(); - } - - int SnowboyDetectBitsPerSample(SnowboyDetect* detector) { - assert(detector != NULL); - return reinterpret_cast(detector)->BitsPerSample(); - } - - void SnowboyDetectDestructor(SnowboyDetect* detector) { - assert(detector != NULL); - delete reinterpret_cast(detector); - detector = NULL; - } -} diff --git a/bindings/C/snowboy-detect-c-wrapper.h b/bindings/C/snowboy-detect-c-wrapper.h deleted file mode 100644 index 99a4e02a..00000000 --- a/bindings/C/snowboy-detect-c-wrapper.h +++ /dev/null @@ -1,51 +0,0 @@ -// snowboy-detect-c-wrapper.h - -// Copyright 2017 KITT.AI (author: Guoguo Chen) - -#ifndef SNOWBOY_DETECT_C_WRAPPER_H_ -#define SNOWBOY_DETECT_C_WRAPPER_H_ - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - - typedef struct SnowboyDetect SnowboyDetect; - - SnowboyDetect* SnowboyDetectConstructor(const char* const resource_filename, - const char* const model_str); - - bool SnowboyDetectReset(SnowboyDetect* detector); - - int SnowboyDetectRunDetection(SnowboyDetect* detector, - const int16_t* const data, - const int array_length, bool is_end); - - void SnowboyDetectSetSensitivity(SnowboyDetect* detector, - const char* const sensitivity_str); - - void SnowboyDetectSetAudioGain(SnowboyDetect* detector, - const float audio_gain); - - void SnowboyDetectUpdateModel(SnowboyDetect* detector); - - void SnowboyDetectApplyFrontend(SnowboyDetect* detector, - const bool apply_frontend); - - int SnowboyDetectNumHotwords(SnowboyDetect* detector); - - int SnowboyDetectSampleRate(SnowboyDetect* detector); - - int SnowboyDetectNumChannels(SnowboyDetect* detector); - - int SnowboyDetectBitsPerSample(SnowboyDetect* detector); - - void SnowboyDetectDestructor(SnowboyDetect* detector); - -#ifdef __cplusplus -} -#endif - -#endif // SNOWBOY_DETECT_C_WRAPPER_H_ diff --git a/bindings/Ruby/README.md b/bindings/Ruby/README.md deleted file mode 100644 index 3704d591..00000000 --- a/bindings/Ruby/README.md +++ /dev/null @@ -1,32 +0,0 @@ -Ruby bindings for `snowboy` - -Dependencies -=== -snowboy shared lib -`cd ../C && make` - -FFI for ruby. -`sudo gem i ffi` - -Extra -=== -A simple audio capture tool is provided in `./ext/capture/port-audio` -`cd ./ext/capture/port-audio && make` - -Usage -=== -```ruby -require "./lib/snowboy" - -snowboy = Snowboy::Detect.new(resource: resource_path, model: model_path) - -# get audio data -# ... - -result = snowboy.run_detection(data, data_length, false) - -if result > 0 - # handle result -end -``` - diff --git a/bindings/Ruby/ext/capture/port-audio/Makefile b/bindings/Ruby/ext/capture/port-audio/Makefile deleted file mode 100644 index a96633a7..00000000 --- a/bindings/Ruby/ext/capture/port-audio/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -include port-audio-capture.mk - -SHAREDLIB = port-audio-capture.so - -OBJFILES = port-audio-capture.o - -all: $(SHAREDLIB) - -%.a: - $(MAKE) -C ${@D} ${@F} - -# We have to use the C++ compiler to link. -$(SHAREDLIB): $(PORTAUDIOLIBS) $(OBJFILES) - $(CXX) $(OBJFILES) $(PORTAUDIOLIBS) $(LDLIBS) -shared -o $(SHAREDLIB) - -$(PORTAUDIOLIBS): - @-./install_portaudio.sh - -clean: - -rm -f *.o *.a $(SHAREDLIB) $(OBJFILES) diff --git a/bindings/Ruby/ext/capture/port-audio/install_portaudio.sh b/bindings/Ruby/ext/capture/port-audio/install_portaudio.sh deleted file mode 100755 index ceecd2e0..00000000 --- a/bindings/Ruby/ext/capture/port-audio/install_portaudio.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash - -# This script attempts to install PortAudio, which can grap a live audio stream -# from the soundcard. -# -# On linux systems, we only build with ALSA, so make sure you install it using -# e.g.: -# sudo apt-get -y install libasound2-dev - -echo "Installing portaudio" - -if [ ! -e pa_stable_v190600_20161030.tgz ]; then - wget -T 10 -t 3 \ - http://www.portaudio.com/archives/pa_stable_v190600_20161030.tgz || exit 1; -fi - -tar -xovzf pa_stable_v190600_20161030.tgz || exit 1 - -cd portaudio -patch < ../patches/portaudio.patch - -MACOS=`uname 2>/dev/null | grep Darwin` -if [ -z "$MACOS" ]; then - ./configure --without-jack --without-oss \ - --with-alsa --prefix=`pwd`/install --with-pic || exit 1; - sed -i '40s:src/common/pa_ringbuffer.o::g' Makefile - sed -i '40s:$: src/common/pa_ringbuffer.o:' Makefile -else - # People may have changed OSX's default configuration -- we use clang++. - CC=clang CXX=clang++ ./configure --prefix=`pwd`/install --with-pic -fi - -make -make install - -cd .. diff --git a/bindings/Ruby/ext/capture/port-audio/patches/portaudio.patch b/bindings/Ruby/ext/capture/port-audio/patches/portaudio.patch deleted file mode 100644 index 1c618329..00000000 --- a/bindings/Ruby/ext/capture/port-audio/patches/portaudio.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- Makefile.in 2017-05-31 16:42:16.000000000 -0700 -+++ Makefile_new.in 2017-05-31 16:44:02.000000000 -0700 -@@ -193,6 +193,8 @@ - for include in $(INCLUDES); do \ - $(INSTALL_DATA) -m 644 $(top_srcdir)/include/$$include $(DESTDIR)$(includedir)/$$include; \ - done -+ $(INSTALL_DATA) -m 644 $(top_srcdir)/src/common/pa_ringbuffer.h $(DESTDIR)$(includedir)/$$include -+ $(INSTALL_DATA) -m 644 $(top_srcdir)/src/common/pa_util.h $(DESTDIR)$(includedir)/$$include - $(INSTALL) -d $(DESTDIR)$(libdir)/pkgconfig - $(INSTALL) -m 644 portaudio-2.0.pc $(DESTDIR)$(libdir)/pkgconfig/portaudio-2.0.pc - @echo "" diff --git a/bindings/Ruby/ext/capture/port-audio/port-audio-capture.c b/bindings/Ruby/ext/capture/port-audio/port-audio-capture.c deleted file mode 100644 index 4495d7df..00000000 --- a/bindings/Ruby/ext/capture/port-audio/port-audio-capture.c +++ /dev/null @@ -1,160 +0,0 @@ -// bindings/Ruby/ext/capture/port-audio/port-audio-capture.c - -// Copyright 2017 KITT.AI (author: Guoguo Chen, PpiBbuRr) - -#include -#include -#include -#include -#include -#include -#include - -typedef struct { - // Pointer to the ring buffer memory. - char* ringbuffer; - // Ring buffer wrapper used in PortAudio. - PaUtilRingBuffer pa_ringbuffer; - // Pointer to PortAudio stream. - PaStream* pa_stream; - // Number of lost samples at each LoadAudioData() due to ring buffer overflow. - int num_lost_samples; - // Wait for this number of samples in each LoadAudioData() call. - int min_read_samples; - // Pointer to the audio data. - int16_t* audio_data; -} rb_snowboy_port_audio_capture_t; - -int rb_snowboy_port_audio_capture_callback(const void* input, - void* output, - unsigned long frame_count, - const PaStreamCallbackTimeInfo* time_info, - PaStreamCallbackFlags status_flags, - void* user_data) { - - rb_snowboy_port_audio_capture_t* capture = (rb_snowboy_port_audio_capture_t*) user_data; - ring_buffer_size_t num_written_samples = PaUtil_WriteRingBuffer(&capture->pa_ringbuffer, input, frame_count); - capture->num_lost_samples += frame_count - num_written_samples; - return paContinue; -} - -void rb_snowboy_port_audio_capture_start_audio_capturing(rb_snowboy_port_audio_capture_t* capture, int sample_rate, - int num_channels, int bits_per_sample) { - capture->audio_data = NULL; - capture->num_lost_samples = 0; - capture->min_read_samples = sample_rate * 0.1; - - // Allocates ring buffer memory. - int ringbuffer_size = 16384; - capture->ringbuffer = (char*)( - PaUtil_AllocateMemory(bits_per_sample / 8 * ringbuffer_size)); - if (capture->ringbuffer == NULL) { - fprintf(stderr, "Fail to allocate memory for ring buffer.\n"); - exit(1); - } - - // Initializes PortAudio ring buffer. - ring_buffer_size_t rb_init_ans = - PaUtil_InitializeRingBuffer(&capture->pa_ringbuffer, bits_per_sample / 8, - ringbuffer_size, capture->ringbuffer); - if (rb_init_ans == -1) { - fprintf(stderr, "Ring buffer size is not power of 2.\n"); - exit(1); - } - - // Initializes PortAudio. - PaError pa_init_ans = Pa_Initialize(); - if (pa_init_ans != paNoError) { - fprintf(stderr, "Fail to initialize PortAudio, error message is %s.\n", - Pa_GetErrorText(pa_init_ans)); - exit(1); - } - - PaError pa_open_ans; - if (bits_per_sample == 8) { - pa_open_ans = Pa_OpenDefaultStream( - &capture->pa_stream, num_channels, 0, paUInt8, sample_rate, - paFramesPerBufferUnspecified, rb_snowboy_port_audio_capture_callback, capture); - } else if (bits_per_sample == 16) { - pa_open_ans = Pa_OpenDefaultStream( - &capture->pa_stream, num_channels, 0, paInt16, sample_rate, - paFramesPerBufferUnspecified, rb_snowboy_port_audio_capture_callback, capture); - } else if (bits_per_sample == 32) { - pa_open_ans = Pa_OpenDefaultStream( - &capture->pa_stream, num_channels, 0, paInt32, sample_rate, - paFramesPerBufferUnspecified, rb_snowboy_port_audio_capture_callback, capture); - } else { - fprintf(stderr, "Unsupported BitsPerSample: %d.\n", bits_per_sample); - exit(1); - } - if (pa_open_ans != paNoError) { - fprintf(stderr, "Fail to open PortAudio stream, error message is %s.\n", - Pa_GetErrorText(pa_open_ans)); - exit(1); - } - - PaError pa_stream_start_ans = Pa_StartStream(capture->pa_stream); - if (pa_stream_start_ans != paNoError) { - fprintf(stderr, "Fail to start PortAudio stream, error message is %s.\n", - Pa_GetErrorText(pa_stream_start_ans)); - exit(1); - } -} - -void rb_snowboy_port_audio_capture_stop_audio_capturing(rb_snowboy_port_audio_capture_t* capture) { - if (capture->audio_data != NULL) { - free(capture->audio_data); - capture->audio_data = NULL; - } - Pa_StopStream(capture->pa_stream); - Pa_CloseStream(capture->pa_stream); - Pa_Terminate(); - PaUtil_FreeMemory(capture->ringbuffer); -} - -int rb_snowboy_port_audio_capture_load_audio_data(rb_snowboy_port_audio_capture_t* capture) { - if (capture->audio_data != NULL) { - free(capture->audio_data); - capture->audio_data = NULL; - } - - // Checks ring buffer overflow. - if (capture->num_lost_samples > 0) { - fprintf(stderr, "Lost %d samples due to ring buffer overflow.\n", - capture->num_lost_samples); - capture->num_lost_samples = 0; - } - - ring_buffer_size_t num_available_samples = 0; - while (true) { - num_available_samples = - PaUtil_GetRingBufferReadAvailable(&capture->pa_ringbuffer); - if (num_available_samples >= capture->min_read_samples) { - break; - } - Pa_Sleep(5); - } - - // Reads data. - num_available_samples = PaUtil_GetRingBufferReadAvailable(&capture->pa_ringbuffer); - capture->audio_data = malloc(num_available_samples * sizeof(int16_t)); - ring_buffer_size_t num_read_samples = PaUtil_ReadRingBuffer( - &capture->pa_ringbuffer, capture->audio_data, num_available_samples); - if (num_read_samples != num_available_samples) { - fprintf(stderr, "%d samples were available, but only %d samples were read" - ".\n", num_available_samples, num_read_samples); - } - return num_read_samples; -} - -rb_snowboy_port_audio_capture_t* rb_snowboy_port_audio_capture_new() { - rb_snowboy_port_audio_capture_t* capture = malloc(sizeof(rb_snowboy_port_audio_capture_t)); - - memset(capture, 0, sizeof(rb_snowboy_port_audio_capture_t)); - - return capture; -} - -int16_t* rb_snowboy_port_audio_capture_get_audio_data(rb_snowboy_port_audio_capture_t* capture) { - return capture->audio_data; -} diff --git a/bindings/Ruby/ext/capture/port-audio/port-audio-capture.mk b/bindings/Ruby/ext/capture/port-audio/port-audio-capture.mk deleted file mode 100644 index 79a6154d..00000000 --- a/bindings/Ruby/ext/capture/port-audio/port-audio-capture.mk +++ /dev/null @@ -1,53 +0,0 @@ -DYNAMIC := True -CC := -CXX := -LDFLAGS := -LDLIBS := -PORTAUDIOINC := portaudio/install/include -PORTAUDIOLIBS := portaudio/install/lib/libportaudio.a - -CFLAGS := -CXXFLAGS += -D_GLIBCXX_USE_CXX11_ABI=0 - -ifeq ($(DYNAMIC), True) - CFLAGS += -fPIC - CXXFLAGS += -fPIC -endif - -ifeq ($(shell uname -m | cut -c 1-3), x86) - CFLAGS += -msse -msse2 - CXXFLAGS += -msse -msse2 -endif - -ifeq ($(shell uname), Darwin) - # By default Mac uses clang++ as g++, but people may have changed their - # default configuration. - CC := clang - CXX := clang++ - CFLAGS += -I$(TOPDIR) -Wall -I$(PORTAUDIOINC) - CXXFLAGS += -I$(TOPDIR) -Wall -Wno-sign-compare -Winit-self \ - -DHAVE_POSIX_MEMALIGN -DHAVE_CLAPACK -I$(PORTAUDIOINC) - LDLIBS += -ldl -lm -framework Accelerate -framework CoreAudio \ - -framework AudioToolbox -framework AudioUnit -framework CoreServices \ - $(PORTAUDIOLIBS) -else ifeq ($(shell uname), Linux) - CC := gcc - CXX := g++ - CFLAGS += -I$(TOPDIR) -Wall -I$(PORTAUDIOINC) - CXXFLAGS += -I$(TOPDIR) -std=c++0x -Wall -Wno-sign-compare \ - -Wno-unused-local-typedefs -Winit-self -rdynamic \ - -DHAVE_POSIX_MEMALIGN -I$(PORTAUDIOINC) - LDLIBS += -ldl -lm -Wl,-Bstatic -Wl,-Bdynamic -lrt -lpthread $(PORTAUDIOLIBS) -lasound\ - #ifneq (,$(findstring arm,$(shell uname -m))) - #endif -endif - -# Suppress clang warnings... -COMPILER = $(shell $(CXX) -v 2>&1 ) -ifeq ($(findstring clang,$(COMPILER)), clang) - CXXFLAGS += -Wno-mismatched-tags -Wno-c++11-extensions -endif - -# Set optimization level. -CFLAGS += -O3 -CXXFLAGS += -O3 diff --git a/bindings/Ruby/lib/snowboy.rb b/bindings/Ruby/lib/snowboy.rb deleted file mode 100644 index 557618ee..00000000 --- a/bindings/Ruby/lib/snowboy.rb +++ /dev/null @@ -1,109 +0,0 @@ -$: << BASE_PATH=File.dirname(__FILE__) - -require 'ffi' - -module Snowboy - module Lib - extend FFI::Library - - so_path = File.expand_path(File.join(BASE_PATH, '..', '..', 'C', 'libsnowboydetect.so')) - - if File.exist?(so_path) - ffi_lib so_path - else - ffi_lib "libsnowboydetect" - end - - attach_function :SnowboyDetectConstructor, [:string, :string], :pointer - attach_function :SnowboyDetectBitsPerSample, [:pointer], :int - attach_function :SnowboyDetectSampleRate, [:pointer], :int - attach_function :SnowboyDetectNumChannels, [:pointer], :int - attach_function :SnowboyDetectNumHotwords, [:pointer], :int - attach_function :SnowboyDetectReset, [:pointer], :bool - attach_function :SnowboyDetectUpdateModel, [:pointer], :void - attach_function :SnowboyDetectApplyFrontend, [:pointer, :bool], :void - attach_function :SnowboyDetectRunDetection, [:pointer, :pointer, :int, :bool], :int - attach_function :SnowboyDetectSetAudioGain, [:pointer, :float], :void - attach_function :SnowboyDetectSetSensitivity, [:pointer, :string], :void - end - - class Detector - attr_reader :resource, :model, :sensitivity, :audio_gain, :ptr - - # @param +model+ pointing to the model file(s) - # - def initialize resource: nil, model: nil, sensitivity: 0.5, gain: 1 - model = value2str(model) - - @ptr = Lib::SnowboyDetectConstructor(resource, model) - - @resource = resource - @model = model - - self.sensitivity = sensitivity; - self.audio_gain = gain; - end - - def audio_gain= gain - @audio_gain = gain - - Lib::SnowboyDetectSetAudioGain(ptr, audio_gain) - end - - # @param +lvl+ specifying levels per model - # - def sensitivity= lvl - @sensitivity = o=value2str(lvl) - - Lib::SnowboyDetectSetSensitivity(ptr, o) - end - - def sample_rate - Lib::SnowboyDetectSampleRate(ptr) - end - - def bits_per_sample - Lib::SnowboyDetectBitsPerSample(ptr) - end - - def n_channels - Lib::SnowboyDetectNumChannels(ptr) - end - - def n_hotwords - Lib::SnowboyDetectNumHotwords(ptr) - end - - def reset - Lib::SnowboyDetectReset(ptr) - end - - def apply_frontend bool - Lib::SnowboyDetectApplyFrontend(ptr, bool) - end - - def update_model - Lib::SnowboyDetectUpdateModel(ptr) - end - - def run_detection *o - Lib::SnowboyDetectRunDetection(ptr, *o) - end - - private - def value2str obj - if obj.is_a?(String) - obj - elsif obj.is_a?(Array) - obj.map do |o| - o.to_s - end.join(",") - elsif obj.is_a?(Numeric) - obj.to_s - else - obj.to_s - end - end - end -end - diff --git a/bindings/Ruby/lib/snowboy/capture/port-audio/port-audio-capture.rb b/bindings/Ruby/lib/snowboy/capture/port-audio/port-audio-capture.rb deleted file mode 100644 index 07ce722a..00000000 --- a/bindings/Ruby/lib/snowboy/capture/port-audio/port-audio-capture.rb +++ /dev/null @@ -1,68 +0,0 @@ -module Snowboy - module Capture - class PortAudio - module Lib - extend FFI::Library - - ffi_lib File.expand_path(File.join(File.dirname(__FILE__), "..", '..', '..', '..', 'ext', 'capture', 'port-audio', 'port-audio-capture.so')) - - attach_function :rb_snowboy_port_audio_capture_start_audio_capturing, [:pointer, :int, :int, :int], :void - attach_function :rb_snowboy_port_audio_capture_stop_audio_capturing, [:pointer], :void - attach_function :rb_snowboy_port_audio_capture_load_audio_data, [:pointer], :int - attach_function :rb_snowboy_port_audio_capture_get_audio_data, [:pointer], :pointer - attach_function :rb_snowboy_port_audio_capture_new, [], :pointer - end - - attr_reader :ptr - def initialize - @ptr = Lib.rb_snowboy_port_audio_capture_new() - end - - def start_capture(rate, channels, bps) - Lib.rb_snowboy_port_audio_capture_start_audio_capturing(@ptr, rate, channels, bps) - end - - def stop_capture - @running = false - Lib.rb_snowboy_port_audio_capture_stop_audio_capturing(@ptr) - end - - def load_audio_data - Lib.rb_snowboy_port_audio_capture_load_audio_data(@ptr) - end - - def get_audio_data - Lib.rb_snowboy_port_audio_capture_get_audio_data(@ptr) - end - - attr_accessor :thread - def run(rate, channels, bps, &b) - raise ArgumentError.new("No Block passed") unless b - - @running = true - - start_capture(rate, channels, bps) - - @thread = Thread.new do - begin - while running? - if (len=load_audio_data) > 0 - b.call(get_audio_data, len) - end - end - - stop_capture - rescue => e - puts e - puts e.backtrace.join("\n") - stop_capture - end - end - end - - def running? - @running - end - end - end -end diff --git a/examples/Ruby/README.md b/examples/Ruby/README.md deleted file mode 100644 index cb99750b..00000000 --- a/examples/Ruby/README.md +++ /dev/null @@ -1,17 +0,0 @@ -Sample program to detect hotword. - -Dependencies -=== -snowboy shared lib -`cd ../../bindings/C && make` - -FFI for ruby. -`sudo gem i ffi` - - -This sample uses portaudio to capture from a library is provied in `../../bindings/Ruby/ext/capture/port-audio` -`cd ../../bindings/Ruby/ext/capture/port-audio && make` - -Usage -=== -`ruby port-audio-sample.rb` diff --git a/examples/Ruby/port-audio-sample.rb b/examples/Ruby/port-audio-sample.rb deleted file mode 100644 index d481090e..00000000 --- a/examples/Ruby/port-audio-sample.rb +++ /dev/null @@ -1,23 +0,0 @@ -$: << File.join(File.dirname(__FILE__), "..", "..", "bindings", "Ruby", "lib") - - -require "snowboy" -require "snowboy/capture/port-audio/port-audio-capture" - -resource_path = "../../resources/common.res" -model_path = "../../resources/models/snowboy.umdl" - -snowboy = Snowboy::Detector.new(model: model_path, resource: resource_path, gain: 1, sensitivity: 0.5) -pac = Snowboy::Capture::PortAudio.new - -puts "Listening... press Ctrl-c to exit." - -pac.run(snowboy.sample_rate, snowboy.n_channels, snowboy.bits_per_sample) do |data, length| - result = snowboy.run_detection(data, length, false) - - if result > 0 - puts "Hotword %d detected." % result - end -end - -while true; end From 46221f13b2568d54fd830d4c2e603861bc4a5c20 Mon Sep 17 00:00:00 2001 From: ppibburr Date: Fri, 6 Apr 2018 21:39:47 -0400 Subject: [PATCH 13/13] move ruby bindings to 'swig', use symlinks --- examples/Ruby/README.md | 19 +++ examples/Ruby/port-audio-sample.rb | 23 +++ swig/Ruby/C/Makefile | 17 ++ swig/Ruby/C/libsnowboydetect.mk | 55 ++++++ swig/Ruby/C/snowboy-detect-c-wrapper.cc | 1 + swig/Ruby/C/snowboy-detect-c-wrapper.h | 1 + swig/Ruby/README.md | 32 ++++ swig/Ruby/ext/capture/port-audio/Makefile | 20 +++ .../capture/port-audio/install_portaudio.sh | 1 + swig/Ruby/ext/capture/port-audio/patches | 1 + .../capture/port-audio/port-audio-capture.c | 160 ++++++++++++++++++ .../capture/port-audio/port-audio-capture.mk | 53 ++++++ swig/Ruby/lib/snowboy.rb | 139 +++++++++++++++ .../capture/port-audio/port-audio-capture.rb | 68 ++++++++ 14 files changed, 590 insertions(+) create mode 100644 examples/Ruby/README.md create mode 100644 examples/Ruby/port-audio-sample.rb create mode 100644 swig/Ruby/C/Makefile create mode 100644 swig/Ruby/C/libsnowboydetect.mk create mode 120000 swig/Ruby/C/snowboy-detect-c-wrapper.cc create mode 120000 swig/Ruby/C/snowboy-detect-c-wrapper.h create mode 100644 swig/Ruby/README.md create mode 100644 swig/Ruby/ext/capture/port-audio/Makefile create mode 120000 swig/Ruby/ext/capture/port-audio/install_portaudio.sh create mode 120000 swig/Ruby/ext/capture/port-audio/patches create mode 100644 swig/Ruby/ext/capture/port-audio/port-audio-capture.c create mode 100644 swig/Ruby/ext/capture/port-audio/port-audio-capture.mk create mode 100644 swig/Ruby/lib/snowboy.rb create mode 100644 swig/Ruby/lib/snowboy/capture/port-audio/port-audio-capture.rb diff --git a/examples/Ruby/README.md b/examples/Ruby/README.md new file mode 100644 index 00000000..162a37c1 --- /dev/null +++ b/examples/Ruby/README.md @@ -0,0 +1,19 @@ +Sample program to detect hotword. + +Dependencies +=== +snowboy shared lib +`cd ../../swig/Ruby/C && make` + +FFI for ruby. +`sudo gem i ffi` + + +This sample uses portaudio to capture with. +a library is provied in `../../swig/Ruby/ext/capture/port-audio` + +`cd ../../swig/Ruby/ext/capture/port-audio && make` + +Usage +=== +`ruby port-audio-sample.rb` diff --git a/examples/Ruby/port-audio-sample.rb b/examples/Ruby/port-audio-sample.rb new file mode 100644 index 00000000..5b9074a8 --- /dev/null +++ b/examples/Ruby/port-audio-sample.rb @@ -0,0 +1,23 @@ +$: << File.join(File.dirname(__FILE__), "..", "..", "swig", "Ruby", "lib") + + +require "snowboy" +require "snowboy/capture/port-audio/port-audio-capture" + +resource_path = "../../resources/common.res" +model_path = "../../resources/models/snowboy.umdl" + +snowboy = Snowboy::Detector.new(model: model_path, resource: resource_path, gain: 1, sensitivity: 0.5) +pac = Snowboy::Capture::PortAudio.new + +puts "Listening... press Ctrl-c to exit." + +pac.run(snowboy.sample_rate, snowboy.n_channels, snowboy.bits_per_sample) do |data, length| + result = snowboy.run_detection(data, length, false) + + if result > 0 + puts "Hotword %d detected." % result + end +end + +while true; end diff --git a/swig/Ruby/C/Makefile b/swig/Ruby/C/Makefile new file mode 100644 index 00000000..fe7e4667 --- /dev/null +++ b/swig/Ruby/C/Makefile @@ -0,0 +1,17 @@ +include libsnowboydetect.mk + +SHAREDLIB = libsnowboydetect.so + +OBJFILES = snowboy-detect-c-wrapper.o + +all: $(SHAREDLIB) + +%.a: + $(MAKE) -C ${@D} ${@F} + +# We have to use the C++ compiler to link. +$(SHAREDLIB): $(SNOWBOYDETECTLIBFILE) $(OBJFILES) + $(CXX) $(OBJFILES) $(SNOWBOYDETECTLIBFILE) $(LDLIBS) -shared -o $(SHAREDLIB) + +clean: + -rm -f *.o *.a $(SHAREDLIB) $(OBJFILES) diff --git a/swig/Ruby/C/libsnowboydetect.mk b/swig/Ruby/C/libsnowboydetect.mk new file mode 100644 index 00000000..c96d9807 --- /dev/null +++ b/swig/Ruby/C/libsnowboydetect.mk @@ -0,0 +1,55 @@ +TOPDIR := ../../../ +DYNAMIC := True +CC := +CXX := +LDFLAGS := +LDLIBS := + +CFLAGS := +CXXFLAGS += -D_GLIBCXX_USE_CXX11_ABI=0 + +ifeq ($(DYNAMIC), True) + CFLAGS += -fPIC + CXXFLAGS += -fPIC +endif + +ifeq ($(shell uname -m | cut -c 1-3), x86) + CFLAGS += -msse -msse2 + CXXFLAGS += -msse -msse2 +endif + +ifeq ($(shell uname), Darwin) + # By default Mac uses clang++ as g++, but people may have changed their + # default configuration. + CC := clang + CXX := clang++ + CFLAGS += -I$(TOPDIR) -Wall -I$(PORTAUDIOINC) + CXXFLAGS += -I$(TOPDIR) -Wall -Wno-sign-compare -Winit-self \ + -DHAVE_POSIX_MEMALIGN -DHAVE_CLAPACK + LDLIBS += -ldl -lm -framework Accelerate -framework CoreAudio \ + -framework AudioToolbox -framework AudioUnit -framework CoreServices + SNOWBOYDETECTLIBFILE := $(TOPDIR)/lib/osx/libsnowboy-detect.a +else ifeq ($(shell uname), Linux) + CC := gcc + CXX := g++ + CFLAGS += -I$(TOPDIR) -Wall + CXXFLAGS += -I$(TOPDIR) -std=c++0x -Wall -Wno-sign-compare \ + -Wno-unused-local-typedefs -Winit-self -rdynamic \ + -DHAVE_POSIX_MEMALIGN + LDLIBS += -ldl -lm -Wl,-Bstatic -Wl,-Bdynamic -lrt -lpthread\ + -L/usr/lib/atlas-base -lf77blas -lcblas -llapack_atlas -latlas + SNOWBOYDETECTLIBFILE := $(TOPDIR)/lib/ubuntu64/libsnowboy-detect.a + ifneq (,$(findstring arm,$(shell uname -m))) + SNOWBOYDETECTLIBFILE := $(TOPDIR)/lib/rpi/libsnowboy-detect.a + endif +endif + +# Suppress clang warnings... +COMPILER = $(shell $(CXX) -v 2>&1 ) +ifeq ($(findstring clang,$(COMPILER)), clang) + CXXFLAGS += -Wno-mismatched-tags -Wno-c++11-extensions +endif + +# Set optimization level. +CFLAGS += -O3 +CXXFLAGS += -O3 diff --git a/swig/Ruby/C/snowboy-detect-c-wrapper.cc b/swig/Ruby/C/snowboy-detect-c-wrapper.cc new file mode 120000 index 00000000..49aedb3e --- /dev/null +++ b/swig/Ruby/C/snowboy-detect-c-wrapper.cc @@ -0,0 +1 @@ +../../../examples/C/snowboy-detect-c-wrapper.cc \ No newline at end of file diff --git a/swig/Ruby/C/snowboy-detect-c-wrapper.h b/swig/Ruby/C/snowboy-detect-c-wrapper.h new file mode 120000 index 00000000..df4846ef --- /dev/null +++ b/swig/Ruby/C/snowboy-detect-c-wrapper.h @@ -0,0 +1 @@ +../../../examples/C/snowboy-detect-c-wrapper.h \ No newline at end of file diff --git a/swig/Ruby/README.md b/swig/Ruby/README.md new file mode 100644 index 00000000..39d121c7 --- /dev/null +++ b/swig/Ruby/README.md @@ -0,0 +1,32 @@ +Ruby bindings for `snowboy` + +Dependencies +=== +snowboy shared lib +`cd ./C && make` + +FFI for ruby. +`sudo gem i ffi` + +Extra +=== +A simple audio capture tool is provided in `./ext/capture/port-audio` +`cd ./ext/capture/port-audio && make` + +Usage +=== +```ruby +require "./lib/snowboy" + +snowboy = Snowboy::Detect.new(resource: resource_path, model: model_path) + +# get audio data +# ... + +result = snowboy.run_detection(data, data_length, false) + +if result > 0 + # handle result +end +``` + diff --git a/swig/Ruby/ext/capture/port-audio/Makefile b/swig/Ruby/ext/capture/port-audio/Makefile new file mode 100644 index 00000000..a96633a7 --- /dev/null +++ b/swig/Ruby/ext/capture/port-audio/Makefile @@ -0,0 +1,20 @@ +include port-audio-capture.mk + +SHAREDLIB = port-audio-capture.so + +OBJFILES = port-audio-capture.o + +all: $(SHAREDLIB) + +%.a: + $(MAKE) -C ${@D} ${@F} + +# We have to use the C++ compiler to link. +$(SHAREDLIB): $(PORTAUDIOLIBS) $(OBJFILES) + $(CXX) $(OBJFILES) $(PORTAUDIOLIBS) $(LDLIBS) -shared -o $(SHAREDLIB) + +$(PORTAUDIOLIBS): + @-./install_portaudio.sh + +clean: + -rm -f *.o *.a $(SHAREDLIB) $(OBJFILES) diff --git a/swig/Ruby/ext/capture/port-audio/install_portaudio.sh b/swig/Ruby/ext/capture/port-audio/install_portaudio.sh new file mode 120000 index 00000000..b653772a --- /dev/null +++ b/swig/Ruby/ext/capture/port-audio/install_portaudio.sh @@ -0,0 +1 @@ +../../../../../examples/C/install_portaudio.sh \ No newline at end of file diff --git a/swig/Ruby/ext/capture/port-audio/patches b/swig/Ruby/ext/capture/port-audio/patches new file mode 120000 index 00000000..c67d76cc --- /dev/null +++ b/swig/Ruby/ext/capture/port-audio/patches @@ -0,0 +1 @@ +../../../../../examples/C/patches \ No newline at end of file diff --git a/swig/Ruby/ext/capture/port-audio/port-audio-capture.c b/swig/Ruby/ext/capture/port-audio/port-audio-capture.c new file mode 100644 index 00000000..4495d7df --- /dev/null +++ b/swig/Ruby/ext/capture/port-audio/port-audio-capture.c @@ -0,0 +1,160 @@ +// bindings/Ruby/ext/capture/port-audio/port-audio-capture.c + +// Copyright 2017 KITT.AI (author: Guoguo Chen, PpiBbuRr) + +#include +#include +#include +#include +#include +#include +#include + +typedef struct { + // Pointer to the ring buffer memory. + char* ringbuffer; + // Ring buffer wrapper used in PortAudio. + PaUtilRingBuffer pa_ringbuffer; + // Pointer to PortAudio stream. + PaStream* pa_stream; + // Number of lost samples at each LoadAudioData() due to ring buffer overflow. + int num_lost_samples; + // Wait for this number of samples in each LoadAudioData() call. + int min_read_samples; + // Pointer to the audio data. + int16_t* audio_data; +} rb_snowboy_port_audio_capture_t; + +int rb_snowboy_port_audio_capture_callback(const void* input, + void* output, + unsigned long frame_count, + const PaStreamCallbackTimeInfo* time_info, + PaStreamCallbackFlags status_flags, + void* user_data) { + + rb_snowboy_port_audio_capture_t* capture = (rb_snowboy_port_audio_capture_t*) user_data; + ring_buffer_size_t num_written_samples = PaUtil_WriteRingBuffer(&capture->pa_ringbuffer, input, frame_count); + capture->num_lost_samples += frame_count - num_written_samples; + return paContinue; +} + +void rb_snowboy_port_audio_capture_start_audio_capturing(rb_snowboy_port_audio_capture_t* capture, int sample_rate, + int num_channels, int bits_per_sample) { + capture->audio_data = NULL; + capture->num_lost_samples = 0; + capture->min_read_samples = sample_rate * 0.1; + + // Allocates ring buffer memory. + int ringbuffer_size = 16384; + capture->ringbuffer = (char*)( + PaUtil_AllocateMemory(bits_per_sample / 8 * ringbuffer_size)); + if (capture->ringbuffer == NULL) { + fprintf(stderr, "Fail to allocate memory for ring buffer.\n"); + exit(1); + } + + // Initializes PortAudio ring buffer. + ring_buffer_size_t rb_init_ans = + PaUtil_InitializeRingBuffer(&capture->pa_ringbuffer, bits_per_sample / 8, + ringbuffer_size, capture->ringbuffer); + if (rb_init_ans == -1) { + fprintf(stderr, "Ring buffer size is not power of 2.\n"); + exit(1); + } + + // Initializes PortAudio. + PaError pa_init_ans = Pa_Initialize(); + if (pa_init_ans != paNoError) { + fprintf(stderr, "Fail to initialize PortAudio, error message is %s.\n", + Pa_GetErrorText(pa_init_ans)); + exit(1); + } + + PaError pa_open_ans; + if (bits_per_sample == 8) { + pa_open_ans = Pa_OpenDefaultStream( + &capture->pa_stream, num_channels, 0, paUInt8, sample_rate, + paFramesPerBufferUnspecified, rb_snowboy_port_audio_capture_callback, capture); + } else if (bits_per_sample == 16) { + pa_open_ans = Pa_OpenDefaultStream( + &capture->pa_stream, num_channels, 0, paInt16, sample_rate, + paFramesPerBufferUnspecified, rb_snowboy_port_audio_capture_callback, capture); + } else if (bits_per_sample == 32) { + pa_open_ans = Pa_OpenDefaultStream( + &capture->pa_stream, num_channels, 0, paInt32, sample_rate, + paFramesPerBufferUnspecified, rb_snowboy_port_audio_capture_callback, capture); + } else { + fprintf(stderr, "Unsupported BitsPerSample: %d.\n", bits_per_sample); + exit(1); + } + if (pa_open_ans != paNoError) { + fprintf(stderr, "Fail to open PortAudio stream, error message is %s.\n", + Pa_GetErrorText(pa_open_ans)); + exit(1); + } + + PaError pa_stream_start_ans = Pa_StartStream(capture->pa_stream); + if (pa_stream_start_ans != paNoError) { + fprintf(stderr, "Fail to start PortAudio stream, error message is %s.\n", + Pa_GetErrorText(pa_stream_start_ans)); + exit(1); + } +} + +void rb_snowboy_port_audio_capture_stop_audio_capturing(rb_snowboy_port_audio_capture_t* capture) { + if (capture->audio_data != NULL) { + free(capture->audio_data); + capture->audio_data = NULL; + } + Pa_StopStream(capture->pa_stream); + Pa_CloseStream(capture->pa_stream); + Pa_Terminate(); + PaUtil_FreeMemory(capture->ringbuffer); +} + +int rb_snowboy_port_audio_capture_load_audio_data(rb_snowboy_port_audio_capture_t* capture) { + if (capture->audio_data != NULL) { + free(capture->audio_data); + capture->audio_data = NULL; + } + + // Checks ring buffer overflow. + if (capture->num_lost_samples > 0) { + fprintf(stderr, "Lost %d samples due to ring buffer overflow.\n", + capture->num_lost_samples); + capture->num_lost_samples = 0; + } + + ring_buffer_size_t num_available_samples = 0; + while (true) { + num_available_samples = + PaUtil_GetRingBufferReadAvailable(&capture->pa_ringbuffer); + if (num_available_samples >= capture->min_read_samples) { + break; + } + Pa_Sleep(5); + } + + // Reads data. + num_available_samples = PaUtil_GetRingBufferReadAvailable(&capture->pa_ringbuffer); + capture->audio_data = malloc(num_available_samples * sizeof(int16_t)); + ring_buffer_size_t num_read_samples = PaUtil_ReadRingBuffer( + &capture->pa_ringbuffer, capture->audio_data, num_available_samples); + if (num_read_samples != num_available_samples) { + fprintf(stderr, "%d samples were available, but only %d samples were read" + ".\n", num_available_samples, num_read_samples); + } + return num_read_samples; +} + +rb_snowboy_port_audio_capture_t* rb_snowboy_port_audio_capture_new() { + rb_snowboy_port_audio_capture_t* capture = malloc(sizeof(rb_snowboy_port_audio_capture_t)); + + memset(capture, 0, sizeof(rb_snowboy_port_audio_capture_t)); + + return capture; +} + +int16_t* rb_snowboy_port_audio_capture_get_audio_data(rb_snowboy_port_audio_capture_t* capture) { + return capture->audio_data; +} diff --git a/swig/Ruby/ext/capture/port-audio/port-audio-capture.mk b/swig/Ruby/ext/capture/port-audio/port-audio-capture.mk new file mode 100644 index 00000000..79a6154d --- /dev/null +++ b/swig/Ruby/ext/capture/port-audio/port-audio-capture.mk @@ -0,0 +1,53 @@ +DYNAMIC := True +CC := +CXX := +LDFLAGS := +LDLIBS := +PORTAUDIOINC := portaudio/install/include +PORTAUDIOLIBS := portaudio/install/lib/libportaudio.a + +CFLAGS := +CXXFLAGS += -D_GLIBCXX_USE_CXX11_ABI=0 + +ifeq ($(DYNAMIC), True) + CFLAGS += -fPIC + CXXFLAGS += -fPIC +endif + +ifeq ($(shell uname -m | cut -c 1-3), x86) + CFLAGS += -msse -msse2 + CXXFLAGS += -msse -msse2 +endif + +ifeq ($(shell uname), Darwin) + # By default Mac uses clang++ as g++, but people may have changed their + # default configuration. + CC := clang + CXX := clang++ + CFLAGS += -I$(TOPDIR) -Wall -I$(PORTAUDIOINC) + CXXFLAGS += -I$(TOPDIR) -Wall -Wno-sign-compare -Winit-self \ + -DHAVE_POSIX_MEMALIGN -DHAVE_CLAPACK -I$(PORTAUDIOINC) + LDLIBS += -ldl -lm -framework Accelerate -framework CoreAudio \ + -framework AudioToolbox -framework AudioUnit -framework CoreServices \ + $(PORTAUDIOLIBS) +else ifeq ($(shell uname), Linux) + CC := gcc + CXX := g++ + CFLAGS += -I$(TOPDIR) -Wall -I$(PORTAUDIOINC) + CXXFLAGS += -I$(TOPDIR) -std=c++0x -Wall -Wno-sign-compare \ + -Wno-unused-local-typedefs -Winit-self -rdynamic \ + -DHAVE_POSIX_MEMALIGN -I$(PORTAUDIOINC) + LDLIBS += -ldl -lm -Wl,-Bstatic -Wl,-Bdynamic -lrt -lpthread $(PORTAUDIOLIBS) -lasound\ + #ifneq (,$(findstring arm,$(shell uname -m))) + #endif +endif + +# Suppress clang warnings... +COMPILER = $(shell $(CXX) -v 2>&1 ) +ifeq ($(findstring clang,$(COMPILER)), clang) + CXXFLAGS += -Wno-mismatched-tags -Wno-c++11-extensions +endif + +# Set optimization level. +CFLAGS += -O3 +CXXFLAGS += -O3 diff --git a/swig/Ruby/lib/snowboy.rb b/swig/Ruby/lib/snowboy.rb new file mode 100644 index 00000000..92e61bf8 --- /dev/null +++ b/swig/Ruby/lib/snowboy.rb @@ -0,0 +1,139 @@ +$: << BASE_PATH=File.dirname(__FILE__) + +require 'ffi' + +module Snowboy + module Lib + extend FFI::Library + + so_path = File.expand_path(File.join(BASE_PATH, '..', 'C', 'libsnowboydetect.so')) + + if File.exist?(so_path) + ffi_lib so_path + else + ffi_lib "libsnowboydetect" + end + + attach_function :SnowboyDetectConstructor, [:string, :string], :pointer + attach_function :SnowboyDetectDestructor, [:pointer], :void + attach_function :SnowboyDetectBitsPerSample, [:pointer], :int + attach_function :SnowboyDetectSampleRate, [:pointer], :int + attach_function :SnowboyDetectNumChannels, [:pointer], :int + attach_function :SnowboyDetectNumHotwords, [:pointer], :int + attach_function :SnowboyDetectReset, [:pointer], :bool + attach_function :SnowboyDetectUpdateModel, [:pointer], :void + attach_function :SnowboyDetectApplyFrontend, [:pointer, :bool], :void + attach_function :SnowboyDetectRunDetection, [:pointer, :pointer, :int, :bool], :int + attach_function :SnowboyDetectSetAudioGain, [:pointer, :float], :void + attach_function :SnowboyDetectSetSensitivity, [:pointer, :string], :void + end + + class Detector + attr_reader :resource, :model, :sensitivity, :audio_gain, :ptr + + # @param model [String, Array] path(s) pointing to the model file(s) + # @param sensitivity [Numeric, Array] the sensitivity level(s per hotword of all +models+) + # @param gain [Numeric] the gain level + # @param resource [String] path to the resource file + # + def initialize resource: nil, model: nil, sensitivity: 0.5, gain: 1 + model = value2str(model) + + @ptr = Lib::SnowboyDetectConstructor(resource, model) + + @resource = resource + @model = model + + self.sensitivity = sensitivity; + self.audio_gain = gain; + end + + # @param gain [Numeric] the gain level + def audio_gain= gain + @audio_gain = gain + + Lib::SnowboyDetectSetAudioGain(ptr, audio_gain) + end + + # @param lvl [Numeric, Array] specifying level(s per model) + # + def sensitivity= lvl + if @model.is_a?(Array) and !sensitivity.is_a?(Array) + v = lvl + lvl = [] + + n_hotwords.times do + lvl << v + end + end + + @sensitivity = o=value2str(lvl) + + Lib::SnowboyDetectSetSensitivity(ptr, o) + end + + # @return [Integer] sample rate + # + def sample_rate + Lib::SnowboyDetectSampleRate(ptr) + end + + # @return [Integer] bits per sample + # + def bits_per_sample + Lib::SnowboyDetectBitsPerSample(ptr) + end + + # @return [Integer] number of channels + # + def n_channels + Lib::SnowboyDetectNumChannels(ptr) + end + + # @return [Integer] number of hotwords + # + def n_hotwords + Lib::SnowboyDetectNumHotwords(ptr) + end + + def reset + Lib::SnowboyDetectReset(ptr) + end + + # @param bool [true, false] apply frontend + # + def apply_frontend bool + Lib::SnowboyDetectApplyFrontend(ptr, bool) + end + + def update_model + Lib::SnowboyDetectUpdateModel(ptr) + end + + # run detection on audio +data+ + # + # @param data [FFI::MemoryPointer] representing the audio data + # @param length [Integer] the data length + # @param is_end [true, false] defaults false + # + def run_detection data, length, is_end=false + Lib::SnowboyDetectRunDetection(ptr, data, length, is_end) + end + + private + def value2str obj + if obj.is_a?(String) + obj + elsif obj.is_a?(Array) + obj.map do |o| + o.to_s + end.join(",") + elsif obj.is_a?(Numeric) + obj.to_s + else + obj.to_s + end + end + end +end + diff --git a/swig/Ruby/lib/snowboy/capture/port-audio/port-audio-capture.rb b/swig/Ruby/lib/snowboy/capture/port-audio/port-audio-capture.rb new file mode 100644 index 00000000..07ce722a --- /dev/null +++ b/swig/Ruby/lib/snowboy/capture/port-audio/port-audio-capture.rb @@ -0,0 +1,68 @@ +module Snowboy + module Capture + class PortAudio + module Lib + extend FFI::Library + + ffi_lib File.expand_path(File.join(File.dirname(__FILE__), "..", '..', '..', '..', 'ext', 'capture', 'port-audio', 'port-audio-capture.so')) + + attach_function :rb_snowboy_port_audio_capture_start_audio_capturing, [:pointer, :int, :int, :int], :void + attach_function :rb_snowboy_port_audio_capture_stop_audio_capturing, [:pointer], :void + attach_function :rb_snowboy_port_audio_capture_load_audio_data, [:pointer], :int + attach_function :rb_snowboy_port_audio_capture_get_audio_data, [:pointer], :pointer + attach_function :rb_snowboy_port_audio_capture_new, [], :pointer + end + + attr_reader :ptr + def initialize + @ptr = Lib.rb_snowboy_port_audio_capture_new() + end + + def start_capture(rate, channels, bps) + Lib.rb_snowboy_port_audio_capture_start_audio_capturing(@ptr, rate, channels, bps) + end + + def stop_capture + @running = false + Lib.rb_snowboy_port_audio_capture_stop_audio_capturing(@ptr) + end + + def load_audio_data + Lib.rb_snowboy_port_audio_capture_load_audio_data(@ptr) + end + + def get_audio_data + Lib.rb_snowboy_port_audio_capture_get_audio_data(@ptr) + end + + attr_accessor :thread + def run(rate, channels, bps, &b) + raise ArgumentError.new("No Block passed") unless b + + @running = true + + start_capture(rate, channels, bps) + + @thread = Thread.new do + begin + while running? + if (len=load_audio_data) > 0 + b.call(get_audio_data, len) + end + end + + stop_capture + rescue => e + puts e + puts e.backtrace.join("\n") + stop_capture + end + end + end + + def running? + @running + end + end + end +end