Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ruby bindings and sample #403

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
19 changes: 19 additions & 0 deletions examples/Ruby/README.md
Original file line number Diff line number Diff line change
@@ -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`
23 changes: 23 additions & 0 deletions examples/Ruby/port-audio-sample.rb
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions swig/Ruby/C/Makefile
Original file line number Diff line number Diff line change
@@ -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)
55 changes: 55 additions & 0 deletions swig/Ruby/C/libsnowboydetect.mk
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions swig/Ruby/C/snowboy-detect-c-wrapper.cc
1 change: 1 addition & 0 deletions swig/Ruby/C/snowboy-detect-c-wrapper.h
32 changes: 32 additions & 0 deletions swig/Ruby/README.md
Original file line number Diff line number Diff line change
@@ -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
```

20 changes: 20 additions & 0 deletions swig/Ruby/ext/capture/port-audio/Makefile
Original file line number Diff line number Diff line change
@@ -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)
1 change: 1 addition & 0 deletions swig/Ruby/ext/capture/port-audio/install_portaudio.sh
1 change: 1 addition & 0 deletions swig/Ruby/ext/capture/port-audio/patches
160 changes: 160 additions & 0 deletions swig/Ruby/ext/capture/port-audio/port-audio-capture.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// bindings/Ruby/ext/capture/port-audio/port-audio-capture.c

// Copyright 2017 KITT.AI (author: Guoguo Chen, PpiBbuRr)

#include <assert.h>
#include <pa_ringbuffer.h>
#include <pa_util.h>
#include <portaudio.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

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;
}
53 changes: 53 additions & 0 deletions swig/Ruby/ext/capture/port-audio/port-audio-capture.mk
Original file line number Diff line number Diff line change
@@ -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
Loading