threshold: 1.0, # defaults to 0.5
min_speech_duration_ms: 500, # defaults to 250
min_silence_duration_ms: 200, # defaults to 100
- max_speech_duration_s: 30000, # default is FLT_MAX,
+ max_speech_duration_s: 30000.0, # default is FLT_MAX,
speech_pad_ms: 50, # defaults to 30
samples_overlap: 0.5 # defaults to 0.1
),
Boolean options:
* `-DGGML_BLAS=1` -> `--enable-ggml-blas`
-* `-DWHISER_COREML=OFF` -> `--disable-whisper-coreml`
+* `-DWHISPER_COREML=OFF` -> `--disable-whisper-coreml`
Argument options:
Combination:
-* `-DGGML_CUDA=1 -DCMAKE_CUDA_ARCHITECTURES="86"` -> `--enable-ggml-cuda --cmake_cuda-architectures="86"`
+* `-DGGML_CUDA=1 -DCMAKE_CUDA_ARCHITECTURES="86"` -> `--enable-ggml-cuda --cmake-cuda-architectures="86"`
For boolean options like `GGML_CUDA`, the README says `-DGGML_CUDA=1`. You need strip `-D`, prepend `--enable-` for `1` or `ON` (`--disable-` for `0` or `OFF`) and make it kebab-case: `--enable-ggml-cuda`.
For options which require arguments like `CMAKE_CUDA_ARCHITECTURES`, the README says `-DCMAKE_CUDA_ARCHITECTURES="86"`. You need strip `-D`, prepend `--`, make it kebab-case, append `=` and append argument: `--cmake-cuda-architectures="86"`.
% cd whisper.cpp/bindings/ruby
% rake test
-First call of `rake test` builds an extension and downloads a model for testing. After that, you add tests in `tests` directory and modify `ext/ruby_whisper.cpp`.
+First call of `rake test` builds an extension and downloads a model for testing. After that, you add tests in `test` directory and modify `ext/*.{h,c,cpp}`.
If something seems wrong on build, running `rake clean` solves some cases.
### Need help ###
* Windows support
+* Check of compilation with various hardware
* Refinement of C/C++ code, especially memory management
License
FileUtils.mkpath File.dirname(cache_path)
File.open cache_path, "w" do |file|
@options.reject {|name, (type, value)| value.nil?}.each do |name, (type, value)|
- line = "set(CACHE{%<name>s} TYPE %<type>s FORCE VALUE %<value>s)" % {
+ line = 'set(%<name>s %<value>s CACHE %<type>s "" FORCE)' %{
name:,
type:,
value: value == true ? "ON" : value == false ? "OFF" : escape_cmake(value)
id_coreml_compiled_models = rb_intern("coreml_compiled_models");
id_cache = rb_intern("cache");
id_n_processors = rb_intern("n_processors");
- id_extended = rb_intern("extended");
id_start_log_callback_thread = rb_intern("start_log_callback_thread");
id_log_callback_thread = rb_intern("@log_callback_thread");
id_alive_p = rb_intern("alive?");
int n_samples;
} ruby_whisper_full_args;
+typedef struct segments_from_samples_args {
+ VALUE *context;
+ VALUE *params;
+ float *samples;
+ int n_samples;
+} segments_from_samples_args;
+
typedef struct ruby_whisper_full_parallel_args {
VALUE *context;
VALUE *params;
VALUE context;
} ruby_whisper_parakeet_model;
-extern ID id_extended;
extern ID id_log_callback_thread;
extern ID id_start_log_callback_thread;
extern ID id_alive_p;
if (RB_TYPE_P(*args->src, T_ARRAY)) {
for (int i = 0; i < args->n_samples; i++) {
- args->dest[i] = RFLOAT_VALUE(rb_ary_entry(*args->src, i));
+ VALUE sample = rb_ary_entry(*args->src, i);
+ if (!RB_FLOAT_TYPE_P(sample)) {
+ sample = rb_to_float(sample);
+ }
+ args->dest[i] = RFLOAT_VALUE(sample);
}
} else {
// TODO: use rb_block_call
for (int i = 0; i < args->n_samples; i++) {
// TODO: check if iter is exhausted and raise ArgumentError appropriately
VALUE sample = rb_funcall(iter, id_next, 0);
+ if (!RB_FLOAT_TYPE_P(sample)) {
+ sample = rb_to_float(sample);
+ }
args->dest[i] = RFLOAT_VALUE(sample);
}
}
return DBL2NUM(no_speech_prob);
}
+static VALUE
+ruby_whisper_full_n_vad_segments(VALUE self)
+{
+ ruby_whisper *rw;
+ GetContext(self, rw);
+
+ return INT2NUM(whisper_full_n_vad_segments(rw->context));
+}
+
+static int
+ruby_whisper_full_check_vad_segment_index(const ruby_whisper *rw, const VALUE i_segment)
+{
+ const int c_i_segment = NUM2INT(i_segment);
+ if (c_i_segment < 0 || c_i_segment >= whisper_full_n_vad_segments(rw->context)) {
+ rb_raise(rb_eIndexError, "segment index %d out of range", c_i_segment);
+ }
+ return c_i_segment;
+}
+
+static VALUE
+ruby_whisper_full_get_vad_segment_t0(VALUE self, VALUE i_segment)
+{
+ ruby_whisper *rw;
+ GetContext(self, rw);
+ const int c_i_segment = ruby_whisper_full_check_vad_segment_index(rw, i_segment);
+
+ return LONG2NUM(whisper_full_get_vad_segment_t0(rw->context, c_i_segment));
+}
+
+static VALUE
+ruby_whisper_full_get_vad_segment_t1(VALUE self, VALUE i_segment)
+{
+ ruby_whisper *rw;
+ GetContext(self, rw);
+ const int c_i_segment = ruby_whisper_full_check_vad_segment_index(rw, i_segment);
+
+ return LONG2NUM(whisper_full_get_vad_segment_t1(rw->context, c_i_segment));
+}
+
// High level API
static VALUE
rb_define_method(cContext, "full_get_segment_speaker_turn_next", ruby_whisper_full_get_segment_speaker_turn_next, 1);
rb_define_method(cContext, "full_get_segment_text", ruby_whisper_full_get_segment_text, 1);
rb_define_method(cContext, "full_get_segment_no_speech_prob", ruby_whisper_full_get_segment_no_speech_prob, 1);
+ rb_define_method(cContext, "full_n_vad_segments", ruby_whisper_full_n_vad_segments, 0);
+ rb_define_method(cContext, "full_get_vad_segment_t0", ruby_whisper_full_get_vad_segment_t0, 1);
+ rb_define_method(cContext, "full_get_vad_segment_t1", ruby_whisper_full_get_vad_segment_t1, 1);
rb_define_method(cContext, "full", ruby_whisper_full, -1);
rb_define_method(cContext, "full_parallel", ruby_whisper_full_parallel, -1);
rb_define_singleton_method(mod, "drain_logs", ruby_whisper_##log_queue##_s_drain_logs, 0); \
rb_define_singleton_method(mod, "log_set", ruby_whisper_##log_queue##_s_log_set, 2); \
rb_set_end_proc(ruby_whisper_##log_queue##_end_proc, Qnil); \
- rb_extend_object(mod, mLogSettable); \
- rb_funcall(mLogSettable, id_extended, 1, mod);
+ rb_extend_object(mod, mLogSettable);
#endif
rwp->params.vad_model_path = ruby_strdup(rwp->params.vad_model_path);
}
rwp->diarize = false;
- rwp->vad_params = TypedData_Wrap_Struct(cVADParams, &ruby_whisper_vad_params_type, (void *)&rwp->params.vad_params);
+ rwp->vad_params = rb_class_new_instance(0, NULL, cVADParams);
rwp->new_segment_callback_container = ruby_whisper_callback_container_allocate();
rwp->progress_callback_container = ruby_whisper_callback_container_allocate();
rwp->encoder_begin_callback_container = ruby_whisper_callback_container_allocate();
{
ruby_whisper_params *rwp;
TypedData_Get_Struct(self, ruby_whisper_params, &ruby_whisper_params_type, rwp);
- rwp->params.temperature = RFLOAT_VALUE(value);
+ rwp->params.temperature = NUM2DBL(value);
return value;
}
/*
{
ruby_whisper_params *rwp;
TypedData_Get_Struct(self, ruby_whisper_params, &ruby_whisper_params_type, rwp);
- rwp->params.max_initial_ts = RFLOAT_VALUE(value);
+ rwp->params.max_initial_ts = NUM2DBL(value);
return value;
}
/*
{
ruby_whisper_params *rwp;
TypedData_Get_Struct(self, ruby_whisper_params, &ruby_whisper_params_type, rwp);
- rwp->params.length_penalty = RFLOAT_VALUE(value);
+ rwp->params.length_penalty = NUM2DBL(value);
return value;
}
/*
{
ruby_whisper_params *rwp;
TypedData_Get_Struct(self, ruby_whisper_params, &ruby_whisper_params_type, rwp);
- rwp->params.temperature_inc = RFLOAT_VALUE(value);
+ rwp->params.temperature_inc = NUM2DBL(value);
return value;
}
/*
{
ruby_whisper_params *rwp;
TypedData_Get_Struct(self, ruby_whisper_params, &ruby_whisper_params_type, rwp);
- rwp->params.entropy_thold = RFLOAT_VALUE(value);
+ rwp->params.entropy_thold = NUM2DBL(value);
return value;
}
/*
{
ruby_whisper_params *rwp;
TypedData_Get_Struct(self, ruby_whisper_params, &ruby_whisper_params_type, rwp);
- rwp->params.logprob_thold = RFLOAT_VALUE(value);
+ rwp->params.logprob_thold = NUM2DBL(value);
return value;
}
/*
{
ruby_whisper_params *rwp;
TypedData_Get_Struct(self, ruby_whisper_params, &ruby_whisper_params_type, rwp);
- rwp->params.no_speech_thold = RFLOAT_VALUE(value);
+ rwp->params.no_speech_thold = NUM2DBL(value);
return value;
}
static VALUE
ruby_whisper_params_set_vad_params(VALUE self, VALUE value)
{
ruby_whisper_params *rwp;
+ rb_check_typeddata(value, &ruby_whisper_vad_params_type);
TypedData_Get_Struct(self, ruby_whisper_params, &ruby_whisper_params_type, rwp);
rwp->vad_params = value;
return value;
extern ID id_to_path;
extern ID transcribe_option_names[1];
-extern void prepare_transcription(ruby_whisper_params * rwp, VALUE * self, int n_processors);
extern VALUE full_body(VALUE rb_args);
extern VALUE full_parallel_body(VALUE rb_args);
-typedef struct{
- struct whisper_context *context;
- struct whisper_full_params *params;
- float *samples;
- size_t n_samples;
- int n_processors;
- int result;
-} transcribe_without_gvl_args;
-
-static void*
-transcribe_without_gvl(void *rb_args)
-{
- transcribe_without_gvl_args *args = (transcribe_without_gvl_args *)rb_args;
- args->result = whisper_full_parallel(args->context, *args->params, args->samples, args->n_samples, args->n_processors);
-
- return NULL;
-}
-
/*
* transcribe a single file
* can emit to a block results
extern VALUE ruby_whisper_vad_segments_s_init(struct whisper_vad_segments *segments);
-typedef struct segments_from_samples_args {
- VALUE *context;
- VALUE *params;
- float *samples;
- int n_samples;
-} segments_from_samples_args;
-
static size_t
ruby_whisper_vad_context_memsize(const void *p)
{
return Qnil;
}
-static VALUE
+typedef struct {
+ struct whisper_vad_context *context;
+ struct whisper_vad_params *params;
+ float *samples;
+ int n_samples;
+ struct whisper_vad_segments *result;
+} segments_from_samples_without_gvl_args;
+
+static void*
+segments_from_samples_without_gvl(void *rb_args)
+{
+ segments_from_samples_without_gvl_args *args = (segments_from_samples_without_gvl_args *)rb_args;
+ args->result = whisper_vad_segments_from_samples(args->context, *args->params, args->samples, args->n_samples);
+
+ return NULL;
+}
+
+VALUE
segments_from_samples_body(VALUE rb_args)
{
segments_from_samples_args *args = (segments_from_samples_args *)rb_args;
GetVADContext(*args->context, rwvc);
GetVADParams(*args->params, rwvp);
- struct whisper_vad_segments *segments = whisper_vad_segments_from_samples(rwvc->context, rwvp->params, args->samples, args->n_samples);
+ segments_from_samples_without_gvl_args segments_from_samples_without_gvl_args = {
+ rwvc->context,
+ &rwvp->params,
+ args->samples,
+ args->n_samples,
+ NULL
+ };
+ rb_thread_call_without_gvl(segments_from_samples_without_gvl, (void *)&segments_from_samples_without_gvl_args, NULL, NULL);
+ if (!segments_from_samples_without_gvl_args.result) {
+ rb_raise(rb_eRuntimeError, "Failed to process audio");
+ }
- return ruby_whisper_vad_segments_s_init(segments);
+ return ruby_whisper_vad_segments_s_init(segments_from_samples_without_gvl_args.result);
}
static VALUE
extern const rb_data_type_t ruby_whisper_vad_segments_type;
extern VALUE ruby_whisper_vad_segments_s_init(struct whisper_vad_segments *segments);
+extern VALUE segments_from_samples_body(VALUE rb_args);
VALUE
ruby_whisper_vad_detect(VALUE self, VALUE file_path, VALUE params) {
ruby_whisper_vad_context *rwvc;
- ruby_whisper_vad_params *rwvp;
std::string cpp_file_path;
std::vector<float> pcmf32;
std::vector<std::vector<float>> pcmf32s;
- whisper_vad_segments *segments;
GetVADContext(self, rwvc);
- TypedData_Get_Struct(params, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
if (rb_respond_to(file_path, id_to_path)) {
file_path = rb_funcall(file_path, id_to_path, 0);
rb_raise(rb_eRuntimeError, "Failed to open '%s' as WAV file\n", cpp_file_path.c_str());
}
- segments = whisper_vad_segments_from_samples(rwvc->context, rwvp->params, pcmf32.data(), pcmf32.size());
- if (segments == nullptr) {
- rb_raise(rb_eRuntimeError, "Failed to process audio\n");
- }
-
- return ruby_whisper_vad_segments_s_init(segments);
+ segments_from_samples_args args = {
+ &self,
+ ¶ms,
+ pcmf32.data(),
+ (int)pcmf32.size(),
+ };
+ return segments_from_samples_body((VALUE)&args);
}
#ifdef __cplusplus
const rb_data_type_t ruby_whisper_vad_params_type = {
"ruby_whisper_vad_params",
- {0, 0, ruby_whisper_vad_params_memsize,},
+ {0, RUBY_DEFAULT_FREE, ruby_whisper_vad_params_memsize,},
0, 0,
0
};
{
ruby_whisper_vad_params *rwvp;
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
- rwvp->params.threshold = RFLOAT_VALUE(value);
+ rwvp->params.threshold = NUM2DBL(value);
return value;
}
{
ruby_whisper_vad_params *rwvp;
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
- rwvp->params.max_speech_duration_s = RFLOAT_VALUE(value);
+ rwvp->params.max_speech_duration_s = NUM2DBL(value);
return value;
}
{
ruby_whisper_vad_params *rwvp;
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
- rwvp->params.samples_overlap = RFLOAT_VALUE(value);
+ rwvp->params.samples_overlap = NUM2DBL(value);
return value;
}
TypedData_Get_Struct(self, ruby_whisper_vad_segment, &ruby_whisper_vad_segment_type, rwvs);
TypedData_Get_Struct(rwvs->segments, ruby_whisper_vad_segments, &ruby_whisper_vad_segments_type, rwvss);
t0 = whisper_vad_segments_get_segment_t0(rwvss->segments, rwvs->index);
- return DBL2NUM(t0 * 10);
+ return LONG2NUM(t0 * 10);
}
static VALUE
TypedData_Get_Struct(self, ruby_whisper_vad_segment, &ruby_whisper_vad_segment_type, rwvs);
TypedData_Get_Struct(rwvs->segments, ruby_whisper_vad_segments, &ruby_whisper_vad_segments_type, rwvss);
t1 = whisper_vad_segments_get_segment_t1(rwvss->segments, rwvs->index);
- return DBL2NUM(t1 * 10);
+ return LONG2NUM(t1 * 10);
}
static VALUE
-require "mutex_m"
-
module Whisper
module LogSettable
- class << self
- def extended(base)
- base.extend Mutex_m
- end
- end
-
private
def start_log_callback_thread
end
}
end
+
+ def synchronize(&block)
+ @mutex ||= Thread::Mutex.new
+ @mutex.synchronize &block
+ end
end
end
return path if cache_path.exist?
headers = {}
- headers["if-modified-since"] = path.mtime.httpdate if path.exist?
request @uri, headers
path
end
request = Net::HTTP::Get.new(uri, headers)
http.request request do |response|
case response
- when Net::HTTPNotModified
- # noop
when Net::HTTPOK
- return if !response.key?("last-modified") && cache_path.exist?
-
download response
when Net::HTTPRedirection
request URI(response["location"]), headers
else
- return if headers.key?("if-modified-since") # Use cache file
-
raise "#{response.code} #{response.message}\n#{response.body}"
end
end
def full_get_segment_no_speech_prob: (Integer) -> Float
+ def full_n_vad_segments: () -> Integer
+
+ def full_get_vad_segment_t0: (Integer) -> Integer
+
+ def full_get_vad_segment_t1: (Integer) -> Integer
+
# Run the entire model: PCM -> log mel spectrogram -> encoder -> decoder -> text
# Not thread safe for same context
# Uses the specified decoding strategy to obtain the text.
assert_match(/ask not what your country can do for you[,.] ask what you can do for your country/i, text)
end
end
+
+ def test_vad_segments_api
+ @whisper.transcribe TestBase::AUDIO, @params
+
+ assert_equal 4, @whisper.full_n_vad_segments
+ @whisper.full_n_vad_segments.times do |i|
+ assert @whisper.full_get_vad_segment_t0(i) < @whisper.full_get_vad_segment_t1(i)
+ end
+ end
end
assert_instance_of Enumerator, segments.each
segment = segments.each.first
- assert_instance_of Float, segment.start_time
- assert_instance_of Float, segment.end_time
+ assert_instance_of Integer, segment.start_time
+ assert_instance_of Integer, segment.end_time
segment => {start_time:, end_time:}
assert_equal segment.start_time, start_time
Gem::Specification.new do |s|
s.name = "whispercpp"
s.authors = ["Georgi Gerganov", "Todd A. Fisher"]
- s.version = '1.3.7'
+ s.version = '1.3.8'
s.description = %q{High-performance inference of OpenAI's Whisper automatic speech recognition (ASR) model via Ruby}
s.email = 'todd.fisher@gmail.com'
s.extra_rdoc_files = ['LICENSE', 'README.md']