Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
716eec4
Bump version
KitaitiMakoto Jun 17, 2026
21db4ba
Add test for VAD segments API
KitaitiMakoto Jul 6, 2026
d7e0a59
Implement VAD segments API
KitaitiMakoto Jul 6, 2026
c92d125
Add signatures of VAD segments API
KitaitiMakoto Jul 6, 2026
19e5dc1
Stop to dependent on mutex_m
KitaitiMakoto Jul 13, 2026
5273f5c
Check segment range for vad_segment_t0/t1
KitaitiMakoto Jul 13, 2026
6f91c60
Remove needless check
KitaitiMakoto Jul 13, 2026
0e4da25
Remove dead codes
KitaitiMakoto Jul 13, 2026
959ebd6
Fix index check for VAD segments
KitaitiMakoto Jul 13, 2026
81a7cff
Remove needless line
KitaitiMakoto Jul 13, 2026
80a6a65
Use NUM2DBL instead of RFLOAT_VALUE for safety
KitaitiMakoto Jul 13, 2026
48e0deb
Fix document
KitaitiMakoto Jul 13, 2026
a434268
Check type of samples
KitaitiMakoto Jul 13, 2026
7b3885d
Arrange CMake cache notation for older version
KitaitiMakoto Jul 13, 2026
38027bc
Remove unnecessary line break
KitaitiMakoto Jul 13, 2026
0952f6f
Check return value of whisper_vad_segments_from_samples
KitaitiMakoto Jul 13, 2026
3faf33a
Fix type of VAD::Segment#start_time and #end_time
KitaitiMakoto Jul 13, 2026
72d881a
Prevent dangling pointer from Params to VAD::Params
KitaitiMakoto Jul 14, 2026
aca173f
Fix typos
KitaitiMakoto Jul 14, 2026
f922dc8
Update README
KitaitiMakoto Jul 14, 2026
2e4fcf5
Make test follow fix of impl
KitaitiMakoto Jul 14, 2026
24438a2
Use segments_from_samples_body in ruby_whisper_vad_detect
KitaitiMakoto Jul 14, 2026
ed2a208
Run segments_from_samples without GVL
KitaitiMakoto Jul 14, 2026
91f7c4e
Remove unnecessary codes
KitaitiMakoto Jul 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions bindings/ruby/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ Whisper::Params.new(
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
),
Expand Down Expand Up @@ -176,15 +176,15 @@ See whisper.cpp's [README](https://github.com/ggml-org/whisper.cpp/blob/master/R
Boolean options:

* `-DGGML_BLAS=1` -> `--enable-ggml-blas`
* `-DWHISER_COREML=OFF` -> `--disable-whisper-coreml`
* `-DWHISPER_COREML=OFF` -> `--disable-whisper-coreml`

Argument options:

* `-DGGML_CUDA_COMPRESSION_MODE=size` -> `--ggml-cuda-compression-mode=size`

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"`.
Expand Down Expand Up @@ -478,13 +478,14 @@ Development
% 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
Expand Down
2 changes: 1 addition & 1 deletion bindings/ruby/ext/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def write_cache_file
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)
Expand Down
1 change: 0 additions & 1 deletion bindings/ruby/ext/ruby_whisper.c
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ void Init_whisper() {
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?");
Expand Down
8 changes: 7 additions & 1 deletion bindings/ruby/ext/ruby_whisper.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ typedef struct {
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;
Expand Down Expand Up @@ -149,7 +156,6 @@ typedef struct {
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;
Expand Down
51 changes: 50 additions & 1 deletion bindings/ruby/ext/ruby_whisper_context.c
Original file line number Diff line number Diff line change
Expand Up @@ -349,14 +349,21 @@ fill_samples(VALUE rb_args)

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
VALUE iter = rb_funcall(*args->src, id_to_enum, 1, rb_str_new2("each"));
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);
}
}
Expand Down Expand Up @@ -745,6 +752,45 @@ ruby_whisper_full_get_segment_no_speech_prob(VALUE self, VALUE i_segment)
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
Expand Down Expand Up @@ -830,6 +876,9 @@ init_ruby_whisper_context(VALUE *mWhisper)
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);

Expand Down
3 changes: 1 addition & 2 deletions bindings/ruby/ext/ruby_whisper_log_settable.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
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
17 changes: 9 additions & 8 deletions bindings/ruby/ext/ruby_whisper_params.c
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ ruby_whisper_params_allocate(VALUE klass)
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();
Expand Down Expand Up @@ -918,7 +918,7 @@ ruby_whisper_params_set_temperature(VALUE self, VALUE value)
{
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;
}
/*
Expand All @@ -943,7 +943,7 @@ ruby_whisper_params_set_max_initial_ts(VALUE self, VALUE 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;
}
/*
Expand All @@ -966,7 +966,7 @@ ruby_whisper_params_set_length_penalty(VALUE self, VALUE 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;
}
/*
Expand All @@ -989,7 +989,7 @@ ruby_whisper_params_set_temperature_inc(VALUE self, VALUE 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;
}
/*
Expand All @@ -1014,7 +1014,7 @@ ruby_whisper_params_set_entropy_thold(VALUE self, VALUE 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;
}
/*
Expand All @@ -1037,7 +1037,7 @@ ruby_whisper_params_set_logprob_thold(VALUE self, VALUE 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;
}
/*
Expand All @@ -1060,7 +1060,7 @@ ruby_whisper_params_set_no_speech_thold(VALUE self, VALUE 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
Expand Down Expand Up @@ -1307,6 +1307,7 @@ 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;
Expand Down
19 changes: 0 additions & 19 deletions bindings/ruby/ext/ruby_whisper_transcribe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,9 @@ extern ID id_call;
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
Expand Down
40 changes: 30 additions & 10 deletions bindings/ruby/ext/ruby_whisper_vad_context.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,6 @@ extern VALUE release_samples(VALUE parsed);

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)
{
Expand Down Expand Up @@ -77,7 +70,24 @@ ruby_whisper_vad_context_initialize(VALUE self, VALUE model_path)
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;
Expand All @@ -87,9 +97,19 @@ segments_from_samples_body(VALUE 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
Expand Down
17 changes: 8 additions & 9 deletions bindings/ruby/ext/ruby_whisper_vad_context_detect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,16 @@ extern const rb_data_type_t ruby_whisper_vad_params_type;
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);
Expand All @@ -38,12 +36,13 @@ ruby_whisper_vad_detect(VALUE self, VALUE file_path, VALUE params) {
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,
&params,
pcmf32.data(),
(int)pcmf32.size(),
};
return segments_from_samples_body((VALUE)&args);
}

#ifdef __cplusplus
Expand Down
Loading
Loading