From f3b2ed68e5f9a5a09dd6d8c619c8b7dc3470e408 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 20 Apr 2026 10:18:51 +0900 Subject: [PATCH 001/118] Add Whisper::Parakeet::Params --- bindings/ruby/ext/ruby_whisper.c | 5 + bindings/ruby/ext/ruby_whisper.h | 5 + .../ruby/ext/ruby_whisper_parakeet_params.c | 138 ++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 bindings/ruby/ext/ruby_whisper_parakeet_params.c diff --git a/bindings/ruby/ext/ruby_whisper.c b/bindings/ruby/ext/ruby_whisper.c index 56fceb1c894..5c74e9c0743 100644 --- a/bindings/ruby/ext/ruby_whisper.c +++ b/bindings/ruby/ext/ruby_whisper.c @@ -2,12 +2,14 @@ VALUE mWhisper; VALUE mVAD; +VALUE mParakeet; VALUE cContext; VALUE cParams; VALUE cVADContext; VALUE cVADParams; VALUE cVADSegments; VALUE cVADSegment; +VALUE cParakeetParams; VALUE eError; VALUE cSegment; @@ -45,6 +47,7 @@ extern void init_ruby_whisper_vad_params(VALUE *mVAD); extern void init_ruby_whisper_vad_context(VALUE *mVAD); extern void init_ruby_whisper_vad_segment(VALUE *mVAD); extern void init_ruby_whisper_vad_segments(VALUE *mVAD); +extern void init_ruby_whisper_parakeet_params(VALUE *mParakeet); extern void register_callbacks(ruby_whisper_params *rwp, VALUE *context); /* @@ -192,6 +195,7 @@ void Init_whisper() { mWhisper = rb_define_module("Whisper"); mVAD = rb_define_module_under(mWhisper, "VAD"); + mParakeet = rb_define_module_under(mWhisper, "Parakeet"); rb_define_const(mWhisper, "VERSION", rb_str_new2(whisper_version())); rb_define_const(mWhisper, "LOG_LEVEL_NONE", INT2NUM(GGML_LOG_LEVEL_NONE)); @@ -236,6 +240,7 @@ void Init_whisper() { init_ruby_whisper_vad_segment(&mVAD); init_ruby_whisper_vad_segments(&mVAD); init_ruby_whisper_vad_context(&mVAD); + init_ruby_whisper_parakeet_params(&mParakeet); rb_require("whisper/context"); rb_require("whisper/segment"); diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index ba4d8b6fbcc..a94797b6eb8 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -7,6 +7,7 @@ #include #include #include "whisper.h" +#include "parakeet.h" #if RUBY_API_VERSION_MAJOR < 4 // Exists but not declared as public API @@ -84,6 +85,10 @@ typedef struct parsed_samples_t { bool memview_exported; } parsed_samples_t; +typedef struct { + struct parakeet_full_params params; +} ruby_whisper_parakeet_params; + #define GetContext(obj, rw) do { \ TypedData_Get_Struct((obj), ruby_whisper, &ruby_whisper_type, (rw)); \ if ((rw)->context == NULL) { \ diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c new file mode 100644 index 00000000000..6272e3bfb69 --- /dev/null +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -0,0 +1,138 @@ +#include "ruby_whisper.h" + +#define ITERATE_PARAMS(ITERATOR) \ + ITERATOR(n_threads, INT) \ + ITERATOR(offset_ms, INT) \ + ITERATOR(duration_ms, INT) \ + ITERATOR(no_context, BOOL) \ + ITERATOR(audio_ctx, INT) \ + ITERATOR(chunk_length_ms, INT) \ + ITERATOR(left_context_ms, INT) \ + ITERATOR(right_context_ms, INT) + +enum { +#define DEF_IDX(name, type) RUBY_WHISPER_PARAKEET_PARAM_##name, + ITERATE_PARAMS(DEF_IDX) +#undef DEF_IDX + RUBY_WHISPER_PARAKEET_NUM_PARAMS +}; + +#define VAL_TO_INT(v) (NUM2INT(v)) +#define VAL_FROM_INT(v) (INT2NUM(v)) +#define VAL_TO_BOOL(v) (RTEST(v)) +#define VAL_FROM_BOOL(v) (v ? Qtrue : Qfalse) + +static ID param_names[RUBY_WHISPER_PARAKEET_NUM_PARAMS]; +typedef VALUE (*param_writer_t)(VALUE, VALUE); +static param_writer_t param_writers[RUBY_WHISPER_PARAKEET_NUM_PARAMS]; + +static void +ruby_whisper_parakeet_params_mark(void *p) +{ +} + +static void +ruby_whisper_parakeet_params_free(void *p) +{ +} + +static size_t +ruby_whisper_parakeet_params_memsize(const void *p) +{ + const struct ruby_whisper_parakeet_params *params = p; + size_t size = sizeof(params); + if (!params) { + return 0; + } + return size; +} + +const rb_data_type_t ruby_whisper_parakeet_params_type = { + "ruby_whisper_parakeet_params", + {ruby_whisper_parakeet_params_mark, ruby_whisper_parakeet_params_free, ruby_whisper_parakeet_params_memsize,}, + 0, 0, + 0 +}; + +#define DEF_PARAM_ATTR(name, type) \ + static VALUE \ + ruby_whisper_parakeet_params_get_##name(VALUE self) \ + { \ + ruby_whisper_parakeet_params *rwpp; \ + TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); \ + return VAL_FROM_##type(rwpp->params.name); \ + } \ + static VALUE \ + ruby_whisper_parakeet_params_set_##name(VALUE self, VALUE val) \ + { \ + ruby_whisper_parakeet_params *rwpp; \ + TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); \ + rwpp->params.name = VAL_TO_##type(val); \ + return val; \ + } + +ITERATE_PARAMS(DEF_PARAM_ATTR) + +#undef DEF_PARAM_ATTR + +static VALUE +ruby_whisper_parakeet_params_s_allocate(VALUE klass) +{ + ruby_whisper_parakeet_params *rwpp; + VALUE obj = TypedData_Make_Struct(klass, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); + rwpp->params = parakeet_full_default_params(PARAKEET_SAMPLING_GREEDY); + return obj; +} + +static VALUE +ruby_whisper_parakeet_params_initialize(int argc, VALUE *argv, VALUE self) +{ + VALUE kw_hash; + VALUE values[RUBY_WHISPER_PARAKEET_NUM_PARAMS] = {Qundef}; + VALUE id; + VALUE value; + ruby_whisper_parakeet_params *rwpp; + int i; + + rb_scan_args_kw(RB_SCAN_ARGS_KEYWORDS, argc, argv, ":", &kw_hash); + if (NIL_P(kw_hash)) { + return Qnil; + } + + rb_get_kwargs(kw_hash, param_names, 0, RUBY_WHISPER_PARAKEET_NUM_PARAMS, values); + TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); + + for (i = 0; i < RUBY_WHISPER_PARAKEET_NUM_PARAMS; i++) { + id = param_names[i]; + value = values[i]; + if (value == Qundef) { + continue; + } + param_writers[i](self, value); + } + + return Qnil; +} + +void +init_ruby_whisper_parakeet_params(VALUE *mParakeet) +{ + VALUE cParakeetParams = rb_define_class_under(*mParakeet, "Params", rb_cObject); + rb_define_alloc_func(cParakeetParams, ruby_whisper_parakeet_params_s_allocate); + + rb_define_method(cParakeetParams, "initialize", ruby_whisper_parakeet_params_initialize, -1); + + int i = 0; +#define REGISTER_PARAM_ATTR(name, type) \ + param_names[i] = rb_intern(#name); \ + param_writers[i] = ruby_whisper_parakeet_params_set_##name; \ + rb_define_method(cParakeetParams, #name, ruby_whisper_parakeet_params_get_##name, 0); \ + rb_define_method(cParakeetParams, #name "=", ruby_whisper_parakeet_params_set_##name, 1); \ + i++; + + ITERATE_PARAMS(REGISTER_PARAM_ATTR) + +#undef REGISTER_PARAM_ATTR +} + +#undef ITERATE_PARAMS From 30abb35db820dc7edaffdff4e1723abb64dd2a2c Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 20 Apr 2026 10:19:03 +0900 Subject: [PATCH 002/118] Add tests for Parakeet::Params --- bindings/ruby/test/test_parakeet_params.rb | 101 +++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 bindings/ruby/test/test_parakeet_params.rb diff --git a/bindings/ruby/test/test_parakeet_params.rb b/bindings/ruby/test/test_parakeet_params.rb new file mode 100644 index 00000000000..d50ecebb7fe --- /dev/null +++ b/bindings/ruby/test/test_parakeet_params.rb @@ -0,0 +1,101 @@ +require_relative "helper" + +class TestParakeetParams < TestBase + PARAM_NAMES = [ + :n_threads, + :offset_ms, + :duration_ms, + :no_context, + :audio_ctx, + :chunk_length_ms, + :left_context_ms, + :right_context_ms + ] + + def setup + @params = Whisper::Parakeet::Params.new + end + + def test_new + assert_instance_of Whisper::Parakeet::Params, @params + end + + def test_n_threads + assert_equal [4, Etc.nprocessors].min, @params.n_threads + + @params.n_threads = 1 + assert_equal 1, @params.n_threads + end + + def test_offset_ms + assert_equal 0, @params.offset_ms + + @params.offset_ms = 10_000 + assert_equal 10_000, @params.offset_ms + end + + def test_duration_ms + assert_equal 0, @params.duration_ms + + @params.duration_ms = 60_000 + assert_equal 60_000, @params.duration_ms + end + + def test_no_context + assert_equal true, @params.no_context + + @params.no_context = false + assert_equal false, @params.no_context + end + + def test_audio_ctx + assert_equal 0, @params.audio_ctx + + @params.audio_ctx = 1 + assert_equal 1, @params.audio_ctx + end + + def test_chunk_length_ms + assert_equal 10_000, @params.chunk_length_ms + + @params.chunk_length_ms = 30_000 + assert_equal 30_000, @params.chunk_length_ms + end + + def test_left_context_ms + assert_equal 10_000, @params.left_context_ms + + @params.left_context_ms = 30_000 + assert_equal 30_000, @params.left_context_ms + end + + def test_right_context_ms + assert_equal 4_960, @params.right_context_ms + + @params.right_context_ms = 5_000 + assert_equal 5_000, @params.right_context_ms + end + + def test_new_with_kw_args + params = Whisper::Parakeet::Params.new(n_threads: 1) + assert_equal 1, params.n_threads + assert_equal 0, params.offset_ms + end + + data(PARAM_NAMES.collect {|param| [param, param]}.to_h) + def test_new_with_kw_args_default_values(param) + default_value = @params.send(param) + value = case [param, default_value] + in [*, true | false] + !default_value + in [*, Integer] + default_value + 1 + end + params = Whisper::Parakeet::Params.new(param => value) + assert_equal value, params.send(param) + + PARAM_NAMES.reject {|name| name == param}.each do |name| + assert_equal @params.send(name), params.send(name) + end + end +end From 703fe18e6021145c2a1ccc15a4a684789413671f Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 6 May 2026 19:36:41 +0900 Subject: [PATCH 003/118] Remove unused variabel --- bindings/ruby/ext/ruby_whisper_parakeet_params.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c index 6272e3bfb69..065aee1f8ce 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_params.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -89,7 +89,6 @@ ruby_whisper_parakeet_params_initialize(int argc, VALUE *argv, VALUE self) { VALUE kw_hash; VALUE values[RUBY_WHISPER_PARAKEET_NUM_PARAMS] = {Qundef}; - VALUE id; VALUE value; ruby_whisper_parakeet_params *rwpp; int i; @@ -103,7 +102,6 @@ ruby_whisper_parakeet_params_initialize(int argc, VALUE *argv, VALUE self) TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); for (i = 0; i < RUBY_WHISPER_PARAKEET_NUM_PARAMS; i++) { - id = param_names[i]; value = values[i]; if (value == Qundef) { continue; From 555569481cb3ff7fc5cde9b873702974208de701 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 6 May 2026 20:27:16 +0900 Subject: [PATCH 004/118] Add callbacks to Parakeet::Params --- bindings/ruby/ext/ruby_whisper.h | 5 + .../ruby/ext/ruby_whisper_parakeet_params.c | 131 +++++++++++++++--- bindings/ruby/ext/ruby_whisper_params.c | 14 +- 3 files changed, 127 insertions(+), 23 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index a94797b6eb8..07e5e0f1e75 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -87,6 +87,11 @@ typedef struct parsed_samples_t { typedef struct { struct parakeet_full_params params; + ruby_whisper_callback_container *new_segment_callback_container; + ruby_whisper_callback_container *new_token_callback_container; + ruby_whisper_callback_container *progress_callback_container; + ruby_whisper_callback_container *encoder_begin_callback_container; + ruby_whisper_callback_container *abort_callback_container; } ruby_whisper_parakeet_params; #define GetContext(obj, rw) do { \ diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c index 065aee1f8ce..a12d113e278 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_params.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -1,17 +1,27 @@ #include "ruby_whisper.h" #define ITERATE_PARAMS(ITERATOR) \ - ITERATOR(n_threads, INT) \ - ITERATOR(offset_ms, INT) \ - ITERATOR(duration_ms, INT) \ - ITERATOR(no_context, BOOL) \ - ITERATOR(audio_ctx, INT) \ - ITERATOR(chunk_length_ms, INT) \ - ITERATOR(left_context_ms, INT) \ - ITERATOR(right_context_ms, INT) + ITERATOR(n_threads, n_threads,INT) \ + ITERATOR(offset_ms, offset_ms, INT) \ + ITERATOR(duration_ms, duration_ms, INT) \ + ITERATOR(no_context, no_context, BOOL) \ + ITERATOR(audio_ctx, audio_ctx, INT) \ + ITERATOR(chunk_length_ms, chunk_length_ms, INT) \ + ITERATOR(left_context_ms, left_context_ms, INT) \ + ITERATOR(right_context_ms, right_context_ms, INT) \ + ITERATOR(new_segment_callback, new_segment_callback, CALLBACK) \ + ITERATOR(new_segment_callback_user_data, new_segment_callback, USER_DATA) \ + ITERATOR(new_token_callback, new_token_callback, CALLBACK) \ + ITERATOR(new_token_callback_user_data, new_token_callback, USER_DATA) \ + ITERATOR(progress_callback, progress_callback, CALLBACK) \ + ITERATOR(progress_callback_user_data, progress_callback, USER_DATA) \ + ITERATOR(encoder_begin_callback, encoder_begin_callback, CALLBACK) \ + ITERATOR(encoder_begin_callback_user_data, encoder_begin_callback, USER_DATA) \ + ITERATOR(abort_callback, abort_callback, CALLBACK) \ + ITERATOR(abort_callback_user_data, abort_callback, USER_DATA) enum { -#define DEF_IDX(name, type) RUBY_WHISPER_PARAKEET_PARAM_##name, +#define DEF_IDX(name, cb, type) RUBY_WHISPER_PARAKEET_PARAM_##name, ITERATE_PARAMS(DEF_IDX) #undef DEF_IDX RUBY_WHISPER_PARAKEET_NUM_PARAMS @@ -22,6 +32,9 @@ enum { #define VAL_TO_BOOL(v) (RTEST(v)) #define VAL_FROM_BOOL(v) (v ? Qtrue : Qfalse) +extern void ruby_whisper_callback_container_mark(ruby_whisper_callback_container *rwc); +extern ruby_whisper_callback_container* ruby_whisper_callback_container_allocate(void); + static ID param_names[RUBY_WHISPER_PARAKEET_NUM_PARAMS]; typedef VALUE (*param_writer_t)(VALUE, VALUE); static param_writer_t param_writers[RUBY_WHISPER_PARAKEET_NUM_PARAMS]; @@ -29,11 +42,34 @@ static param_writer_t param_writers[RUBY_WHISPER_PARAKEET_NUM_PARAMS]; static void ruby_whisper_parakeet_params_mark(void *p) { + ruby_whisper_parakeet_params *rwpp = (ruby_whisper_parakeet_params *)p; + ruby_whisper_callback_container_mark(rwpp->new_segment_callback_container); + ruby_whisper_callback_container_mark(rwpp->new_token_callback_container); + ruby_whisper_callback_container_mark(rwpp->progress_callback_container); + ruby_whisper_callback_container_mark(rwpp->encoder_begin_callback_container); + ruby_whisper_callback_container_mark(rwpp->abort_callback_container); } static void ruby_whisper_parakeet_params_free(void *p) { + ruby_whisper_parakeet_params *rwpp = (ruby_whisper_parakeet_params *)p; + if (rwpp->params.new_segment_callback_user_data) { + xfree(&rwpp->params.new_segment_callback_user_data); + } + if (rwpp->params.new_token_callback_user_data) { + xfree(&rwpp->params.new_token_callback_user_data); + } + if (rwpp->params.progress_callback_user_data) { + xfree(&rwpp->params.progress_callback_user_data); + } + if (rwpp->params.encoder_begin_callback_user_data) { + xfree(&rwpp->params.encoder_begin_callback_user_data); + } + if (rwpp->params.abort_callback_user_data) { + xfree(&rwpp->params.abort_callback_user_data); + } + xfree(rwpp); } static size_t @@ -54,26 +90,80 @@ const rb_data_type_t ruby_whisper_parakeet_params_type = { 0 }; -#define DEF_PARAM_ATTR(name, type) \ +#define DEF_BOOL_PARAM_ATTR(name, cb) \ static VALUE \ ruby_whisper_parakeet_params_get_##name(VALUE self) \ { \ ruby_whisper_parakeet_params *rwpp; \ TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); \ - return VAL_FROM_##type(rwpp->params.name); \ + return VAL_FROM_BOOL(rwpp->params.name); \ } \ static VALUE \ ruby_whisper_parakeet_params_set_##name(VALUE self, VALUE val) \ { \ ruby_whisper_parakeet_params *rwpp; \ TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); \ - rwpp->params.name = VAL_TO_##type(val); \ + rwpp->params.name = VAL_TO_BOOL(val); \ return val; \ } -ITERATE_PARAMS(DEF_PARAM_ATTR) +#define DEF_INT_PARAM_ATTR(name, cb) \ + static VALUE \ + ruby_whisper_parakeet_params_get_##name(VALUE self) \ + { \ + ruby_whisper_parakeet_params *rwpp; \ + TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); \ + return VAL_FROM_INT(rwpp->params.name); \ + } \ + static VALUE \ + ruby_whisper_parakeet_params_set_##name(VALUE self, VALUE val) \ + { \ + ruby_whisper_parakeet_params *rwpp; \ + TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); \ + rwpp->params.name = VAL_TO_INT(val); \ + return val; \ + } -#undef DEF_PARAM_ATTR +#define CALLBACK_CONTAINER_NAME(name) name ## _container + +#define DEF_CALLBACK_PARAM_ATTR(name, cb) \ + static VALUE \ + ruby_whisper_parakeet_params_get_##name(VALUE self) \ + { \ + ruby_whisper_parakeet_params *rwpp; \ + TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); \ + return rwpp->CALLBACK_CONTAINER_NAME(cb)->callback; \ + } \ + static VALUE \ + ruby_whisper_parakeet_params_set_##name(VALUE self, VALUE val) \ + { \ + ruby_whisper_parakeet_params *rwpp; \ + TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); \ + rwpp->CALLBACK_CONTAINER_NAME(cb)->callback = (val); \ + return val; \ + } + +#define DEF_USER_DATA_PARAM_ATTR(name, cb) \ + static VALUE \ + ruby_whisper_parakeet_params_get_##name(VALUE self) \ + { \ + ruby_whisper_parakeet_params *rwpp; \ + TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); \ + return rwpp->CALLBACK_CONTAINER_NAME(cb)->user_data; \ + } \ + static VALUE \ + ruby_whisper_parakeet_params_set_##name(VALUE self, VALUE val) \ + { \ + ruby_whisper_parakeet_params *rwpp; \ + TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); \ + rwpp->CALLBACK_CONTAINER_NAME(cb)->user_data = val; \ + return val; \ + } + +#define DEF_PARAM_ATTR(name, cb, type) DEF_PARAM_ATTR_I(name, cb, type) +#define DEF_PARAM_ATTR_I(name, cb, type) DEF_##type##_PARAM_ATTR(name, cb) + +ITERATE_PARAMS(DEF_PARAM_ATTR) static VALUE ruby_whisper_parakeet_params_s_allocate(VALUE klass) @@ -89,19 +179,28 @@ ruby_whisper_parakeet_params_initialize(int argc, VALUE *argv, VALUE self) { VALUE kw_hash; VALUE values[RUBY_WHISPER_PARAKEET_NUM_PARAMS] = {Qundef}; + VALUE id; VALUE value; ruby_whisper_parakeet_params *rwpp; int i; + TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); + + rwpp->new_segment_callback_container = ruby_whisper_callback_container_allocate(); + rwpp->new_token_callback_container = ruby_whisper_callback_container_allocate(); + rwpp->progress_callback_container = ruby_whisper_callback_container_allocate(); + rwpp->encoder_begin_callback_container = ruby_whisper_callback_container_allocate(); + rwpp->abort_callback_container = ruby_whisper_callback_container_allocate(); + rb_scan_args_kw(RB_SCAN_ARGS_KEYWORDS, argc, argv, ":", &kw_hash); if (NIL_P(kw_hash)) { return Qnil; } rb_get_kwargs(kw_hash, param_names, 0, RUBY_WHISPER_PARAKEET_NUM_PARAMS, values); - TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); for (i = 0; i < RUBY_WHISPER_PARAKEET_NUM_PARAMS; i++) { + id = param_names[i]; value = values[i]; if (value == Qundef) { continue; @@ -121,7 +220,7 @@ init_ruby_whisper_parakeet_params(VALUE *mParakeet) rb_define_method(cParakeetParams, "initialize", ruby_whisper_parakeet_params_initialize, -1); int i = 0; -#define REGISTER_PARAM_ATTR(name, type) \ +#define REGISTER_PARAM_ATTR(name, cb, type) \ param_names[i] = rb_intern(#name); \ param_writers[i] = ruby_whisper_parakeet_params_set_##name; \ rb_define_method(cParakeetParams, #name, ruby_whisper_parakeet_params_get_##name, 0); \ diff --git a/bindings/ruby/ext/ruby_whisper_params.c b/bindings/ruby/ext/ruby_whisper_params.c index 2aae7c12d19..a8632cd879e 100644 --- a/bindings/ruby/ext/ruby_whisper_params.c +++ b/bindings/ruby/ext/ruby_whisper_params.c @@ -76,8 +76,8 @@ static ID id_vad; static ID id_vad_model_path; static ID id_vad_params; -static void -rb_whisper_callbcack_container_mark(ruby_whisper_callback_container *rwc) +void +ruby_whisper_callbcack_container_mark(ruby_whisper_callback_container *rwc) { if (rwc == NULL) return; @@ -86,8 +86,8 @@ rb_whisper_callbcack_container_mark(ruby_whisper_callback_container *rwc) rb_gc_mark(rwc->callbacks); } -static ruby_whisper_callback_container* -rb_whisper_callback_container_allocate() { +ruby_whisper_callback_container* +ruby_whisper_callback_container_allocate() { ruby_whisper_callback_container *container; container = ALLOC(ruby_whisper_callback_container); container->context = NULL; @@ -492,9 +492,9 @@ ruby_whisper_params_allocate(VALUE klass) } rwp->diarize = false; rwp->vad_params = TypedData_Wrap_Struct(cVADParams, &ruby_whisper_vad_params_type, (void *)&rwp->params.vad_params); - rwp->new_segment_callback_container = rb_whisper_callback_container_allocate(); - rwp->progress_callback_container = rb_whisper_callback_container_allocate(); - rwp->encoder_begin_callback_container = rb_whisper_callback_container_allocate(); + 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(); rwp->abort_callback_container = rb_whisper_abort_callback_container_allocate(); return obj; } From f39b100bb0d1454e1296a410f3ad4ccbe41f1a66 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 6 May 2026 21:00:38 +0900 Subject: [PATCH 005/118] Group callback and user_data params --- .../ruby/ext/ruby_whisper_parakeet_params.c | 88 ++++++++++++------- 1 file changed, 56 insertions(+), 32 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c index a12d113e278..7bddf30cc40 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_params.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -1,28 +1,36 @@ #include "ruby_whisper.h" #define ITERATE_PARAMS(ITERATOR) \ - ITERATOR(n_threads, n_threads,INT) \ - ITERATOR(offset_ms, offset_ms, INT) \ - ITERATOR(duration_ms, duration_ms, INT) \ - ITERATOR(no_context, no_context, BOOL) \ - ITERATOR(audio_ctx, audio_ctx, INT) \ - ITERATOR(chunk_length_ms, chunk_length_ms, INT) \ - ITERATOR(left_context_ms, left_context_ms, INT) \ - ITERATOR(right_context_ms, right_context_ms, INT) \ - ITERATOR(new_segment_callback, new_segment_callback, CALLBACK) \ - ITERATOR(new_segment_callback_user_data, new_segment_callback, USER_DATA) \ - ITERATOR(new_token_callback, new_token_callback, CALLBACK) \ - ITERATOR(new_token_callback_user_data, new_token_callback, USER_DATA) \ - ITERATOR(progress_callback, progress_callback, CALLBACK) \ - ITERATOR(progress_callback_user_data, progress_callback, USER_DATA) \ - ITERATOR(encoder_begin_callback, encoder_begin_callback, CALLBACK) \ - ITERATOR(encoder_begin_callback_user_data, encoder_begin_callback, USER_DATA) \ - ITERATOR(abort_callback, abort_callback, CALLBACK) \ - ITERATOR(abort_callback_user_data, abort_callback, USER_DATA) + ITERATOR(n_threads, INT) \ + ITERATOR(offset_ms, INT) \ + ITERATOR(duration_ms, INT) \ + ITERATOR(no_context, BOOL) \ + ITERATOR(audio_ctx, INT) \ + ITERATOR(chunk_length_ms, INT) \ + ITERATOR(left_context_ms, INT) \ + ITERATOR(right_context_ms, INT) + +#define ITERATE_CALLBACK_PARAMS(ITERATOR) \ + ITERATOR(new_segment_callback) \ + ITERATOR(new_token_callback) \ + ITERATOR(progress_callback) \ + ITERATOR(encoder_begin_callback) \ + ITERATOR(abort_callback) + +#define ITERATE_USER_DATA_PARAMS(ITERATOR) \ + ITERATOR(new_segment_callback) \ + ITERATOR(new_token_callback) \ + ITERATOR(progress_callback) \ + ITERATOR(encoder_begin_callback) \ + ITERATOR(abort_callback) enum { -#define DEF_IDX(name, cb, type) RUBY_WHISPER_PARAKEET_PARAM_##name, +#define DEF_IDX(name, type) RUBY_WHISPER_PARAKEET_PARAM_##name, +#define DEF_IDX_CALLBACK(name) RUBY_WHISPER_PARAKEET_PARAM_##name, +#define DEF_IDX_USER_DATA(name) RUBY_WHISPER_PARAKEET_PARAM_##name##_user_data, ITERATE_PARAMS(DEF_IDX) + ITERATE_CALLBACK_PARAMS(DEF_IDX_CALLBACK) + ITERATE_USER_DATA_PARAMS(DEF_IDX_USER_DATA) #undef DEF_IDX RUBY_WHISPER_PARAKEET_NUM_PARAMS }; @@ -90,7 +98,7 @@ const rb_data_type_t ruby_whisper_parakeet_params_type = { 0 }; -#define DEF_BOOL_PARAM_ATTR(name, cb) \ +#define DEF_BOOL_PARAM_ATTR(name) \ static VALUE \ ruby_whisper_parakeet_params_get_##name(VALUE self) \ { \ @@ -107,7 +115,7 @@ const rb_data_type_t ruby_whisper_parakeet_params_type = { return val; \ } -#define DEF_INT_PARAM_ATTR(name, cb) \ +#define DEF_INT_PARAM_ATTR(name) \ static VALUE \ ruby_whisper_parakeet_params_get_##name(VALUE self) \ { \ @@ -126,44 +134,46 @@ const rb_data_type_t ruby_whisper_parakeet_params_type = { #define CALLBACK_CONTAINER_NAME(name) name ## _container -#define DEF_CALLBACK_PARAM_ATTR(name, cb) \ +#define DEF_CALLBACK_PARAM_ATTR(name) \ static VALUE \ ruby_whisper_parakeet_params_get_##name(VALUE self) \ { \ ruby_whisper_parakeet_params *rwpp; \ TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); \ - return rwpp->CALLBACK_CONTAINER_NAME(cb)->callback; \ + return rwpp->CALLBACK_CONTAINER_NAME(name)->callback; \ } \ static VALUE \ ruby_whisper_parakeet_params_set_##name(VALUE self, VALUE val) \ { \ ruby_whisper_parakeet_params *rwpp; \ TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); \ - rwpp->CALLBACK_CONTAINER_NAME(cb)->callback = (val); \ + rwpp->CALLBACK_CONTAINER_NAME(name)->callback = (val); \ return val; \ } -#define DEF_USER_DATA_PARAM_ATTR(name, cb) \ +#define DEF_USER_DATA_PARAM_ATTR(name) \ static VALUE \ - ruby_whisper_parakeet_params_get_##name(VALUE self) \ + ruby_whisper_parakeet_params_get_##name##_user_data(VALUE self) \ { \ ruby_whisper_parakeet_params *rwpp; \ TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); \ - return rwpp->CALLBACK_CONTAINER_NAME(cb)->user_data; \ + return rwpp->CALLBACK_CONTAINER_NAME(name)->user_data; \ } \ static VALUE \ - ruby_whisper_parakeet_params_set_##name(VALUE self, VALUE val) \ + ruby_whisper_parakeet_params_set_##name##_user_data(VALUE self, VALUE val) \ { \ ruby_whisper_parakeet_params *rwpp; \ TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); \ - rwpp->CALLBACK_CONTAINER_NAME(cb)->user_data = val; \ + rwpp->CALLBACK_CONTAINER_NAME(name)->user_data = val; \ return val; \ } -#define DEF_PARAM_ATTR(name, cb, type) DEF_PARAM_ATTR_I(name, cb, type) -#define DEF_PARAM_ATTR_I(name, cb, type) DEF_##type##_PARAM_ATTR(name, cb) +#define DEF_PARAM_ATTR(name, type) DEF_PARAM_ATTR_I(name, type) +#define DEF_PARAM_ATTR_I(name, type) DEF_##type##_PARAM_ATTR(name) ITERATE_PARAMS(DEF_PARAM_ATTR) +ITERATE_CALLBACK_PARAMS(DEF_CALLBACK_PARAM_ATTR) +ITERATE_USER_DATA_PARAMS(DEF_USER_DATA_PARAM_ATTR) static VALUE ruby_whisper_parakeet_params_s_allocate(VALUE klass) @@ -220,14 +230,28 @@ init_ruby_whisper_parakeet_params(VALUE *mParakeet) rb_define_method(cParakeetParams, "initialize", ruby_whisper_parakeet_params_initialize, -1); int i = 0; -#define REGISTER_PARAM_ATTR(name, cb, type) \ +#define REGISTER_PARAM_ATTR(name, type) \ + param_names[i] = rb_intern(#name); \ + param_writers[i] = ruby_whisper_parakeet_params_set_##name; \ + rb_define_method(cParakeetParams, #name, ruby_whisper_parakeet_params_get_##name, 0); \ + rb_define_method(cParakeetParams, #name "=", ruby_whisper_parakeet_params_set_##name, 1); \ + i++; +#define REGISTER_CALLBACK_PARAM_ATTR(name) \ param_names[i] = rb_intern(#name); \ param_writers[i] = ruby_whisper_parakeet_params_set_##name; \ rb_define_method(cParakeetParams, #name, ruby_whisper_parakeet_params_get_##name, 0); \ rb_define_method(cParakeetParams, #name "=", ruby_whisper_parakeet_params_set_##name, 1); \ i++; +#define REGISTER_USER_DATA_PARAM_ATTR(name) \ + param_names[i] = rb_intern(#name "_user_data"); \ + param_writers[i] = ruby_whisper_parakeet_params_set_##name##_user_data; \ + rb_define_method(cParakeetParams, #name "_user_data", ruby_whisper_parakeet_params_get_##name##_user_data, 0); \ + rb_define_method(cParakeetParams, #name "_user_data=", ruby_whisper_parakeet_params_set_##name##_user_data, 1); \ + i++; ITERATE_PARAMS(REGISTER_PARAM_ATTR) + ITERATE_CALLBACK_PARAMS(REGISTER_CALLBACK_PARAM_ATTR) + ITERATE_USER_DATA_PARAMS(REGISTER_USER_DATA_PARAM_ATTR) #undef REGISTER_PARAM_ATTR } From f412e289eacde2169f47ee658edcdeda339665e7 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 6 May 2026 21:04:24 +0900 Subject: [PATCH 006/118] Undefine local macros --- .../ruby/ext/ruby_whisper_parakeet_params.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c index 7bddf30cc40..5db65acab0c 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_params.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -32,6 +32,8 @@ enum { ITERATE_CALLBACK_PARAMS(DEF_IDX_CALLBACK) ITERATE_USER_DATA_PARAMS(DEF_IDX_USER_DATA) #undef DEF_IDX +#undef DEF_IDX_CALLBACK +#undef DEF_IDX_USER_DATA RUBY_WHISPER_PARAKEET_NUM_PARAMS }; @@ -254,6 +256,21 @@ init_ruby_whisper_parakeet_params(VALUE *mParakeet) ITERATE_USER_DATA_PARAMS(REGISTER_USER_DATA_PARAM_ATTR) #undef REGISTER_PARAM_ATTR +#undef REGISTER_CALLBACK_PARAM_ATTR +#undef REGISTER_USER_DATA_PARAM_ATTR } +#undef VAL_TO_INT +#undef VAL_FROM_INT +#undef VAL_TO_BOOL +#undef VAL_FROM_BOOL +#undef DEF_BOOL_PARAM_ATTR +#undef DEF_INT_PARAM_ATTR +#undef CALLBACK_CONTAINER_NAME +#undef DEF_CALLBACK_PARAM_ATTR +#undef DEF_USER_DATA_PARAM_ATTR +#undef DEF_PARAM_ATTR +#undef DEF_PARAM_ATTR_I #undef ITERATE_PARAMS +#undef ITERATE_CALLBACK_PARAMS +#undef ITERATE_USER_DATA_PARAMS From b1dbf7452d4e8fae471441d1d056adb354baf962 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 6 May 2026 21:08:23 +0900 Subject: [PATCH 007/118] Define GetParakeetParams --- bindings/ruby/ext/ruby_whisper.h | 4 ++++ bindings/ruby/ext/ruby_whisper_parakeet_params.c | 16 ++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index 07e5e0f1e75..f93cdf0c55c 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -130,4 +130,8 @@ typedef struct { } \ } while (0) +#define GetParakeetParams(obj, rwpp) do { \ + TypedData_Get_Struct((obj), ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, (rwpp)); \ +} while (0) + #endif diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c index 5db65acab0c..eebccaabaa9 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_params.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -105,14 +105,14 @@ const rb_data_type_t ruby_whisper_parakeet_params_type = { ruby_whisper_parakeet_params_get_##name(VALUE self) \ { \ ruby_whisper_parakeet_params *rwpp; \ - TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); \ + GetParakeetParams(self, rwpp); \ return VAL_FROM_BOOL(rwpp->params.name); \ } \ static VALUE \ ruby_whisper_parakeet_params_set_##name(VALUE self, VALUE val) \ { \ ruby_whisper_parakeet_params *rwpp; \ - TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); \ + GetParakeetParams(self, rwpp); \ rwpp->params.name = VAL_TO_BOOL(val); \ return val; \ } @@ -122,14 +122,14 @@ const rb_data_type_t ruby_whisper_parakeet_params_type = { ruby_whisper_parakeet_params_get_##name(VALUE self) \ { \ ruby_whisper_parakeet_params *rwpp; \ - TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); \ + GetParakeetParams(self, rwpp); \ return VAL_FROM_INT(rwpp->params.name); \ } \ static VALUE \ ruby_whisper_parakeet_params_set_##name(VALUE self, VALUE val) \ { \ ruby_whisper_parakeet_params *rwpp; \ - TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); \ + GetParakeetParams(self, rwpp); \ rwpp->params.name = VAL_TO_INT(val); \ return val; \ } @@ -141,14 +141,14 @@ const rb_data_type_t ruby_whisper_parakeet_params_type = { ruby_whisper_parakeet_params_get_##name(VALUE self) \ { \ ruby_whisper_parakeet_params *rwpp; \ - TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); \ + GetParakeetParams(self, rwpp); \ return rwpp->CALLBACK_CONTAINER_NAME(name)->callback; \ } \ static VALUE \ ruby_whisper_parakeet_params_set_##name(VALUE self, VALUE val) \ { \ ruby_whisper_parakeet_params *rwpp; \ - TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); \ + GetParakeetParams(self, rwpp); \ rwpp->CALLBACK_CONTAINER_NAME(name)->callback = (val); \ return val; \ } @@ -158,14 +158,14 @@ const rb_data_type_t ruby_whisper_parakeet_params_type = { ruby_whisper_parakeet_params_get_##name##_user_data(VALUE self) \ { \ ruby_whisper_parakeet_params *rwpp; \ - TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); \ + GetParakeetParams(self, rwpp); \ return rwpp->CALLBACK_CONTAINER_NAME(name)->user_data; \ } \ static VALUE \ ruby_whisper_parakeet_params_set_##name##_user_data(VALUE self, VALUE val) \ { \ ruby_whisper_parakeet_params *rwpp; \ - TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); \ + GetParakeetParams(self, rwpp); \ rwpp->CALLBACK_CONTAINER_NAME(name)->user_data = val; \ return val; \ } From cd0e91175ae0b26e6363e85bd9f647357515173d Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 6 May 2026 21:08:37 +0900 Subject: [PATCH 008/118] Remove unused variable --- bindings/ruby/ext/ruby_whisper_parakeet_params.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c index eebccaabaa9..24d14ee3c84 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_params.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -191,7 +191,6 @@ ruby_whisper_parakeet_params_initialize(int argc, VALUE *argv, VALUE self) { VALUE kw_hash; VALUE values[RUBY_WHISPER_PARAKEET_NUM_PARAMS] = {Qundef}; - VALUE id; VALUE value; ruby_whisper_parakeet_params *rwpp; int i; @@ -212,7 +211,6 @@ ruby_whisper_parakeet_params_initialize(int argc, VALUE *argv, VALUE self) rb_get_kwargs(kw_hash, param_names, 0, RUBY_WHISPER_PARAKEET_NUM_PARAMS, values); for (i = 0; i < RUBY_WHISPER_PARAKEET_NUM_PARAMS; i++) { - id = param_names[i]; value = values[i]; if (value == Qundef) { continue; From d051c0884161d08182784dfefe4d003685a4893e Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 6 May 2026 21:21:54 +0900 Subject: [PATCH 009/118] Use ITERATE_CALLBACK_PARAMS --- .../ruby/ext/ruby_whisper_parakeet_params.c | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c index 24d14ee3c84..f0e676ccfe4 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_params.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -53,32 +53,28 @@ static void ruby_whisper_parakeet_params_mark(void *p) { ruby_whisper_parakeet_params *rwpp = (ruby_whisper_parakeet_params *)p; - ruby_whisper_callback_container_mark(rwpp->new_segment_callback_container); - ruby_whisper_callback_container_mark(rwpp->new_token_callback_container); - ruby_whisper_callback_container_mark(rwpp->progress_callback_container); - ruby_whisper_callback_container_mark(rwpp->encoder_begin_callback_container); - ruby_whisper_callback_container_mark(rwpp->abort_callback_container); + +#define MARK_CONTAINER(name) ruby_whisper_callback_container_mark(rwpp->name##_container); + + ITERATE_CALLBACK_PARAMS(MARK_CONTAINER) + +#undef MARK_CONTAINER } static void ruby_whisper_parakeet_params_free(void *p) { ruby_whisper_parakeet_params *rwpp = (ruby_whisper_parakeet_params *)p; - if (rwpp->params.new_segment_callback_user_data) { - xfree(&rwpp->params.new_segment_callback_user_data); - } - if (rwpp->params.new_token_callback_user_data) { - xfree(&rwpp->params.new_token_callback_user_data); - } - if (rwpp->params.progress_callback_user_data) { - xfree(&rwpp->params.progress_callback_user_data); - } - if (rwpp->params.encoder_begin_callback_user_data) { - xfree(&rwpp->params.encoder_begin_callback_user_data); - } - if (rwpp->params.abort_callback_user_data) { - xfree(&rwpp->params.abort_callback_user_data); + +#define FREE_CONTAINER(name) \ + if (rwpp->name##_container) { \ + xfree(rwpp->name##_container); \ } + +ITERATE_CALLBACK_PARAMS(FREE_CONTAINER) + +#undef FREE_CONTAINER + xfree(rwpp); } @@ -197,11 +193,11 @@ ruby_whisper_parakeet_params_initialize(int argc, VALUE *argv, VALUE self) TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp); - rwpp->new_segment_callback_container = ruby_whisper_callback_container_allocate(); - rwpp->new_token_callback_container = ruby_whisper_callback_container_allocate(); - rwpp->progress_callback_container = ruby_whisper_callback_container_allocate(); - rwpp->encoder_begin_callback_container = ruby_whisper_callback_container_allocate(); - rwpp->abort_callback_container = ruby_whisper_callback_container_allocate(); +#define INIT_CONTAINER(name) rwpp->name##_container = ruby_whisper_callback_container_allocate(); + +ITERATE_CALLBACK_PARAMS(INIT_CONTAINER) + +#undef INIT_CONTAINER rb_scan_args_kw(RB_SCAN_ARGS_KEYWORDS, argc, argv, ":", &kw_hash); if (NIL_P(kw_hash)) { From 09eff4d1babdd39d82cb05750a332934c9d0aa87 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 6 May 2026 21:25:41 +0900 Subject: [PATCH 010/118] Use ITERATE_CALLBACK_PARAMS instead of ITERATE_USER_DATA_PARAMS --- .../ruby/ext/ruby_whisper_parakeet_params.c | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c index f0e676ccfe4..741076e0308 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_params.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -17,20 +17,13 @@ ITERATOR(encoder_begin_callback) \ ITERATOR(abort_callback) -#define ITERATE_USER_DATA_PARAMS(ITERATOR) \ - ITERATOR(new_segment_callback) \ - ITERATOR(new_token_callback) \ - ITERATOR(progress_callback) \ - ITERATOR(encoder_begin_callback) \ - ITERATOR(abort_callback) - enum { #define DEF_IDX(name, type) RUBY_WHISPER_PARAKEET_PARAM_##name, #define DEF_IDX_CALLBACK(name) RUBY_WHISPER_PARAKEET_PARAM_##name, #define DEF_IDX_USER_DATA(name) RUBY_WHISPER_PARAKEET_PARAM_##name##_user_data, ITERATE_PARAMS(DEF_IDX) ITERATE_CALLBACK_PARAMS(DEF_IDX_CALLBACK) - ITERATE_USER_DATA_PARAMS(DEF_IDX_USER_DATA) + ITERATE_CALLBACK_PARAMS(DEF_IDX_USER_DATA) #undef DEF_IDX #undef DEF_IDX_CALLBACK #undef DEF_IDX_USER_DATA @@ -171,7 +164,7 @@ const rb_data_type_t ruby_whisper_parakeet_params_type = { ITERATE_PARAMS(DEF_PARAM_ATTR) ITERATE_CALLBACK_PARAMS(DEF_CALLBACK_PARAM_ATTR) -ITERATE_USER_DATA_PARAMS(DEF_USER_DATA_PARAM_ATTR) +ITERATE_CALLBACK_PARAMS(DEF_USER_DATA_PARAM_ATTR) static VALUE ruby_whisper_parakeet_params_s_allocate(VALUE klass) @@ -237,8 +230,7 @@ init_ruby_whisper_parakeet_params(VALUE *mParakeet) param_writers[i] = ruby_whisper_parakeet_params_set_##name; \ rb_define_method(cParakeetParams, #name, ruby_whisper_parakeet_params_get_##name, 0); \ rb_define_method(cParakeetParams, #name "=", ruby_whisper_parakeet_params_set_##name, 1); \ - i++; -#define REGISTER_USER_DATA_PARAM_ATTR(name) \ + i++; \ param_names[i] = rb_intern(#name "_user_data"); \ param_writers[i] = ruby_whisper_parakeet_params_set_##name##_user_data; \ rb_define_method(cParakeetParams, #name "_user_data", ruby_whisper_parakeet_params_get_##name##_user_data, 0); \ @@ -247,11 +239,9 @@ init_ruby_whisper_parakeet_params(VALUE *mParakeet) ITERATE_PARAMS(REGISTER_PARAM_ATTR) ITERATE_CALLBACK_PARAMS(REGISTER_CALLBACK_PARAM_ATTR) - ITERATE_USER_DATA_PARAMS(REGISTER_USER_DATA_PARAM_ATTR) #undef REGISTER_PARAM_ATTR #undef REGISTER_CALLBACK_PARAM_ATTR -#undef REGISTER_USER_DATA_PARAM_ATTR } #undef VAL_TO_INT @@ -267,4 +257,3 @@ init_ruby_whisper_parakeet_params(VALUE *mParakeet) #undef DEF_PARAM_ATTR_I #undef ITERATE_PARAMS #undef ITERATE_CALLBACK_PARAMS -#undef ITERATE_USER_DATA_PARAMS From 1e7c734a1d08abddd896afb90f9435b8abb77ae5 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 6 May 2026 21:28:04 +0900 Subject: [PATCH 011/118] Fix memsize --- bindings/ruby/ext/ruby_whisper_parakeet_params.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c index 741076e0308..e41ab765c3e 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_params.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -75,11 +75,10 @@ static size_t ruby_whisper_parakeet_params_memsize(const void *p) { const struct ruby_whisper_parakeet_params *params = p; - size_t size = sizeof(params); if (!params) { return 0; } - return size; + return sizeof(ruby_whisper_parakeet_params); } const rb_data_type_t ruby_whisper_parakeet_params_type = { From 17bd8195854fed7f0855861007b6d9ffd17a553b Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 6 May 2026 21:36:22 +0900 Subject: [PATCH 012/118] Remove unnecessary macros --- .../ruby/ext/ruby_whisper_parakeet_params.c | 30 +++++-------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c index e41ab765c3e..114cc0cee45 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_params.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -88,37 +88,22 @@ const rb_data_type_t ruby_whisper_parakeet_params_type = { 0 }; -#define DEF_BOOL_PARAM_ATTR(name) \ +#define READER(type) VAL_FROM_##type +#define WRITER(type) VAL_TO_##type +#define DEF_PARAM_ATTR(name, type) \ static VALUE \ ruby_whisper_parakeet_params_get_##name(VALUE self) \ { \ ruby_whisper_parakeet_params *rwpp; \ GetParakeetParams(self, rwpp); \ - return VAL_FROM_BOOL(rwpp->params.name); \ + return READER(type)(rwpp->params.name); \ } \ static VALUE \ ruby_whisper_parakeet_params_set_##name(VALUE self, VALUE val) \ { \ ruby_whisper_parakeet_params *rwpp; \ GetParakeetParams(self, rwpp); \ - rwpp->params.name = VAL_TO_BOOL(val); \ - return val; \ - } - -#define DEF_INT_PARAM_ATTR(name) \ - static VALUE \ - ruby_whisper_parakeet_params_get_##name(VALUE self) \ - { \ - ruby_whisper_parakeet_params *rwpp; \ - GetParakeetParams(self, rwpp); \ - return VAL_FROM_INT(rwpp->params.name); \ - } \ - static VALUE \ - ruby_whisper_parakeet_params_set_##name(VALUE self, VALUE val) \ - { \ - ruby_whisper_parakeet_params *rwpp; \ - GetParakeetParams(self, rwpp); \ - rwpp->params.name = VAL_TO_INT(val); \ + rwpp->params.name = WRITER(type)(val); \ return val; \ } @@ -158,9 +143,6 @@ const rb_data_type_t ruby_whisper_parakeet_params_type = { return val; \ } -#define DEF_PARAM_ATTR(name, type) DEF_PARAM_ATTR_I(name, type) -#define DEF_PARAM_ATTR_I(name, type) DEF_##type##_PARAM_ATTR(name) - ITERATE_PARAMS(DEF_PARAM_ATTR) ITERATE_CALLBACK_PARAMS(DEF_CALLBACK_PARAM_ATTR) ITERATE_CALLBACK_PARAMS(DEF_USER_DATA_PARAM_ATTR) @@ -252,6 +234,8 @@ init_ruby_whisper_parakeet_params(VALUE *mParakeet) #undef CALLBACK_CONTAINER_NAME #undef DEF_CALLBACK_PARAM_ATTR #undef DEF_USER_DATA_PARAM_ATTR +#undef READER +#undef WRITER #undef DEF_PARAM_ATTR #undef DEF_PARAM_ATTR_I #undef ITERATE_PARAMS From c5894984b349d503d737a57ff953281f0f0714c9 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 6 May 2026 21:45:30 +0900 Subject: [PATCH 013/118] Simplify params registration --- .../ruby/ext/ruby_whisper_parakeet_params.c | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c index 114cc0cee45..7ec93126ba4 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_params.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -200,29 +200,25 @@ init_ruby_whisper_parakeet_params(VALUE *mParakeet) rb_define_method(cParakeetParams, "initialize", ruby_whisper_parakeet_params_initialize, -1); int i = 0; -#define REGISTER_PARAM_ATTR(name, type) \ +#define REGISTER_PARAM(name) \ param_names[i] = rb_intern(#name); \ param_writers[i] = ruby_whisper_parakeet_params_set_##name; \ rb_define_method(cParakeetParams, #name, ruby_whisper_parakeet_params_get_##name, 0); \ rb_define_method(cParakeetParams, #name "=", ruby_whisper_parakeet_params_set_##name, 1); \ i++; -#define REGISTER_CALLBACK_PARAM_ATTR(name) \ - param_names[i] = rb_intern(#name); \ - param_writers[i] = ruby_whisper_parakeet_params_set_##name; \ - rb_define_method(cParakeetParams, #name, ruby_whisper_parakeet_params_get_##name, 0); \ - rb_define_method(cParakeetParams, #name "=", ruby_whisper_parakeet_params_set_##name, 1); \ - i++; \ - param_names[i] = rb_intern(#name "_user_data"); \ - param_writers[i] = ruby_whisper_parakeet_params_set_##name##_user_data; \ - rb_define_method(cParakeetParams, #name "_user_data", ruby_whisper_parakeet_params_get_##name##_user_data, 0); \ - rb_define_method(cParakeetParams, #name "_user_data=", ruby_whisper_parakeet_params_set_##name##_user_data, 1); \ - i++; + +#define REGISTER_PARAM_ATTR(name, type) REGISTER_PARAM(name) +#define REGISTER_CALLBACK_PARAM_ATTR(name) REGISTER_PARAM(name) +#define REGISTER_USER_DATA_PARAM_ATTR(name) REGISTER_PARAM(name##_user_data) ITERATE_PARAMS(REGISTER_PARAM_ATTR) ITERATE_CALLBACK_PARAMS(REGISTER_CALLBACK_PARAM_ATTR) + ITERATE_CALLBACK_PARAMS(REGISTER_USER_DATA_PARAM_ATTR) +#undef REGISTER_PARAM #undef REGISTER_PARAM_ATTR #undef REGISTER_CALLBACK_PARAM_ATTR +#undef REGISTER_USER_DATA_PARAM_ATTR } #undef VAL_TO_INT From 105f7a86b93be46119816469f11c8ac97573d9a6 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 6 May 2026 21:58:19 +0900 Subject: [PATCH 014/118] Define Parakeet --- bindings/ruby/test/helper.rb | 2 ++ bindings/ruby/test/test_parakeet_params.rb | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/bindings/ruby/test/helper.rb b/bindings/ruby/test/helper.rb index 56cd3849fdd..6dc4b6a319b 100644 --- a/bindings/ruby/test/helper.rb +++ b/bindings/ruby/test/helper.rb @@ -2,6 +2,8 @@ require "whisper" require_relative "jfk_reader/jfk_reader" +Parakeet = Whisper::Parakeet + class TestBase < Test::Unit::TestCase AUDIO = File.join(__dir__, "fixtures", "jfk.wav") diff --git a/bindings/ruby/test/test_parakeet_params.rb b/bindings/ruby/test/test_parakeet_params.rb index d50ecebb7fe..f00ca8eb31d 100644 --- a/bindings/ruby/test/test_parakeet_params.rb +++ b/bindings/ruby/test/test_parakeet_params.rb @@ -13,11 +13,11 @@ class TestParakeetParams < TestBase ] def setup - @params = Whisper::Parakeet::Params.new + @params = Parakeet::Params.new end def test_new - assert_instance_of Whisper::Parakeet::Params, @params + assert_instance_of Parakeet::Params, @params end def test_n_threads @@ -77,7 +77,7 @@ def test_right_context_ms end def test_new_with_kw_args - params = Whisper::Parakeet::Params.new(n_threads: 1) + params = Parakeet::Params.new(n_threads: 1) assert_equal 1, params.n_threads assert_equal 0, params.offset_ms end @@ -91,7 +91,7 @@ def test_new_with_kw_args_default_values(param) in [*, Integer] default_value + 1 end - params = Whisper::Parakeet::Params.new(param => value) + params = Parakeet::Params.new(param => value) assert_equal value, params.send(param) PARAM_NAMES.reject {|name| name == param}.each do |name| From d051ab626133c7a833def131a42cad14048aa6a4 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 7 May 2026 01:12:16 +0900 Subject: [PATCH 015/118] Add hook methods to Parakeet::Params --- .../ruby/ext/ruby_whisper_parakeet_params.c | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c index 7ec93126ba4..4652718ff52 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_params.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -143,9 +143,24 @@ const rb_data_type_t ruby_whisper_parakeet_params_type = { return val; \ } +#define DEF_HOOK(name) \ + static VALUE \ + ruby_whisper_parakeet_params_on_##name(VALUE self) \ + { \ + ruby_whisper_parakeet_params *rwpp; \ + GetParakeetParams(self, rwpp); \ + const VALUE blk = rb_block_proc(); \ + if (!rwpp->name##_container->callbacks) { \ + rwpp->name##_container->callbacks = rb_ary_new(); \ + } \ + rb_ary_push(rwpp->name##_container->callbacks, blk); \ + return Qnil; \ + } + ITERATE_PARAMS(DEF_PARAM_ATTR) ITERATE_CALLBACK_PARAMS(DEF_CALLBACK_PARAM_ATTR) ITERATE_CALLBACK_PARAMS(DEF_USER_DATA_PARAM_ATTR) +ITERATE_CALLBACK_PARAMS(DEF_HOOK) static VALUE ruby_whisper_parakeet_params_s_allocate(VALUE klass) @@ -215,10 +230,16 @@ init_ruby_whisper_parakeet_params(VALUE *mParakeet) ITERATE_CALLBACK_PARAMS(REGISTER_CALLBACK_PARAM_ATTR) ITERATE_CALLBACK_PARAMS(REGISTER_USER_DATA_PARAM_ATTR) +#define REGISTER_HOOK(name) \ + rb_define_method(cParakeetParams, "on_" #name, ruby_whisper_parakeet_params_on_##name, 0); + + ITERATE_CALLBACK_PARAMS(REGISTER_HOOK) + #undef REGISTER_PARAM #undef REGISTER_PARAM_ATTR #undef REGISTER_CALLBACK_PARAM_ATTR #undef REGISTER_USER_DATA_PARAM_ATTR +#undef REGISTER_HOOK } #undef VAL_TO_INT @@ -230,6 +251,7 @@ init_ruby_whisper_parakeet_params(VALUE *mParakeet) #undef CALLBACK_CONTAINER_NAME #undef DEF_CALLBACK_PARAM_ATTR #undef DEF_USER_DATA_PARAM_ATTR +#undef DEF_HOOK #undef READER #undef WRITER #undef DEF_PARAM_ATTR From a3515ac9fc35e7bec8951f059596974133325929 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 7 May 2026 01:32:28 +0900 Subject: [PATCH 016/118] Fix typo --- bindings/ruby/ext/ruby_whisper.c | 2 ++ bindings/ruby/ext/ruby_whisper_params.c | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper.c b/bindings/ruby/ext/ruby_whisper.c index 5c74e9c0743..18069515f76 100644 --- a/bindings/ruby/ext/ruby_whisper.c +++ b/bindings/ruby/ext/ruby_whisper.c @@ -48,6 +48,7 @@ extern void init_ruby_whisper_vad_context(VALUE *mVAD); extern void init_ruby_whisper_vad_segment(VALUE *mVAD); extern void init_ruby_whisper_vad_segments(VALUE *mVAD); extern void init_ruby_whisper_parakeet_params(VALUE *mParakeet); +extern void init_ruby_whisper_parakeet_context(VALUE *mParakeet); extern void register_callbacks(ruby_whisper_params *rwp, VALUE *context); /* @@ -241,6 +242,7 @@ void Init_whisper() { init_ruby_whisper_vad_segments(&mVAD); init_ruby_whisper_vad_context(&mVAD); init_ruby_whisper_parakeet_params(&mParakeet); + init_ruby_whisper_parakeet_context(&mParakeet); rb_require("whisper/context"); rb_require("whisper/segment"); diff --git a/bindings/ruby/ext/ruby_whisper_params.c b/bindings/ruby/ext/ruby_whisper_params.c index a8632cd879e..7ec0ac107d8 100644 --- a/bindings/ruby/ext/ruby_whisper_params.c +++ b/bindings/ruby/ext/ruby_whisper_params.c @@ -77,7 +77,7 @@ static ID id_vad_model_path; static ID id_vad_params; void -ruby_whisper_callbcack_container_mark(ruby_whisper_callback_container *rwc) +ruby_whisper_callback_container_mark(ruby_whisper_callback_container *rwc) { if (rwc == NULL) return; @@ -421,9 +421,9 @@ void rb_whisper_params_mark(void *p) { ruby_whisper_params *rwp = (ruby_whisper_params *)p; - rb_whisper_callbcack_container_mark(rwp->new_segment_callback_container); - rb_whisper_callbcack_container_mark(rwp->progress_callback_container); - rb_whisper_callbcack_container_mark(rwp->encoder_begin_callback_container); + ruby_whisper_callback_container_mark(rwp->new_segment_callback_container); + ruby_whisper_callback_container_mark(rwp->progress_callback_container); + ruby_whisper_callback_container_mark(rwp->encoder_begin_callback_container); rb_whisper_abort_callback_container_mark(rwp->abort_callback_container); rb_gc_mark(rwp->vad_params); } From f55f3f347c32fcbc5c6ab56ab7bfa8f98dbfdec0 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 7 May 2026 01:43:40 +0900 Subject: [PATCH 017/118] Check callback container in GetParakeetParams --- bindings/ruby/ext/ruby_whisper.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index f93cdf0c55c..2dba8adcc20 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -132,6 +132,21 @@ typedef struct { #define GetParakeetParams(obj, rwpp) do { \ TypedData_Get_Struct((obj), ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, (rwpp)); \ + if (!(rwpp)->new_segment_callback_container) { \ + rb_raise(rb_eRuntimeError, "Not initialized"); \ + } \ + if (!(rwpp)->new_token_callback_container) { \ + rb_raise(rb_eRuntimeError, "Not initialized"); \ + } \ + if (!(rwpp)->progress_callback_container) { \ + rb_raise(rb_eRuntimeError, "Not initialized"); \ + } \ + if (!(rwpp)->encoder_begin_callback_container) { \ + rb_raise(rb_eRuntimeError, "Not initialized"); \ + } \ + if (!(rwpp)->abort_callback_container) { \ + rb_raise(rb_eRuntimeError, "Not initialized"); \ + } \ } while (0) #endif From 3bae1e2f1bb02845ca4658ea6a54d2a3e2cefa09 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 8 May 2026 15:28:34 +0900 Subject: [PATCH 018/118] Reduce if --- bindings/ruby/ext/ruby_whisper.h | 18 +++++------------- .../ruby/ext/ruby_whisper_parakeet_context.c | 9 +++++++++ 2 files changed, 14 insertions(+), 13 deletions(-) create mode 100644 bindings/ruby/ext/ruby_whisper_parakeet_context.c diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index 2dba8adcc20..4ed6a272012 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -132,19 +132,11 @@ typedef struct { #define GetParakeetParams(obj, rwpp) do { \ TypedData_Get_Struct((obj), ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, (rwpp)); \ - if (!(rwpp)->new_segment_callback_container) { \ - rb_raise(rb_eRuntimeError, "Not initialized"); \ - } \ - if (!(rwpp)->new_token_callback_container) { \ - rb_raise(rb_eRuntimeError, "Not initialized"); \ - } \ - if (!(rwpp)->progress_callback_container) { \ - rb_raise(rb_eRuntimeError, "Not initialized"); \ - } \ - if (!(rwpp)->encoder_begin_callback_container) { \ - rb_raise(rb_eRuntimeError, "Not initialized"); \ - } \ - if (!(rwpp)->abort_callback_container) { \ + if (!(rwpp)->new_segment_callback_container || \ + !(rwpp)->new_token_callback_container || \ + !(rwpp)->progress_callback_container || \ + !(rwpp)->encoder_begin_callback_container || \ + !(rwpp)->abort_callback_container) { \ rb_raise(rb_eRuntimeError, "Not initialized"); \ } \ } while (0) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_context.c b/bindings/ruby/ext/ruby_whisper_parakeet_context.c new file mode 100644 index 00000000000..90bcda35e6c --- /dev/null +++ b/bindings/ruby/ext/ruby_whisper_parakeet_context.c @@ -0,0 +1,9 @@ +#include "ruby_whisper.h" + + + +void +init_ruby_whisper_parakeet_context(VALUE *mParakeet) +{ + VALUE cParakeetContext = rb_define_class_under(*mParakeet, "Context", rb_cObject); +} From 8615ac87ec9ec8e10f231d113eb6c61969c607fb Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 8 May 2026 18:49:28 +0900 Subject: [PATCH 019/118] Free parakeet_full_params --- bindings/ruby/ext/ruby_whisper_parakeet_params.c | 1 + 1 file changed, 1 insertion(+) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c index 4652718ff52..33579ecfc80 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_params.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -58,6 +58,7 @@ static void ruby_whisper_parakeet_params_free(void *p) { ruby_whisper_parakeet_params *rwpp = (ruby_whisper_parakeet_params *)p; + parakeet_free_params(&rwpp->params); #define FREE_CONTAINER(name) \ if (rwpp->name##_container) { \ From fdaf0318588a6a937fb81a794d7df54858b1b2cb Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sun, 10 May 2026 20:44:24 +0900 Subject: [PATCH 020/118] Implement Parakeet::Context#initialize --- bindings/ruby/ext/ruby_whisper.h | 4 ++ .../ruby/ext/ruby_whisper_parakeet_context.c | 66 +++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index 4ed6a272012..78bc3fc3eb4 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -94,6 +94,10 @@ typedef struct { ruby_whisper_callback_container *abort_callback_container; } ruby_whisper_parakeet_params; +typedef struct { + struct parakeet_context *context; +} ruby_whisper_parakeet_context; + #define GetContext(obj, rw) do { \ TypedData_Get_Struct((obj), ruby_whisper, &ruby_whisper_type, (rw)); \ if ((rw)->context == NULL) { \ diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_context.c b/bindings/ruby/ext/ruby_whisper_parakeet_context.c index 90bcda35e6c..cefb1b23361 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_context.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_context.c @@ -1,9 +1,75 @@ #include "ruby_whisper.h" +extern ID id_to_s; +extern VALUE ruby_whisper_normalize_model_path(VALUE model_path); + +static void +ruby_whisper_parakeet_context_free(void *p) +{ + ruby_whisper_parakeet_context *rwpc = (ruby_whisper_parakeet_context *)p; + if (rwpc->context) { + parakeet_free(rwpc->context); + rwpc->context = NULL; + } +} + +static size_t +ruby_whisper_parakeet_context_memsize(const void *p) +{ + ruby_whisper_parakeet_context *rwpc = (ruby_whisper_parakeet_context *)p; + if (!rwpc) { + return 0; + } + size_t size = sizeof(*rwpc); + return size; +} + +static const rb_data_type_t ruby_whisper_parakeet_context_type = { + "ruby_whisper_parakeet_context", + {0, ruby_whisper_parakeet_context_free, ruby_whisper_parakeet_context_memsize,}, + 0, 0, + 0 +}; + +static VALUE +ruby_whisper_parakeet_context_allocate(VALUE klass) +{ + ruby_whisper_parakeet_context *rwpc; + + VALUE obj = TypedData_Make_Struct(klass, ruby_whisper_parakeet_context, &ruby_whisper_parakeet_context_type, rwpc); + rwpc->context = NULL; + + return obj; +} + +static VALUE +ruby_whisper_parakeet_context_initialize(int argc, VALUE *argv, VALUE self) +{ + ruby_whisper_parakeet_context *rwpc; + VALUE model_path; + + rb_scan_args(argc, argv, "1", &model_path); + TypedData_Get_Struct(self, ruby_whisper_parakeet_context, &ruby_whisper_parakeet_context_type, rwpc); + + model_path = ruby_whisper_normalize_model_path(model_path); + if (!rb_respond_to(model_path, id_to_s)) { + rb_raise(rb_eRuntimeError, "Expected file path to model to initialize Parakeet::Context"); + } + rwpc->context = parakeet_init_from_file_with_params(StringValueCStr(model_path), parakeet_context_default_params()); + if (rwpc->context == NULL) { + rb_raise(rb_eRuntimeError, "Failed to load model"); + } + + return Qnil; +} void init_ruby_whisper_parakeet_context(VALUE *mParakeet) { VALUE cParakeetContext = rb_define_class_under(*mParakeet, "Context", rb_cObject); + + rb_define_alloc_func(cParakeetContext, ruby_whisper_parakeet_context_allocate); + + rb_define_method(cParakeetContext, "initialize", ruby_whisper_parakeet_context_initialize, -1); } From 46a3a2cb93cb33d35b5e8ada8372d5f7b02a3a79 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sun, 10 May 2026 20:47:06 +0900 Subject: [PATCH 021/118] Add TestParakeetContext --- bindings/ruby/test/test_parakeet_context.rb | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 bindings/ruby/test/test_parakeet_context.rb diff --git a/bindings/ruby/test/test_parakeet_context.rb b/bindings/ruby/test/test_parakeet_context.rb new file mode 100644 index 00000000000..295112bddf1 --- /dev/null +++ b/bindings/ruby/test/test_parakeet_context.rb @@ -0,0 +1,7 @@ +require_relative "helper" + +class TestParakeetContext < TestBase + def test_new + assert_instance_of Parakeet::Context, Parakeet::Context.new(File.join(__dir__, "../../../models/parakeet-tdt-0.6b-v3.bin")) + end +end From 2cfefa926b4081a3ea30a083b06c7bedef1b6cda Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sun, 10 May 2026 20:47:23 +0900 Subject: [PATCH 022/118] Add Parakeet::Segment --- bindings/ruby/ext/ruby_whisper.c | 2 ++ bindings/ruby/ext/ruby_whisper_parakeet_segment.c | 7 +++++++ 2 files changed, 9 insertions(+) create mode 100644 bindings/ruby/ext/ruby_whisper_parakeet_segment.c diff --git a/bindings/ruby/ext/ruby_whisper.c b/bindings/ruby/ext/ruby_whisper.c index 18069515f76..e38f9b0312c 100644 --- a/bindings/ruby/ext/ruby_whisper.c +++ b/bindings/ruby/ext/ruby_whisper.c @@ -48,6 +48,7 @@ extern void init_ruby_whisper_vad_context(VALUE *mVAD); extern void init_ruby_whisper_vad_segment(VALUE *mVAD); extern void init_ruby_whisper_vad_segments(VALUE *mVAD); extern void init_ruby_whisper_parakeet_params(VALUE *mParakeet); +extern void init_ruby_whisper_parakeet_segment(VALUE *mParakeet); extern void init_ruby_whisper_parakeet_context(VALUE *mParakeet); extern void register_callbacks(ruby_whisper_params *rwp, VALUE *context); @@ -242,6 +243,7 @@ void Init_whisper() { init_ruby_whisper_vad_segments(&mVAD); init_ruby_whisper_vad_context(&mVAD); init_ruby_whisper_parakeet_params(&mParakeet); + init_ruby_whisper_parakeet_segment(&mParakeet); init_ruby_whisper_parakeet_context(&mParakeet); rb_require("whisper/context"); diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_segment.c b/bindings/ruby/ext/ruby_whisper_parakeet_segment.c new file mode 100644 index 00000000000..b154930777c --- /dev/null +++ b/bindings/ruby/ext/ruby_whisper_parakeet_segment.c @@ -0,0 +1,7 @@ +#include "ruby_whisper.h" + +void +init_ruby_whisper_parakeet_segment(VALUE *mParakeet) +{ + rb_define_class_under(*mParakeet, "Segment", rb_cObject); +} From 6d2291dc8af75792dbf39062a2195dd4c9a232fc Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 14 May 2026 14:14:54 +0900 Subject: [PATCH 023/118] Prevent double-free --- bindings/ruby/ext/ruby_whisper_parakeet_params.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c index 33579ecfc80..8a50c047a5f 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_params.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -68,8 +68,6 @@ ruby_whisper_parakeet_params_free(void *p) ITERATE_CALLBACK_PARAMS(FREE_CONTAINER) #undef FREE_CONTAINER - - xfree(rwpp); } static size_t From 6bcc09b91122f8b59cb2968d86d5fa6b80cf0fd7 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 14 May 2026 14:49:06 +0900 Subject: [PATCH 024/118] Add Parakeet::Context#transcribe --- bindings/ruby/ext/ruby_whisper.h | 7 +++ .../ruby/ext/ruby_whisper_parakeet_context.c | 4 +- .../ext/ruby_whisper_parakeet_transcribe.cpp | 47 +++++++++++++++++++ bindings/ruby/test/test_parakeet_context.rb | 25 +++++++++- 4 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index 78bc3fc3eb4..0e907a61465 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -134,6 +134,13 @@ typedef struct { } \ } while (0) +#define GetParakeetContext(obj, rwpc) do { \ + TypedData_Get_Struct((obj), ruby_whisper_parakeet_context, &ruby_whisper_parakeet_context_type, (rwpc)); \ + if ((rwpc)->context == NULL) { \ + rb_raise(rb_eRuntimeError, "Not initialized"); \ + } \ +} while (0) + #define GetParakeetParams(obj, rwpp) do { \ TypedData_Get_Struct((obj), ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, (rwpp)); \ if (!(rwpp)->new_segment_callback_container || \ diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_context.c b/bindings/ruby/ext/ruby_whisper_parakeet_context.c index cefb1b23361..226e65e1089 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_context.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_context.c @@ -3,6 +3,7 @@ extern ID id_to_s; extern VALUE ruby_whisper_normalize_model_path(VALUE model_path); +extern VALUE ruby_whisper_parakeet_transcribe(VALUE self, VALUE audio_path, VALUE params); static void ruby_whisper_parakeet_context_free(void *p) @@ -25,7 +26,7 @@ ruby_whisper_parakeet_context_memsize(const void *p) return size; } -static const rb_data_type_t ruby_whisper_parakeet_context_type = { +const rb_data_type_t ruby_whisper_parakeet_context_type = { "ruby_whisper_parakeet_context", {0, ruby_whisper_parakeet_context_free, ruby_whisper_parakeet_context_memsize,}, 0, 0, @@ -72,4 +73,5 @@ init_ruby_whisper_parakeet_context(VALUE *mParakeet) rb_define_alloc_func(cParakeetContext, ruby_whisper_parakeet_context_allocate); rb_define_method(cParakeetContext, "initialize", ruby_whisper_parakeet_context_initialize, -1); + rb_define_method(cParakeetContext, "transcribe", ruby_whisper_parakeet_transcribe, 2); } diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp b/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp new file mode 100644 index 00000000000..ba713dc8cdf --- /dev/null +++ b/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp @@ -0,0 +1,47 @@ +#include "ruby_whisper.h" +#include "common-whisper.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern const rb_data_type_t ruby_whisper_parakeet_context_type; +extern const rb_data_type_t ruby_whisper_parakeet_params_type; + +extern ID id_to_s; +extern ID id_to_path; + +VALUE +ruby_whisper_parakeet_transcribe(VALUE self, VALUE audio_path, VALUE params) +{ + if (rb_respond_to(audio_path, id_to_path)) { + audio_path = rb_funcall(audio_path, id_to_path, 0); + } + + std::string fname = StringValueCStr(audio_path); + std::vector pcmf32; + std::vector> pcmf32s; + + if (!read_audio_data(fname, pcmf32, pcmf32s, false)) { + rb_raise(rb_eRuntimeError, "Failed to open %s", fname.c_str()); + return Qnil; + } + + ruby_whisper_parakeet_context *rwpc; + ruby_whisper_parakeet_params *rwpp; + GetParakeetContext(self, rwpc); + GetParakeetParams(params, rwpp); + + if (parakeet_full(rwpc->context, rwpp->params, pcmf32.data(), pcmf32.size()) != 0) { + rb_raise(rb_eRuntimeError, "Failed to process audio"); + return Qnil; + } + + return self; +} + +#ifdef __cplusplus +} +#endif diff --git a/bindings/ruby/test/test_parakeet_context.rb b/bindings/ruby/test/test_parakeet_context.rb index 295112bddf1..9150fe926a8 100644 --- a/bindings/ruby/test/test_parakeet_context.rb +++ b/bindings/ruby/test/test_parakeet_context.rb @@ -1,7 +1,30 @@ require_relative "helper" class TestParakeetContext < TestBase + def setup + @parakeet = Parakeet::Context.new(File.join(__dir__, "../../../models/parakeet-tdt-0.6b-v3.bin")) + @params = Parakeet::Params.new + end + def test_new - assert_instance_of Parakeet::Context, Parakeet::Context.new(File.join(__dir__, "../../../models/parakeet-tdt-0.6b-v3.bin")) + assert_instance_of Parakeet::Context, @parakeet + end + + def test_transcribe + assert_nothing_raised do + @parakeet.transcribe AUDIO, @params + end + end + + def test_transcribe_with_pathname + assert_nothing_raised do + @parakeet.transcribe Pathname(AUDIO), @params + end + end + + def test_transcribe_with_nothing + assert_raise_message(/open/) do + @parakeet.transcribe "nothing", @params + end end end From bb096e54acb58a62d98a0e39582fa1756a28042b Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 15 May 2026 01:38:08 +0900 Subject: [PATCH 025/118] Add Parakeet::Context#each_segment --- bindings/ruby/ext/ruby_whisper.c | 2 + bindings/ruby/ext/ruby_whisper.h | 5 ++ .../ruby/ext/ruby_whisper_parakeet_context.c | 27 +++++++++- .../ruby/ext/ruby_whisper_parakeet_params.c | 4 +- .../ruby/ext/ruby_whisper_parakeet_segment.c | 54 ++++++++++++++++++- bindings/ruby/test/test_parakeet_segment.rb | 14 +++++ 6 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 bindings/ruby/test/test_parakeet_segment.rb diff --git a/bindings/ruby/ext/ruby_whisper.c b/bindings/ruby/ext/ruby_whisper.c index e38f9b0312c..0c21e6252d3 100644 --- a/bindings/ruby/ext/ruby_whisper.c +++ b/bindings/ruby/ext/ruby_whisper.c @@ -9,7 +9,9 @@ VALUE cVADContext; VALUE cVADParams; VALUE cVADSegments; VALUE cVADSegment; +VALUE cParakeetContext; VALUE cParakeetParams; +VALUE cParakeetSegment; VALUE eError; VALUE cSegment; diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index 0e907a61465..c62a32ed37f 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -98,6 +98,11 @@ typedef struct { struct parakeet_context *context; } ruby_whisper_parakeet_context; +typedef struct { + VALUE context; + int index; +} ruby_whisper_parakeet_segment; + #define GetContext(obj, rw) do { \ TypedData_Get_Struct((obj), ruby_whisper, &ruby_whisper_type, (rw)); \ if ((rw)->context == NULL) { \ diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_context.c b/bindings/ruby/ext/ruby_whisper_parakeet_context.c index 226e65e1089..8bde0711c83 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_context.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_context.c @@ -1,9 +1,14 @@ #include "ruby_whisper.h" extern ID id_to_s; +extern ID id___method__; +extern ID id_to_enum; + +extern VALUE cParakeetContext; extern VALUE ruby_whisper_normalize_model_path(VALUE model_path); extern VALUE ruby_whisper_parakeet_transcribe(VALUE self, VALUE audio_path, VALUE params); +extern VALUE ruby_whisper_parakeet_segment_init(VALUE context, int index); static void ruby_whisper_parakeet_context_free(void *p) @@ -65,13 +70,33 @@ ruby_whisper_parakeet_context_initialize(int argc, VALUE *argv, VALUE self) return Qnil; } +static VALUE +ruby_whisper_parakeet_context_each_segment(VALUE self) +{ + if (!rb_block_given_p()) { + const VALUE method_name = rb_funcall(self, id___method__, 0); + return rb_funcall(self, id_to_enum, 1, method_name); + } + + ruby_whisper_parakeet_context *rwpc; + GetParakeetContext(self, rwpc); + + const int n_segments = parakeet_full_n_segments(rwpc->context); + for (int i = 0; i < n_segments; ++i) { + rb_yield(ruby_whisper_parakeet_segment_init(self, i)); + } + + return self; +} + void init_ruby_whisper_parakeet_context(VALUE *mParakeet) { - VALUE cParakeetContext = rb_define_class_under(*mParakeet, "Context", rb_cObject); + cParakeetContext = rb_define_class_under(*mParakeet, "Context", rb_cObject); rb_define_alloc_func(cParakeetContext, ruby_whisper_parakeet_context_allocate); rb_define_method(cParakeetContext, "initialize", ruby_whisper_parakeet_context_initialize, -1); rb_define_method(cParakeetContext, "transcribe", ruby_whisper_parakeet_transcribe, 2); + rb_define_method(cParakeetContext, "each_segment", ruby_whisper_parakeet_context_each_segment, 0); } diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c index 8a50c047a5f..c8565a9d5ef 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_params.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -35,6 +35,8 @@ enum { #define VAL_TO_BOOL(v) (RTEST(v)) #define VAL_FROM_BOOL(v) (v ? Qtrue : Qfalse) +extern VALUE cParakeetParams; + extern void ruby_whisper_callback_container_mark(ruby_whisper_callback_container *rwc); extern ruby_whisper_callback_container* ruby_whisper_callback_container_allocate(void); @@ -208,7 +210,7 @@ ITERATE_CALLBACK_PARAMS(INIT_CONTAINER) void init_ruby_whisper_parakeet_params(VALUE *mParakeet) { - VALUE cParakeetParams = rb_define_class_under(*mParakeet, "Params", rb_cObject); + cParakeetParams = rb_define_class_under(*mParakeet, "Params", rb_cObject); rb_define_alloc_func(cParakeetParams, ruby_whisper_parakeet_params_s_allocate); rb_define_method(cParakeetParams, "initialize", ruby_whisper_parakeet_params_initialize, -1); diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_segment.c b/bindings/ruby/ext/ruby_whisper_parakeet_segment.c index b154930777c..35ce9c15db3 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_segment.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_segment.c @@ -1,7 +1,59 @@ #include "ruby_whisper.h" +extern VALUE cParakeetSegment; + +static void +rb_whisper_parakeet_segment_mark(void *p) +{ + ruby_whisper_parakeet_segment *rwps = (ruby_whisper_parakeet_segment *)p; + rb_gc_mark(rwps->context); +} + +static size_t +ruby_whisper_parakeet_segment_memsize(const void *p) +{ + const ruby_whisper_parakeet_segment *rwps = (const ruby_whisper_parakeet_segment *)p; + if (!rwps) { + return 0; + } + size_t size = sizeof(*rwps); + if (rwps->index) { + size += sizeof(rwps->index); + } + return size; +} + +static const rb_data_type_t ruby_whisper_parakeet_segment_type = { + "ruby_whisper_segment", + {rb_whisper_parakeet_segment_mark, RUBY_DEFAULT_FREE, ruby_whisper_parakeet_segment_memsize,}, + 0, 0, + 0 +}; + +static VALUE +ruby_whisper_parakeet_segment_s_allocate(VALUE klass) +{ + ruby_whisper_parakeet_segment *rwps; + return TypedData_Make_Struct(klass, ruby_whisper_parakeet_segment, &ruby_whisper_parakeet_segment_type, rwps); +} + +VALUE +ruby_whisper_parakeet_segment_init(VALUE context, int index) +{ + ruby_whisper_parakeet_segment *rwps; + + const VALUE segment = ruby_whisper_parakeet_segment_s_allocate(cParakeetSegment); + TypedData_Get_Struct(segment, ruby_whisper_parakeet_segment, &ruby_whisper_parakeet_segment_type, rwps); + rwps->context = context; + rwps->index = index; + + return segment; +} + void init_ruby_whisper_parakeet_segment(VALUE *mParakeet) { - rb_define_class_under(*mParakeet, "Segment", rb_cObject); + cParakeetSegment = rb_define_class_under(*mParakeet, "Segment", rb_cObject); + + rb_define_alloc_func(cParakeetSegment, ruby_whisper_parakeet_segment_s_allocate); } diff --git a/bindings/ruby/test/test_parakeet_segment.rb b/bindings/ruby/test/test_parakeet_segment.rb new file mode 100644 index 00000000000..150543326a6 --- /dev/null +++ b/bindings/ruby/test/test_parakeet_segment.rb @@ -0,0 +1,14 @@ +require_relative "helper" + +class TestParakeetSegment < TestBase + def setup + @parakeet = Parakeet::Context.new(File.join(__dir__, "../../../models/parakeet-tdt-0.6b-v3.bin")) + @parakeet.transcribe AUDIO, Parakeet::Params.new + end + + def test_segment + @parakeet.each_segment do |segment| + assert_instance_of Parakeet::Segment, segment + end + end +end From 63d3136465c389ef2aa7936c09fb4834130f3faf Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 15 May 2026 02:26:24 +0900 Subject: [PATCH 026/118] Define Parakeet::Segment attributes --- bindings/ruby/ext/ruby_whisper.h | 7 ++++ .../ruby/ext/ruby_whisper_parakeet_segment.c | 35 +++++++++++++++++++ bindings/ruby/test/test_parakeet_segment.rb | 6 ++++ 3 files changed, 48 insertions(+) diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index c62a32ed37f..aacdbf2045c 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -157,4 +157,11 @@ typedef struct { } \ } while (0) +#define GetParakeetSegment(obj, rwps) do { \ + TypedData_Get_Struct((obj), ruby_whisper_parakeet_segment, &ruby_whisper_parakeet_segment_type, (rwps)); \ + if (!(rwps)->context) { \ + rb_raise(rb_eRuntimeError, "Not initialized"); \ + } \ +} while (0) + #endif diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_segment.c b/bindings/ruby/ext/ruby_whisper_parakeet_segment.c index 35ce9c15db3..76a2fcd1874 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_segment.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_segment.c @@ -1,6 +1,26 @@ #include "ruby_whisper.h" +#define ITERATE_ATTRS(ITERATOR) \ + ITERATOR(start_time, t0, TIME) \ + ITERATOR(end_time, t1, TIME) \ + ITERATOR(text, text, STRING) + +#define VAL_FROM_TIME(v) (LONG2NUM((v) * 10)) +#define VAL_FROM_STRING(v) (rb_str_new2(v)) +#define READER(type) VAL_FROM_##type +#define DEF_ATTR(rb_name, c_name, type) \ + static VALUE \ + ruby_whisper_parakeet_get_##rb_name(VALUE self) \ + { \ + ruby_whisper_parakeet_segment *rwps; \ + GetParakeetSegment(self, rwps); \ + ruby_whisper_parakeet_context *rwpc; \ + GetParakeetContext(rwps->context, rwpc); \ + return READER(type)(parakeet_full_get_segment_##c_name(rwpc->context, rwps->index)); \ + } + extern VALUE cParakeetSegment; +extern const rb_data_type_t ruby_whisper_parakeet_context_type; static void rb_whisper_parakeet_segment_mark(void *p) @@ -50,10 +70,25 @@ ruby_whisper_parakeet_segment_init(VALUE context, int index) return segment; } +ITERATE_ATTRS(DEF_ATTR) + void init_ruby_whisper_parakeet_segment(VALUE *mParakeet) { cParakeetSegment = rb_define_class_under(*mParakeet, "Segment", rb_cObject); rb_define_alloc_func(cParakeetSegment, ruby_whisper_parakeet_segment_s_allocate); + +#define REGISTER_ATTR(rb_name, c_name, type) \ + rb_define_method(cParakeetSegment, #rb_name, ruby_whisper_parakeet_get_##rb_name, 0); + + ITERATE_ATTRS(REGISTER_ATTR) + +#undef REGISTER_ATTR } + +#undef DEF_ATTR +#undef READER +#undef VAL_FROM_STRING +#undef VAL_FROM_TIME +#undef ITERATE_ATTRS diff --git a/bindings/ruby/test/test_parakeet_segment.rb b/bindings/ruby/test/test_parakeet_segment.rb index 150543326a6..019fca36f41 100644 --- a/bindings/ruby/test/test_parakeet_segment.rb +++ b/bindings/ruby/test/test_parakeet_segment.rb @@ -7,8 +7,14 @@ def setup end def test_segment + whole_text = "" @parakeet.each_segment do |segment| assert_instance_of Parakeet::Segment, segment + assert_kind_of Integer, segment.start_time + assert segment.end_time >= segment.start_time + assert_kind_of String, segment.text + whole_text << segment.text end + assert_match(/ask not what your country can do for you, ask what you can do for your country/, whole_text) end end From c2c12db7f7d6819942dac256d5c7be38564ed292 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 15 May 2026 02:59:57 +0900 Subject: [PATCH 027/118] Define Parakeet::Segment#deconstruct_keys --- .../ruby/ext/ruby_whisper_parakeet_segment.c | 54 +++++++++++++++++++ bindings/ruby/ext/ruby_whisper_segment.c | 12 ++--- 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_segment.c b/bindings/ruby/ext/ruby_whisper_parakeet_segment.c index 76a2fcd1874..ec54460050e 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_segment.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_segment.c @@ -5,6 +5,15 @@ ITERATOR(end_time, t1, TIME) \ ITERATOR(text, text, STRING) +enum { +#define DEF_IDX(name, c_name, type) RUBY_WHISPER_PARAKEET_SEGMENT_##name, + + ITERATE_ATTRS(DEF_IDX) + RUBY_WHISPER_PARAKEET_SEGMENT_NUM_ATTRS, + +#undef DEF_IDX +}; + #define VAL_FROM_TIME(v) (LONG2NUM((v) * 10)) #define VAL_FROM_STRING(v) (rb_str_new2(v)) #define READER(type) VAL_FROM_##type @@ -20,6 +29,9 @@ } extern VALUE cParakeetSegment; +extern VALUE sym_start_time; +extern VALUE sym_end_time; +extern VALUE sym_text; extern const rb_data_type_t ruby_whisper_parakeet_context_type; static void @@ -72,6 +84,46 @@ ruby_whisper_parakeet_segment_init(VALUE context, int index) ITERATE_ATTRS(DEF_ATTR) +static VALUE +ruby_whisper_parakeet_segment_deconstruct_keys(VALUE self, VALUE keys) +{ + ruby_whisper_parakeet_segment *rwps; + GetParakeetSegment(self, rwps); + ruby_whisper_parakeet_context *rwpc; + GetParakeetContext(rwps->context, rwpc); + + VALUE hash = rb_hash_new(); + long n_keys; + if (NIL_P(keys)) { + keys = rb_ary_new3( + RUBY_WHISPER_PARAKEET_SEGMENT_NUM_ATTRS, + sym_start_time, + sym_end_time, + sym_text + ); + n_keys = RUBY_WHISPER_PARAKEET_SEGMENT_NUM_ATTRS; + } else { + n_keys = RARRAY_LEN(keys); + if (n_keys > RUBY_WHISPER_PARAKEET_SEGMENT_NUM_ATTRS) { + return hash; + } + } + for (int i = 0; i < n_keys; i++) { + VALUE key = rb_ary_entry(keys, i); + +#define CHECK_AND_SET_KEY(rb_name, c_name, type) \ + if (key == sym_##rb_name) { \ + rb_hash_aset(hash, key, ruby_whisper_parakeet_get_##rb_name(self)); \ + } + + ITERATE_ATTRS(CHECK_AND_SET_KEY) + +#undef CHECK_AND_SET_KEY + } + + return hash; +} + void init_ruby_whisper_parakeet_segment(VALUE *mParakeet) { @@ -85,6 +137,8 @@ init_ruby_whisper_parakeet_segment(VALUE *mParakeet) ITERATE_ATTRS(REGISTER_ATTR) #undef REGISTER_ATTR + + rb_define_method(cParakeetSegment, "deconstruct_keys", ruby_whisper_parakeet_segment_deconstruct_keys, 1); } #undef DEF_ATTR diff --git a/bindings/ruby/ext/ruby_whisper_segment.c b/bindings/ruby/ext/ruby_whisper_segment.c index ee0d66c4cc8..cf0372797d3 100644 --- a/bindings/ruby/ext/ruby_whisper_segment.c +++ b/bindings/ruby/ext/ruby_whisper_segment.c @@ -4,12 +4,12 @@ extern ID id___method__; extern ID id_to_enum; -static VALUE sym_start_time; -static VALUE sym_end_time; -static VALUE sym_text; -static VALUE sym_no_speech_prob; -static VALUE sym_speaker_turn_next; -static VALUE sym_n_tokens; +VALUE sym_start_time; +VALUE sym_end_time; +VALUE sym_text; +VALUE sym_no_speech_prob; +VALUE sym_speaker_turn_next; +VALUE sym_n_tokens; extern const rb_data_type_t ruby_whisper_type; From 99412897f74cc5cd9fdd2336bdef010588ed5712 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 15 May 2026 03:00:19 +0900 Subject: [PATCH 028/118] Add tests for Parakeet::Segment#deconstruct_keys --- bindings/ruby/test/test_parakeet_segment.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/bindings/ruby/test/test_parakeet_segment.rb b/bindings/ruby/test/test_parakeet_segment.rb index 019fca36f41..3192fb7bf0b 100644 --- a/bindings/ruby/test/test_parakeet_segment.rb +++ b/bindings/ruby/test/test_parakeet_segment.rb @@ -17,4 +17,24 @@ def test_segment end assert_match(/ask not what your country can do for you, ask what you can do for your country/, whole_text) end + + def test_deconstruct_keys + segment = @parakeet.each_segment.first + expected = { + start_time: segment.start_time, + end_time: segment.end_time, + text: segment.text + } + assert_equal expected, segment.deconstruct_keys([:start_time, :end_time, :text]) + end + + def test_deconstruct_keys_with_nil + segment = @parakeet.each_segment.first + expected = { + start_time: segment.start_time, + end_time: segment.end_time, + text: segment.text + } + assert_equal expected, segment.deconstruct_keys(nil) + end end From ae88b19443440c93e3c3858fb1b213e7b02c8e7a Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 15 May 2026 03:32:58 +0900 Subject: [PATCH 029/118] Run Parakeet::Context#transcribe without GVL --- .../ext/ruby_whisper_parakeet_transcribe.cpp | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp b/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp index ba713dc8cdf..c5e9a412b24 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp +++ b/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp @@ -13,6 +13,23 @@ extern const rb_data_type_t ruby_whisper_parakeet_params_type; extern ID id_to_s; extern ID id_to_path; +static struct transcribe_without_gvl_args { + struct parakeet_context *context; + struct parakeet_full_params params; + float *samples; + size_t n_samples; + int result; +} transcribe_without_gvl_args; + +static void* +transcribe_without_gvl(void *rb_args) +{ + struct transcribe_without_gvl_args *args = (struct transcribe_without_gvl_args *)rb_args; + args->result = parakeet_full(args->context, args->params, args->samples, args->n_samples); + + return NULL; +} + VALUE ruby_whisper_parakeet_transcribe(VALUE self, VALUE audio_path, VALUE params) { @@ -34,7 +51,16 @@ ruby_whisper_parakeet_transcribe(VALUE self, VALUE audio_path, VALUE params) GetParakeetContext(self, rwpc); GetParakeetParams(params, rwpp); - if (parakeet_full(rwpc->context, rwpp->params, pcmf32.data(), pcmf32.size()) != 0) { + struct transcribe_without_gvl_args args = { + rwpc->context, + rwpp->params, + pcmf32.data(), + pcmf32.size(), + 0, + }; + + rb_thread_call_without_gvl(transcribe_without_gvl, (void *)&args, NULL, NULL); + if (args.result != 0) { rb_raise(rb_eRuntimeError, "Failed to process audio"); return Qnil; } From 4f53fad54ea5b2c6c935350ea592f9985e924115 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sat, 16 May 2026 10:26:17 +0900 Subject: [PATCH 030/118] Make it to abort for Parakeet --- bindings/ruby/ext/ruby_whisper.h | 5 +++ .../ruby/ext/ruby_whisper_parakeet_params.c | 17 +++++++++ .../ext/ruby_whisper_parakeet_transcribe.cpp | 37 ++++++++++++++++--- 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index aacdbf2045c..d312bfb3185 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "whisper.h" #include "parakeet.h" @@ -29,6 +30,10 @@ typedef struct { bool is_interrupted; } ruby_whisper_abort_callback_container; +typedef struct ruby_whisper_parakeet_abort_callback_user_data { + volatile rb_atomic_t is_interrupted; +} ruby_whisper_parakeet_abort_callback_user_data; + typedef struct { struct whisper_context *context; } ruby_whisper; diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c index c8565a9d5ef..58288b90757 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_params.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -44,6 +44,23 @@ static ID param_names[RUBY_WHISPER_PARAKEET_NUM_PARAMS]; typedef VALUE (*param_writer_t)(VALUE, VALUE); static param_writer_t param_writers[RUBY_WHISPER_PARAKEET_NUM_PARAMS]; +static bool +ruby_whisper_parakeet_abort_callback(void *user_data) +{ + ruby_whisper_parakeet_abort_callback_user_data *data = (ruby_whisper_parakeet_abort_callback_user_data *)user_data; + + int is_interrupted = RUBY_ATOMIC_LOAD(data->is_interrupted); + + return is_interrupted == 1; +} + +void +ruby_whisper_parakeet_prepare_transcription(ruby_whisper_parakeet_params *rwpp, ruby_whisper_parakeet_abort_callback_user_data *abort_callback_user_data) +{ + rwpp->params.abort_callback = ruby_whisper_parakeet_abort_callback; + rwpp->params.abort_callback_user_data = (void *)abort_callback_user_data; +} + static void ruby_whisper_parakeet_params_mark(void *p) { diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp b/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp index c5e9a412b24..ea64a9215dc 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp +++ b/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp @@ -10,8 +10,13 @@ extern "C" { extern const rb_data_type_t ruby_whisper_parakeet_context_type; extern const rb_data_type_t ruby_whisper_parakeet_params_type; +extern void ruby_whisper_parakeet_prepare_transcription(ruby_whisper_parakeet_params *rwpp, ruby_whisper_parakeet_abort_callback_user_data *abort_callback_user_data); + extern ID id_to_s; extern ID id_to_path; +extern ID id_new; + +extern VALUE eError; static struct transcribe_without_gvl_args { struct parakeet_context *context; @@ -21,6 +26,18 @@ static struct transcribe_without_gvl_args { int result; } transcribe_without_gvl_args; +typedef struct { + ruby_whisper_parakeet_abort_callback_user_data *abort_callback_user_data; +} ruby_whisper_parakeet_transcribe_ubf_args; + +static void +ruby_whisper_parakeet_transcribe_ubf(void *rb_args) +{ + ruby_whisper_parakeet_transcribe_ubf_args *args = (ruby_whisper_parakeet_transcribe_ubf_args *)rb_args; + + RUBY_ATOMIC_SET(args->abort_callback_user_data->is_interrupted, 1); +} + static void* transcribe_without_gvl(void *rb_args) { @@ -51,6 +68,11 @@ ruby_whisper_parakeet_transcribe(VALUE self, VALUE audio_path, VALUE params) GetParakeetContext(self, rwpc); GetParakeetParams(params, rwpp); + ruby_whisper_parakeet_abort_callback_user_data abort_callback_user_data = { + 0, + }; + ruby_whisper_parakeet_prepare_transcription(rwpp, &abort_callback_user_data); + struct transcribe_without_gvl_args args = { rwpc->context, rwpp->params, @@ -59,13 +81,16 @@ ruby_whisper_parakeet_transcribe(VALUE self, VALUE audio_path, VALUE params) 0, }; - rb_thread_call_without_gvl(transcribe_without_gvl, (void *)&args, NULL, NULL); - if (args.result != 0) { - rb_raise(rb_eRuntimeError, "Failed to process audio"); - return Qnil; - } + ruby_whisper_parakeet_transcribe_ubf_args ubf_args = { + &abort_callback_user_data, + }; - return self; + rb_thread_call_without_gvl(transcribe_without_gvl, (void *)&args, ruby_whisper_parakeet_transcribe_ubf, (void *)&ubf_args); + if (args.result == 0) { + return self; + } else { + rb_exc_raise(rb_funcall(eError, id_new, 1, args.result)); + } } #ifdef __cplusplus From cc958f0af5220585b7e4dfbf62b9a16dc62c74ab Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sun, 17 May 2026 04:11:48 +0900 Subject: [PATCH 031/118] Add Parakeet.log_set --- bindings/ruby/ext/ruby_whisper.c | 2 + bindings/ruby/ext/ruby_whisper.h | 23 +++ bindings/ruby/ext/ruby_whisper_log.c | 151 ++++++++++++++++++ bindings/ruby/ext/ruby_whisper_parakeet.c | 76 +++++++++ .../ruby/ext/ruby_whisper_parakeet_context.c | 19 ++- .../ruby/ext/ruby_whisper_parakeet_params.c | 37 +---- .../ruby/ext/ruby_whisper_parakeet_segment.c | 20 +-- .../ext/ruby_whisper_parakeet_transcribe.cpp | 2 +- bindings/ruby/lib/whisper/parakeet.rb | 32 ++++ bindings/ruby/test/test_parakeet.rb | 20 +++ bindings/ruby/test/test_parakeet_context.rb | 4 + 11 files changed, 332 insertions(+), 54 deletions(-) create mode 100644 bindings/ruby/ext/ruby_whisper_log.c create mode 100644 bindings/ruby/ext/ruby_whisper_parakeet.c create mode 100644 bindings/ruby/lib/whisper/parakeet.rb create mode 100644 bindings/ruby/test/test_parakeet.rb diff --git a/bindings/ruby/ext/ruby_whisper.c b/bindings/ruby/ext/ruby_whisper.c index 0c21e6252d3..40fb3d53ac7 100644 --- a/bindings/ruby/ext/ruby_whisper.c +++ b/bindings/ruby/ext/ruby_whisper.c @@ -49,6 +49,7 @@ extern void init_ruby_whisper_vad_params(VALUE *mVAD); extern void init_ruby_whisper_vad_context(VALUE *mVAD); extern void init_ruby_whisper_vad_segment(VALUE *mVAD); extern void init_ruby_whisper_vad_segments(VALUE *mVAD); +extern void init_ruby_whisper_parakeet(); extern void init_ruby_whisper_parakeet_params(VALUE *mParakeet); extern void init_ruby_whisper_parakeet_segment(VALUE *mParakeet); extern void init_ruby_whisper_parakeet_context(VALUE *mParakeet); @@ -244,6 +245,7 @@ void Init_whisper() { init_ruby_whisper_vad_segment(&mVAD); init_ruby_whisper_vad_segments(&mVAD); init_ruby_whisper_vad_context(&mVAD); + init_ruby_whisper_parakeet(); init_ruby_whisper_parakeet_params(&mParakeet); init_ruby_whisper_parakeet_segment(&mParakeet); init_ruby_whisper_parakeet_context(&mParakeet); diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index d312bfb3185..8d96b3333f7 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include "whisper.h" @@ -15,6 +16,9 @@ int ruby_thread_has_gvl_p(void); #endif +#define LOG_QUEUE_CAPACITY 256 +#define LOG_DEFAULT_CAPACITY 1024 + typedef struct { VALUE *context; VALUE user_data; @@ -34,6 +38,25 @@ typedef struct ruby_whisper_parakeet_abort_callback_user_data { volatile rb_atomic_t is_interrupted; } ruby_whisper_parakeet_abort_callback_user_data; +typedef struct ruby_whisper_log { + enum ggml_log_level level; + char *text; + size_t length; + size_t capacity; +} ruby_whisper_log; + +typedef struct ruby_whisper_log_queue { + rb_nativethread_lock_t lock; + rb_nativethread_cond_t cond; + + size_t head; + size_t tail; + size_t size; + bool is_active; + + ruby_whisper_log *logs; +} ruby_whisper_log_queue; + typedef struct { struct whisper_context *context; } ruby_whisper; diff --git a/bindings/ruby/ext/ruby_whisper_log.c b/bindings/ruby/ext/ruby_whisper_log.c new file mode 100644 index 00000000000..ed4746fd603 --- /dev/null +++ b/bindings/ruby/ext/ruby_whisper_log.c @@ -0,0 +1,151 @@ +#include "ruby_whisper.h" + +void +ruby_whisper_log_queue_initialize(ruby_whisper_log_queue *log_queue) +{ + rb_nativethread_lock_initialize(&log_queue->lock); + rb_native_cond_initialize(&log_queue->cond); + log_queue->head = 0; + log_queue->tail = 0; + log_queue->size = 0; + log_queue->is_active = true; + log_queue->logs = ALLOC_N(ruby_whisper_log, LOG_QUEUE_CAPACITY); + for (size_t i = 0; i < LOG_QUEUE_CAPACITY; i++) { + // we cannot call Ruby API like ALLOC_N because this slot may realloced without GVL + // doesn't free this because log queue lives at the end of process + char *slot = malloc(sizeof(char) * LOG_QUEUE_CAPACITY); + if (!slot) { + rb_raise(rb_eRuntimeError, "Could not allocate memory for log text"); + } + ruby_whisper_log log = { + 0, + slot, + 0, + LOG_QUEUE_CAPACITY, + }; + log_queue->logs[i] = log; + } +} + +static size_t +calc_enough_cap(size_t len) +{ + size_t quot = len / LOG_DEFAULT_CAPACITY; + size_t rem = len % LOG_DEFAULT_CAPACITY; + + return sizeof(char) * (rem == 0 ? quot : quot + 1) * LOG_DEFAULT_CAPACITY; +} + +void +ruby_whisper_log_queue_enqueue(ruby_whisper_log_queue *log_queue, enum ggml_log_level level, const char *text) +{ + rb_nativethread_lock_lock(&log_queue->lock); + + size_t len = strlen(text); + ruby_whisper_log *log = &log_queue->logs[log_queue->head]; + if (len > log->capacity) { + size_t new_cap = calc_enough_cap(len); + // we cannot call Ruby API like REALLOC_N because this function is called without GVL + char *slot = realloc(log->text, new_cap); + if (!slot) { + rb_nativethread_lock_unlock(&log_queue->lock); + return; + } + log->text = slot; + log->capacity = new_cap; + } + // we cannot call Ruby API like MEMCPY because this function is called without GVL + memcpy(log->text, text, sizeof(char) * len); + log->length = len; + log->level = level; + log_queue->head = (log_queue->head + 1) % LOG_QUEUE_CAPACITY; + bool is_full = log_queue->size >= LOG_QUEUE_CAPACITY; + log_queue->size = is_full ? LOG_QUEUE_CAPACITY : log_queue->size + 1; + if (is_full) { + log_queue->tail = log_queue->head; + } + + rb_native_cond_signal(&log_queue->cond); + rb_nativethread_lock_unlock(&log_queue->lock); +} + +static void* +ruby_whisper_log_queue_wait(void *args) +{ + ruby_whisper_log_queue *log_queue = (ruby_whisper_log_queue *)args; + + rb_native_cond_wait(&log_queue->cond, &log_queue->lock); + + return NULL; +} + +static void +ruby_whisper_log_queue_wait_ubf(void *args) +{ + ruby_whisper_log_queue *log_queue = (ruby_whisper_log_queue *)args; + + rb_nativethread_lock_lock(&log_queue->lock); + + rb_native_cond_broadcast(&log_queue->cond); + + rb_nativethread_lock_unlock(&log_queue->lock); +} + +typedef struct { + enum ggml_log_level level; + size_t length; + char *text; +} log_snapshot; + +VALUE +ruby_whisper_log_queue_drain(ruby_whisper_log_queue *log_queue) +{ + log_snapshot logs[LOG_QUEUE_CAPACITY]; + + rb_nativethread_lock_lock(&log_queue->lock); + + while (log_queue->size == 0 && log_queue->is_active) { + rb_thread_call_without_gvl(ruby_whisper_log_queue_wait, (void *)log_queue, ruby_whisper_log_queue_wait_ubf, (void *)log_queue); + } + + if (log_queue->size == 0 && !log_queue->is_active) { + rb_native_cond_broadcast(&log_queue->cond); + rb_nativethread_lock_unlock(&log_queue->lock); + return Qnil; + } + + size_t size = log_queue->size; + ruby_whisper_log *log; + size_t i; + for (i = 0; i < size; i++) { + log = &log_queue->logs[(log_queue->tail + i) % LOG_QUEUE_CAPACITY]; + logs[i].level = log->level; + logs[i].length = log->length; + char *text = malloc(log->length); + if (!text) { + logs[i].text = NULL; + continue; + } + logs[i].text = text; + memcpy(logs[i].text, log->text, log->length); + } + log_queue->size = 0; + log_queue->tail = log_queue->head; + + rb_native_cond_signal(&log_queue->cond); + + rb_nativethread_lock_unlock(&log_queue->lock); + + VALUE rb_logs = rb_ary_new2(size); + VALUE rb_text; + for (i = 0; i < size; i++) { + if (!logs[i].text) { + continue; + } + rb_text = rb_str_new(logs[i].text, logs[i].length); + free(logs[i].text); + rb_ary_push(rb_logs, rb_ary_new3(2, INT2NUM(logs[i].level), rb_text)); + } + + return rb_logs; +} diff --git a/bindings/ruby/ext/ruby_whisper_parakeet.c b/bindings/ruby/ext/ruby_whisper_parakeet.c new file mode 100644 index 00000000000..72653b125b5 --- /dev/null +++ b/bindings/ruby/ext/ruby_whisper_parakeet.c @@ -0,0 +1,76 @@ +#include "ruby_whisper.h" +#include +#include + +extern VALUE mParakeet; + +extern void ruby_whisper_log_queue_initialize(ruby_whisper_log_queue *log_queue); +extern void ruby_whisper_log_queue_enqueue(ruby_whisper_log_queue *log_queue, enum ggml_log_level level, const char *text); +extern VALUE ruby_whisper_log_queue_drain(ruby_whisper_log_queue *log_queue); + +ID id_start_log_callback_thread; + +static ruby_whisper_log_queue parakeet_log_queue; + +static VALUE +ruby_whisper_parakeet_s_drain_logs(VALUE self) +{ + return ruby_whisper_log_queue_drain(¶keet_log_queue); +} + +static void +ruby_whisper_parakeet_log_callback(enum ggml_log_level level, const char *text, void *user_data) +{ + ruby_whisper_log_queue_enqueue(¶keet_log_queue, level, text); +} + +static VALUE +ruby_whisper_parakeet_s_log_set(VALUE self, VALUE log_callback, VALUE user_data) +{ + if (NIL_P(log_callback)) { + parakeet_log_set(NULL, NULL); + } else { + rb_iv_set(self, "@log_callback", log_callback); + rb_iv_set(self, "@log_callback_user_data", user_data); + parakeet_log_queue.is_active = true; + rb_funcall(mParakeet, id_start_log_callback_thread, 0); + parakeet_log_set(ruby_whisper_parakeet_log_callback, NULL); + } + + return Qnil; +} + +static void +ruby_whisper_parakeet_end_proc(VALUE args) +{ + ID id_log_callback_thread = rb_intern("@log_callback_thread"); + ID id_alive = rb_intern("alive?"); + ID id_join = rb_intern("join"); + + rb_nativethread_lock_lock(¶keet_log_queue.lock); + + parakeet_log_queue.is_active = false; + rb_native_cond_broadcast(¶keet_log_queue.cond); + + rb_nativethread_lock_unlock(¶keet_log_queue.lock); + + VALUE log_callback_thread = rb_ivar_get(mParakeet, id_log_callback_thread); + if (!NIL_P(log_callback_thread) && RTEST(rb_funcall(log_callback_thread, id_alive, 0))) { + rb_funcall(log_callback_thread, id_join, 0); + } +} + +void +init_ruby_whisper_parakeet() +{ + id_start_log_callback_thread = rb_intern("start_log_callback_thread"); + + ruby_whisper_log_queue_initialize(¶keet_log_queue); + + rb_define_singleton_method(mParakeet, "log_set", ruby_whisper_parakeet_s_log_set, 2); + rb_define_private_method(rb_singleton_class(mParakeet), "drain_logs", ruby_whisper_parakeet_s_drain_logs, 0); + + rb_set_end_proc(ruby_whisper_parakeet_end_proc, Qnil); + rb_require("whisper/parakeet"); + rb_funcall(mParakeet, id_start_log_callback_thread, 0); +} diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_context.c b/bindings/ruby/ext/ruby_whisper_parakeet_context.c index 8bde0711c83..88c39aa8df0 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_context.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_context.c @@ -49,6 +49,19 @@ ruby_whisper_parakeet_context_allocate(VALUE klass) return obj; } +typedef struct { + struct parakeet_context **context; + char *model_path; +} ruby_whisper_parakeet_context_init_args; + +static void* +ruby_whisper_parakeet_context_init_without_gvl(void *args) +{ + ruby_whisper_parakeet_context_init_args *init_args = (ruby_whisper_parakeet_context_init_args *)args; + *init_args->context = parakeet_init_from_file_with_params(init_args->model_path, parakeet_context_default_params()); + return NULL; +} + static VALUE ruby_whisper_parakeet_context_initialize(int argc, VALUE *argv, VALUE self) { @@ -62,7 +75,11 @@ ruby_whisper_parakeet_context_initialize(int argc, VALUE *argv, VALUE self) if (!rb_respond_to(model_path, id_to_s)) { rb_raise(rb_eRuntimeError, "Expected file path to model to initialize Parakeet::Context"); } - rwpc->context = parakeet_init_from_file_with_params(StringValueCStr(model_path), parakeet_context_default_params()); + ruby_whisper_parakeet_context_init_args init_args = { + &rwpc->context, + StringValueCStr(model_path), + }; + rb_thread_call_without_gvl(ruby_whisper_parakeet_context_init_without_gvl, (void *)&init_args, NULL, NULL); if (rwpc->context == NULL) { rb_raise(rb_eRuntimeError, "Failed to load model"); } diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c index 58288b90757..2d5f9307916 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_params.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -24,9 +24,7 @@ enum { ITERATE_PARAMS(DEF_IDX) ITERATE_CALLBACK_PARAMS(DEF_IDX_CALLBACK) ITERATE_CALLBACK_PARAMS(DEF_IDX_USER_DATA) -#undef DEF_IDX -#undef DEF_IDX_CALLBACK -#undef DEF_IDX_USER_DATA + RUBY_WHISPER_PARAKEET_NUM_PARAMS }; @@ -69,8 +67,6 @@ ruby_whisper_parakeet_params_mark(void *p) #define MARK_CONTAINER(name) ruby_whisper_callback_container_mark(rwpp->name##_container); ITERATE_CALLBACK_PARAMS(MARK_CONTAINER) - -#undef MARK_CONTAINER } static void @@ -84,9 +80,7 @@ ruby_whisper_parakeet_params_free(void *p) xfree(rwpp->name##_container); \ } -ITERATE_CALLBACK_PARAMS(FREE_CONTAINER) - -#undef FREE_CONTAINER + ITERATE_CALLBACK_PARAMS(FREE_CONTAINER) } static size_t @@ -202,9 +196,7 @@ ruby_whisper_parakeet_params_initialize(int argc, VALUE *argv, VALUE self) #define INIT_CONTAINER(name) rwpp->name##_container = ruby_whisper_callback_container_allocate(); -ITERATE_CALLBACK_PARAMS(INIT_CONTAINER) - -#undef INIT_CONTAINER + ITERATE_CALLBACK_PARAMS(INIT_CONTAINER) rb_scan_args_kw(RB_SCAN_ARGS_KEYWORDS, argc, argv, ":", &kw_hash); if (NIL_P(kw_hash)) { @@ -252,27 +244,4 @@ init_ruby_whisper_parakeet_params(VALUE *mParakeet) rb_define_method(cParakeetParams, "on_" #name, ruby_whisper_parakeet_params_on_##name, 0); ITERATE_CALLBACK_PARAMS(REGISTER_HOOK) - -#undef REGISTER_PARAM -#undef REGISTER_PARAM_ATTR -#undef REGISTER_CALLBACK_PARAM_ATTR -#undef REGISTER_USER_DATA_PARAM_ATTR -#undef REGISTER_HOOK } - -#undef VAL_TO_INT -#undef VAL_FROM_INT -#undef VAL_TO_BOOL -#undef VAL_FROM_BOOL -#undef DEF_BOOL_PARAM_ATTR -#undef DEF_INT_PARAM_ATTR -#undef CALLBACK_CONTAINER_NAME -#undef DEF_CALLBACK_PARAM_ATTR -#undef DEF_USER_DATA_PARAM_ATTR -#undef DEF_HOOK -#undef READER -#undef WRITER -#undef DEF_PARAM_ATTR -#undef DEF_PARAM_ATTR_I -#undef ITERATE_PARAMS -#undef ITERATE_CALLBACK_PARAMS diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_segment.c b/bindings/ruby/ext/ruby_whisper_parakeet_segment.c index ec54460050e..313275840b7 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_segment.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_segment.c @@ -10,8 +10,6 @@ enum { ITERATE_ATTRS(DEF_IDX) RUBY_WHISPER_PARAKEET_SEGMENT_NUM_ATTRS, - -#undef DEF_IDX }; #define VAL_FROM_TIME(v) (LONG2NUM((v) * 10)) @@ -48,15 +46,11 @@ ruby_whisper_parakeet_segment_memsize(const void *p) if (!rwps) { return 0; } - size_t size = sizeof(*rwps); - if (rwps->index) { - size += sizeof(rwps->index); - } - return size; + return sizeof(*rwps); } static const rb_data_type_t ruby_whisper_parakeet_segment_type = { - "ruby_whisper_segment", + "ruby_whisper_parakeet_segment", {rb_whisper_parakeet_segment_mark, RUBY_DEFAULT_FREE, ruby_whisper_parakeet_segment_memsize,}, 0, 0, 0 @@ -117,8 +111,6 @@ ruby_whisper_parakeet_segment_deconstruct_keys(VALUE self, VALUE keys) } ITERATE_ATTRS(CHECK_AND_SET_KEY) - -#undef CHECK_AND_SET_KEY } return hash; @@ -136,13 +128,5 @@ init_ruby_whisper_parakeet_segment(VALUE *mParakeet) ITERATE_ATTRS(REGISTER_ATTR) -#undef REGISTER_ATTR - rb_define_method(cParakeetSegment, "deconstruct_keys", ruby_whisper_parakeet_segment_deconstruct_keys, 1); } - -#undef DEF_ATTR -#undef READER -#undef VAL_FROM_STRING -#undef VAL_FROM_TIME -#undef ITERATE_ATTRS diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp b/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp index ea64a9215dc..89244c77c4d 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp +++ b/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp @@ -18,7 +18,7 @@ extern ID id_new; extern VALUE eError; -static struct transcribe_without_gvl_args { +typedef struct transcribe_without_gvl_args { struct parakeet_context *context; struct parakeet_full_params params; float *samples; diff --git a/bindings/ruby/lib/whisper/parakeet.rb b/bindings/ruby/lib/whisper/parakeet.rb new file mode 100644 index 00000000000..e856ad2bb37 --- /dev/null +++ b/bindings/ruby/lib/whisper/parakeet.rb @@ -0,0 +1,32 @@ +require "mutex_m" + +module Whisper + module Parakeet + extend Mutex_m + + class << self + def start_log_callback_thread + return if @log_callback_thread&.alive? + + @log_callback_thread = Thread.new { + begin + while logs = drain_logs + begin + callback, user_data = synchronize {[@log_callback, @log_callback_user_data]} + next if callback.nil? + + logs.each do |(level, text)| + callback.call level, text, user_data + end + rescue => err + $stderr.puts err + end + end + rescue => err + $stderr.puts err + end + } + end + end + end +end diff --git a/bindings/ruby/test/test_parakeet.rb b/bindings/ruby/test/test_parakeet.rb new file mode 100644 index 00000000000..351ebed7b77 --- /dev/null +++ b/bindings/ruby/test/test_parakeet.rb @@ -0,0 +1,20 @@ +require_relative "helper" +require "stringio" + +class TestParakeet < TestBase + def test_log_set + log_callback = Parakeet.instance_variable_get("@log_callback") + user_data = Parakeet.instance_variable_get("@log_callback_user_data") + + $stdout = StringIO.new + Parakeet.log_set proc {|level, message, _| puts [level, message].join(": ")}, nil + Parakeet::Context.new(File.join(__dir__, "../../../models/parakeet-tdt-0.6b-v3.bin")) + sleep 0.1 + $stdout.rewind + logs = $stdout.string + assert_match /loading model from/, logs + ensure + $stdout = STDOUT + Parakeet.log_set log_callback, user_data + end +end diff --git a/bindings/ruby/test/test_parakeet_context.rb b/bindings/ruby/test/test_parakeet_context.rb index 9150fe926a8..2b50b0e44de 100644 --- a/bindings/ruby/test/test_parakeet_context.rb +++ b/bindings/ruby/test/test_parakeet_context.rb @@ -1,7 +1,11 @@ require_relative "helper" +require "stringio" class TestParakeetContext < TestBase def setup + Whisper.instance_variable_set "@whisper", nil + GC.start + @parakeet = Parakeet::Context.new(File.join(__dir__, "../../../models/parakeet-tdt-0.6b-v3.bin")) @params = Parakeet::Params.new end From 997470cfba73f8355e9e74ada821308322441c7b Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 18 May 2026 23:02:05 +0900 Subject: [PATCH 032/118] Define Parakeet::Token --- bindings/ruby/ext/ruby_whisper.c | 2 + bindings/ruby/ext/ruby_whisper.h | 12 ++ .../ruby/ext/ruby_whisper_parakeet_token.c | 179 ++++++++++++++++++ bindings/ruby/test/test_parakeet_token.rb | 71 +++++++ 4 files changed, 264 insertions(+) create mode 100644 bindings/ruby/ext/ruby_whisper_parakeet_token.c create mode 100644 bindings/ruby/test/test_parakeet_token.rb diff --git a/bindings/ruby/ext/ruby_whisper.c b/bindings/ruby/ext/ruby_whisper.c index 40fb3d53ac7..13b7c999c7e 100644 --- a/bindings/ruby/ext/ruby_whisper.c +++ b/bindings/ruby/ext/ruby_whisper.c @@ -51,6 +51,7 @@ extern void init_ruby_whisper_vad_segment(VALUE *mVAD); extern void init_ruby_whisper_vad_segments(VALUE *mVAD); extern void init_ruby_whisper_parakeet(); extern void init_ruby_whisper_parakeet_params(VALUE *mParakeet); +extern void init_ruby_whisper_parakeet_token(VALUE *mParakeet); extern void init_ruby_whisper_parakeet_segment(VALUE *mParakeet); extern void init_ruby_whisper_parakeet_context(VALUE *mParakeet); extern void register_callbacks(ruby_whisper_params *rwp, VALUE *context); @@ -247,6 +248,7 @@ void Init_whisper() { init_ruby_whisper_vad_context(&mVAD); init_ruby_whisper_parakeet(); init_ruby_whisper_parakeet_params(&mParakeet); + init_ruby_whisper_parakeet_token(&mParakeet); init_ruby_whisper_parakeet_segment(&mParakeet); init_ruby_whisper_parakeet_context(&mParakeet); diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index 8d96b3333f7..ee60e3747dc 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -131,6 +131,11 @@ typedef struct { int index; } ruby_whisper_parakeet_segment; +typedef struct { + parakeet_token_data *token_data; + VALUE text; +} ruby_whisper_parakeet_token; + #define GetContext(obj, rw) do { \ TypedData_Get_Struct((obj), ruby_whisper, &ruby_whisper_type, (rw)); \ if ((rw)->context == NULL) { \ @@ -192,4 +197,11 @@ typedef struct { } \ } while (0) +#define GetParakeetToken(obj, rwpt) do { \ + TypedData_Get_Struct((obj), ruby_whisper_parakeet_token, &ruby_whisper_parakeet_token_type, (rwpt)); \ + if (!(rwpt)->token_data) { \ + rb_raise(rb_eRuntimeError, "Not initialized"); \ + } \ +} while (0) + #endif diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_token.c b/bindings/ruby/ext/ruby_whisper_parakeet_token.c new file mode 100644 index 00000000000..98c30de4dbc --- /dev/null +++ b/bindings/ruby/ext/ruby_whisper_parakeet_token.c @@ -0,0 +1,179 @@ +#include "ruby_whisper.h" + +#define ITERATE_MEMBERS(ITERATOR) \ + ITERATOR(id, id, id, id, INT) \ + ITERATOR(duration_idx, duration_idx, duration_idx, duration_idx, INT) \ + ITERATOR(duration_value, duration_value, duration_value, duration_value, INT) \ + ITERATOR(frame_index, frame_index, frame_index, frame_index, INT) \ + ITERATOR(probability, probability, p, p, FLOAT) \ + ITERATOR(log_probability, log_probability, plog, plog, FLOAT) \ + ITERATOR(start_time, start_time, start_time, t0, TIME) \ + ITERATOR(end_time, end_time, end_time, t1, TIME) \ + ITERATOR(word_start?, word_start, word_start_p, is_word_start, BOOL) + +#define ITERATE_ATTRS(ITERATOR) \ + ITERATOR(text, text, text, text, STRING) + +enum { +#define DEF_IDX(rb_name, s_key, c_name, p_name, type) RUBY_WHISPER_PARAKEET_TOKEN_##c_name, + + ITERATE_MEMBERS(DEF_IDX) + ITERATE_ATTRS(DEF_IDX) + RUBY_WHISPER_PARAKEET_TOKEN_NUM_ATTRS, +}; + +#define VAL_FROM_INT(v) (INT2NUM(v)) +#define VAL_FROM_FLOAT(v) (DBL2NUM(v)) +#define VAL_FROM_TIME(v) (LONG2NUM(v * 10)) +#define VAL_FROM_BOOL(v) ((v) ? Qtrue : Qfalse) +#define VAL_FROM_STRING(v) (rb_str_new2(v)) + +#define READER(type) VAL_FROM_##type +#define MEMBER_NAME(name) name +#define DEF_MEMBER_ATTR(rb_name, s_key, c_name, p_name, type) \ + static VALUE \ + ruby_whisper_parakeet_token_get_##c_name(VALUE self) \ + { \ + ruby_whisper_parakeet_token *rwpt; \ + GetParakeetToken(self, rwpt); \ + return READER(type)(rwpt->token_data->MEMBER_NAME(p_name)); \ + } + +#define DEF_ATTR(rb_name, s_key, c_name, p_name, type) \ + static VALUE \ + ruby_whisper_parakeet_token_get_##c_name(VALUE self) \ + { \ + ruby_whisper_parakeet_token *rwpt; \ + GetParakeetToken(self, rwpt); \ + return rwpt->p_name; \ + } + +VALUE cParakeetToken; + +#define DEC_ATTR_SYMS(rb_name, s_key, c_name, p_name, type) static VALUE sym_##s_key; + +ITERATE_MEMBERS(DEC_ATTR_SYMS) +ITERATE_ATTRS(DEC_ATTR_SYMS) + +static void +ruby_whisper_parakeet_token_mark(void *p) +{ + ruby_whisper_parakeet_token *rwpt = (ruby_whisper_parakeet_token *)p; + rb_gc_mark(rwpt->text); +} + +static void +ruby_whisper_parakeet_token_free(void *p) +{ + ruby_whisper_parakeet_token *rwpt = (ruby_whisper_parakeet_token *)p; + if (rwpt->token_data) { + xfree(rwpt->token_data); + rwpt->token_data = NULL; + } + xfree(rwpt); +} + +static size_t +ruby_whisper_parakeet_token_memsize(const void *p) +{ + ruby_whisper_parakeet_token *rwpt = (ruby_whisper_parakeet_token *)p; + if (!rwpt) { + return 0; + } + size_t size = sizeof(*rwpt); + if (rwpt->token_data) { + size += sizeof(*rwpt->token_data); + } + + return size; +} + +static const rb_data_type_t ruby_whisper_parakeet_token_type = { + "ruby_whisper_parakeet_token", + {ruby_whisper_parakeet_token_mark, ruby_whisper_parakeet_token_free, ruby_whisper_parakeet_token_memsize}, + 0, 0, + 0, +}; + +static VALUE +ruby_whisper_parakeet_token_s_allocate(VALUE klass) +{ + ruby_whisper_parakeet_token *rwpt; + VALUE token = TypedData_Make_Struct(klass, ruby_whisper_parakeet_token, &ruby_whisper_parakeet_token_type, rwpt); + + rwpt->token_data = ALLOC(parakeet_token_data); + rwpt->text = Qnil; + + return token; +} + +VALUE +ruby_whisper_parakeet_token_s_init(struct parakeet_context *context, int i_segment, int i_token) +{ + const VALUE token = ruby_whisper_parakeet_token_s_allocate(cParakeetToken); + ruby_whisper_parakeet_token *rwpt; + TypedData_Get_Struct(token, ruby_whisper_parakeet_token, &ruby_whisper_parakeet_token_type, rwpt); + + *rwpt->token_data = parakeet_full_get_token_data(context, i_segment, i_token); + rwpt->text = rb_utf8_str_new_cstr(parakeet_full_get_token_text(context, i_segment, i_token)); + + return token; +} + +ITERATE_MEMBERS(DEF_MEMBER_ATTR) +ITERATE_ATTRS(DEF_ATTR) + +static VALUE +ruby_whisper_parakeet_token_deconstruct_keys(VALUE self, VALUE keys) +{ + ruby_whisper_parakeet_token *rwpt; + GetParakeetToken(self, rwpt); + + VALUE hash = rb_hash_new(); + long n_keys = 0; + + if (NIL_P(keys)) { + VALUE attrs[] = { +#define LIST_SYMS(rb_name, s_key, c_name, p_name, type) sym_##s_key, + + ITERATE_MEMBERS(LIST_SYMS) + ITERATE_ATTRS(LIST_SYMS) + }; + keys = rb_ary_new_from_values(RUBY_WHISPER_PARAKEET_TOKEN_NUM_ATTRS, attrs); + n_keys = RUBY_WHISPER_PARAKEET_TOKEN_NUM_ATTRS; + } else { + n_keys = RARRAY_LEN(keys); + if (n_keys > RUBY_WHISPER_PARAKEET_TOKEN_NUM_ATTRS) { + return hash; + } + } + for (long i = 0; i < n_keys; i++) { + VALUE key = rb_ary_entry(keys, i); + +#define CHECK_AND_SET_KEY(rb_name, s_key, c_name, p_name, type) \ + if (key == sym_##s_key) { \ + rb_hash_aset(hash, key, ruby_whisper_parakeet_token_get_##c_name(self)); \ + } + + ITERATE_MEMBERS(CHECK_AND_SET_KEY) + ITERATE_ATTRS(CHECK_AND_SET_KEY) + } + + return hash; +} + +void +init_ruby_whisper_parakeet_token(VALUE *mParakeet) +{ + cParakeetToken = rb_define_class_under(*mParakeet, "Token", rb_cObject); + rb_define_alloc_func(cParakeetToken, ruby_whisper_parakeet_token_s_allocate); + +#define REGISTER_ATTR(rb_name, s_key, c_name, p_name, type) \ + sym_##s_key = ID2SYM(rb_intern(#s_key)); \ + rb_define_method(cParakeetToken, #rb_name, ruby_whisper_parakeet_token_get_##c_name, 0); + + ITERATE_MEMBERS(REGISTER_ATTR) + ITERATE_ATTRS(REGISTER_ATTR) + + rb_define_method(cParakeetToken, "deconstruct_keys", ruby_whisper_parakeet_token_deconstruct_keys, 1); +} diff --git a/bindings/ruby/test/test_parakeet_token.rb b/bindings/ruby/test/test_parakeet_token.rb new file mode 100644 index 00000000000..3d211446a98 --- /dev/null +++ b/bindings/ruby/test/test_parakeet_token.rb @@ -0,0 +1,71 @@ +require_relative "helper" + +class TestParakeetToken < TestBase + ATTRS = %i[ + id + duration_idx + duration_value + frame_index + probability + log_probability + start_time + end_time + word_start? + text + ] + + def setup + Whisper.instance_variable_set "@whisper", nil + GC.start + + parakeet = Parakeet::Context.new(File.join(__dir__, "../../../models/parakeet-tdt-0.6b-v3.bin")) + params = Parakeet::Params.new + parakeet.transcribe AUDIO, params + @segment = parakeet.each_segment.first + end + + def test_each_token + i = 0 + @segment.each_token do |token| + i += 1 + assert_instance_of Parakeet::Token, token + end + assert_equal 33, i + end + + def test_each_token_without_block + assert_instance_of Enumerator, @segment.each_token + end + + def test_token + token = @segment.each_token.first + + assert_instance_of Parakeet::Token, token + assert_instance_of Integer, token.id + assert_instance_of Integer, token.duration_idx + assert_instance_of Integer, token.duration_value + assert_instance_of Integer, token.frame_index + assert_instance_of Float, token.probability + assert_instance_of Float, token.log_probability + assert_instance_of Integer, token.start_time + assert_instance_of Integer, token.end_time + assert_instance_of String, token.text + end + + def test_text + assert_equal ["▁And", "▁so", ",", "▁my", "▁f", "ell", "ow", "▁Amer", "ic", "ans", ",", "▁a", "sk", "▁not", "▁what", "▁your", "▁co", "un", "tr", "y", "▁can", "▁do", "▁for", "▁you", ",", "▁a", "sk", "▁what", "▁you", "▁can", "▁do", "▁for", "▁your"], + @segment.each_token.collect(&:text) + end + + def test_deconstruct_keys_with_nil + token = @segment.each_token.first + expected = ATTRS.collect {|attr| [attr.to_s.sub(/\?\z/, "").intern, token.send(attr)]}.to_h + assert_equal expected, token.deconstruct_keys(nil) + end + + def test_deconstruct_keys_with_keys + token = @segment.each_token.first + expected = ATTRS.collect {|attr| [attr.to_s.sub(/\?\z/, "").intern, token.send(attr)]}.to_h + assert_equal expected, token.deconstruct_keys(expected.keys) + end +end From 10cf358ced9e91e31dea5f66090aec80f3c876a3 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 18 May 2026 23:36:04 +0900 Subject: [PATCH 033/118] Define Parakeet::Segment#each_token --- .../ruby/ext/ruby_whisper_parakeet_segment.c | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_segment.c b/bindings/ruby/ext/ruby_whisper_parakeet_segment.c index 313275840b7..4479b6cda3c 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_segment.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_segment.c @@ -26,11 +26,14 @@ enum { return READER(type)(parakeet_full_get_segment_##c_name(rwpc->context, rwps->index)); \ } +extern ID id___method__; +extern ID id_to_enum; extern VALUE cParakeetSegment; extern VALUE sym_start_time; extern VALUE sym_end_time; extern VALUE sym_text; extern const rb_data_type_t ruby_whisper_parakeet_context_type; +extern VALUE ruby_whisper_parakeet_token_s_init(struct parakeet_context *context, int i_segment, int i_token); static void rb_whisper_parakeet_segment_mark(void *p) @@ -78,6 +81,27 @@ ruby_whisper_parakeet_segment_init(VALUE context, int index) ITERATE_ATTRS(DEF_ATTR) +static VALUE +ruby_whisper_parakeet_segment_each_token(VALUE self) +{ + if (!rb_block_given_p()) { + const VALUE method_name = rb_funcall(self, id___method__, 0); + return rb_funcall(self, id_to_enum, 1, method_name); + } + + ruby_whisper_parakeet_segment *rwps; + GetParakeetSegment(self, rwps); + ruby_whisper_parakeet_context *rwpc; + GetParakeetContext(rwps->context, rwpc); + + const int n_tokens = parakeet_full_n_tokens(rwpc->context, rwps->index); + for (int i = 0; i < n_tokens; i++) { + rb_yield(ruby_whisper_parakeet_token_s_init(rwpc->context, rwps->index, i)); + } + + return self; +} + static VALUE ruby_whisper_parakeet_segment_deconstruct_keys(VALUE self, VALUE keys) { @@ -128,5 +152,6 @@ init_ruby_whisper_parakeet_segment(VALUE *mParakeet) ITERATE_ATTRS(REGISTER_ATTR) + rb_define_method(cParakeetSegment, "each_token", ruby_whisper_parakeet_segment_each_token, 0); rb_define_method(cParakeetSegment, "deconstruct_keys", ruby_whisper_parakeet_segment_deconstruct_keys, 1); } From 94f327a67b8f9018c03bc5034c18310d2772b7fd Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Tue, 19 May 2026 23:03:55 +0900 Subject: [PATCH 034/118] Implement some hooks of Parakeet::Params --- bindings/ruby/ext/ruby_whisper.h | 1 + .../ruby/ext/ruby_whisper_parakeet_params.c | 176 ++++++++++++++++-- .../ruby/ext/ruby_whisper_parakeet_segment.c | 4 +- .../ruby/ext/ruby_whisper_parakeet_token.c | 14 +- .../ext/ruby_whisper_parakeet_transcribe.cpp | 5 +- bindings/ruby/ext/ruby_whisper_params.c | 2 +- bindings/ruby/ext/ruby_whisper_segment.c | 4 +- 7 files changed, 182 insertions(+), 24 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index ee60e3747dc..c4839346e5e 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -36,6 +36,7 @@ typedef struct { typedef struct ruby_whisper_parakeet_abort_callback_user_data { volatile rb_atomic_t is_interrupted; + ruby_whisper_callback_container *callback_container; } ruby_whisper_parakeet_abort_callback_user_data; typedef struct ruby_whisper_log { diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c index 2d5f9307916..740df507e8c 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_params.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -10,11 +10,18 @@ ITERATOR(left_context_ms, INT) \ ITERATOR(right_context_ms, INT) +#define ITERATE_NORMAL_CALLBACK_NAMES(ITERATOR, DATA) \ + ITERATOR(new_segment, DATA) \ + ITERATOR(new_token, DATA) \ + ITERATOR(progress, DATA) \ + ITERATOR(encoder_begin, DATA) + +#define ITERATE_NORMAL_CALLBACK_PARAM(name, ITERATOR) ITERATOR(name##_callback) +#define ITERATE_NORMAL_CALLBACK_PARAMS(ITERATOR) \ + ITERATE_NORMAL_CALLBACK_NAMES(ITERATE_NORMAL_CALLBACK_PARAM, ITERATOR) + #define ITERATE_CALLBACK_PARAMS(ITERATOR) \ - ITERATOR(new_segment_callback) \ - ITERATOR(new_token_callback) \ - ITERATOR(progress_callback) \ - ITERATOR(encoder_begin_callback) \ + ITERATE_NORMAL_CALLBACK_PARAMS(ITERATOR) \ ITERATOR(abort_callback) enum { @@ -34,14 +41,133 @@ enum { #define VAL_FROM_BOOL(v) (v ? Qtrue : Qfalse) extern VALUE cParakeetParams; +extern ID id_call; extern void ruby_whisper_callback_container_mark(ruby_whisper_callback_container *rwc); extern ruby_whisper_callback_container* ruby_whisper_callback_container_allocate(void); +extern bool ruby_whisper_callback_container_is_present(const ruby_whisper_callback_container *container); +extern VALUE ruby_whisper_parakeet_segment_init(VALUE context, int index); +extern VALUE ruby_whisper_parakeet_token_s_from_token_data(struct parakeet_context *context, parakeet_token_data *token_data); static ID param_names[RUBY_WHISPER_PARAKEET_NUM_PARAMS]; typedef VALUE (*param_writer_t)(VALUE, VALUE); static param_writer_t param_writers[RUBY_WHISPER_PARAKEET_NUM_PARAMS]; +typedef struct { + const ruby_whisper_callback_container *container; + struct parakeet_state *state; + int n_new; +} call_parakeet_new_segment_callbacks_args; + +static void* +call_parakeet_new_segment_callbacks(void *v_args) +{ + call_parakeet_new_segment_callbacks_args *args = (call_parakeet_new_segment_callbacks_args *)v_args; + const ruby_whisper_callback_container *container = args->container; + + if (!NIL_P(container->callback)) { + rb_funcall(container->callback, id_call, 4, *container->context, Qnil, INT2NUM(args->n_new), container->user_data); + } + if (NIL_P(container->callbacks)) { + return NULL; + } + const long n_callbacks = RARRAY_LEN(container->callbacks); + if (n_callbacks == 0) { + return NULL; + } + const int n_segments = parakeet_full_n_segments_from_state(args->state); + for (int i = args->n_new; i > 0; i--) { + int i_segment = n_segments - i; + VALUE segment = ruby_whisper_parakeet_segment_init(*container->context, i_segment); + for (int j = 0; j < n_callbacks; j++) { + VALUE cb = rb_ary_entry(container->callbacks, j); + rb_funcall(cb, id_call, 1, segment); + } + } + + return NULL; +} + +static void +ruby_whisper_parakeet_new_segment_callback(struct parakeet_context *context, struct parakeet_state *state, int n_new, void *user_data) +{ + const ruby_whisper_callback_container *container = (ruby_whisper_callback_container *)user_data; + if (!ruby_whisper_callback_container_is_present(container)) { + return; + } + + call_parakeet_new_segment_callbacks_args args = { + container, + state, + n_new, + }; + rb_thread_call_with_gvl(call_parakeet_new_segment_callbacks, (void *)&args); +} + +typedef struct { + const ruby_whisper_callback_container *container; + struct parakeet_context *context; + struct parakeet_state *state; + const parakeet_token_data *token_data; +} call_parakeet_new_token_callbacks_args; + +static void* +call_parakeet_new_token_callbacks(void *v_args) +{ + call_parakeet_new_token_callbacks_args *args = (call_parakeet_new_token_callbacks_args *)v_args; + VALUE token = Qnil; + const ruby_whisper_callback_container *container = args->container; + + if (!NIL_P(container->callback)) { + token = ruby_whisper_parakeet_token_s_from_token_data(args->context, args->token_data); + rb_funcall(container->callback, id_call, 4, *container->context, Qnil, token, container->user_data); + } + if (NIL_P(container->callbacks)) { + return NULL; + } + const long n_callbacks = RARRAY_LEN(container->callbacks); + if (n_callbacks == 0) { + return NULL; + } + if (NIL_P(token)) { + token = ruby_whisper_parakeet_token_s_from_token_data(args->context, args->token_data); + } + for (int i = 0; i < n_callbacks; i++) { + VALUE cb = rb_ary_entry(container->callbacks, i); + rb_funcall(cb, id_call, 1, token); + } + + return NULL; +} + +static void +ruby_whisper_parakeet_new_token_callback(struct parakeet_context *context, struct parakeet_state *state, const parakeet_token_data *token_data, void *user_data) +{ + const ruby_whisper_callback_container *container = (ruby_whisper_callback_container *)user_data; + if (!ruby_whisper_callback_container_is_present(container)) { + return; + } + + call_parakeet_new_token_callbacks_args args = { + container, + context, + state, + token_data, + }; + rb_thread_call_with_gvl(call_parakeet_new_token_callbacks, (void *)&args); +} + +static void +ruby_whisper_parakeet_progress_callback(struct parakeet_context *context, struct parakeet_state *state, int progress, void *user_data) +{ +} + +static bool +ruby_whisper_parakeet_encoder_begin_callback(struct parakeet_context *context, struct parakeet_state *state, void *user_data) +{ + return true; +} + static bool ruby_whisper_parakeet_abort_callback(void *user_data) { @@ -52,9 +178,25 @@ ruby_whisper_parakeet_abort_callback(void *user_data) return is_interrupted == 1; } +#define CALLBACK_CONTAINER_NAME(name) name ## _container + void -ruby_whisper_parakeet_prepare_transcription(ruby_whisper_parakeet_params *rwpp, ruby_whisper_parakeet_abort_callback_user_data *abort_callback_user_data) +ruby_whisper_parakeet_prepare_transcription(ruby_whisper_parakeet_params *rwpp, VALUE *context, ruby_whisper_parakeet_abort_callback_user_data *abort_callback_user_data) { +#define PARAM_NAME(name) name +#define USER_DATA_NAME(name) name##_user_data +#define REGISTER_CALLBACK(name) \ + if (ruby_whisper_callback_container_is_present(rwpp->CALLBACK_CONTAINER_NAME(name))) { \ + rwpp->CALLBACK_CONTAINER_NAME(name)->context = context; \ + rwpp->params.PARAM_NAME(name) = ruby_whisper_parakeet_##name; \ + rwpp->params.USER_DATA_NAME(name) = rwpp->CALLBACK_CONTAINER_NAME(name); \ + } + + ITERATE_NORMAL_CALLBACK_PARAMS(REGISTER_CALLBACK) + + if (ruby_whisper_callback_container_is_present(rwpp->abort_callback_container)) { + abort_callback_user_data->callback_container = rwpp->abort_callback_container; + } rwpp->params.abort_callback = ruby_whisper_parakeet_abort_callback; rwpp->params.abort_callback_user_data = (void *)abort_callback_user_data; } @@ -119,8 +261,6 @@ const rb_data_type_t ruby_whisper_parakeet_params_type = { return val; \ } -#define CALLBACK_CONTAINER_NAME(name) name ## _container - #define DEF_CALLBACK_PARAM_ATTR(name) \ static VALUE \ ruby_whisper_parakeet_params_get_##name(VALUE self) \ @@ -155,24 +295,30 @@ const rb_data_type_t ruby_whisper_parakeet_params_type = { return val; \ } -#define DEF_HOOK(name) \ +#define DEF_HOOK(name, data) \ static VALUE \ ruby_whisper_parakeet_params_on_##name(VALUE self) \ { \ ruby_whisper_parakeet_params *rwpp; \ GetParakeetParams(self, rwpp); \ const VALUE blk = rb_block_proc(); \ - if (!rwpp->name##_container->callbacks) { \ - rwpp->name##_container->callbacks = rb_ary_new(); \ + if (NIL_P(rwpp->name##_callback_container->callbacks)) { \ + rwpp->name##_callback_container->callbacks = rb_ary_new(); \ } \ - rb_ary_push(rwpp->name##_container->callbacks, blk); \ + rb_ary_push(rwpp->name##_callback_container->callbacks, blk); \ return Qnil; \ } ITERATE_PARAMS(DEF_PARAM_ATTR) ITERATE_CALLBACK_PARAMS(DEF_CALLBACK_PARAM_ATTR) ITERATE_CALLBACK_PARAMS(DEF_USER_DATA_PARAM_ATTR) -ITERATE_CALLBACK_PARAMS(DEF_HOOK) +ITERATE_NORMAL_CALLBACK_NAMES(DEF_HOOK, _) + +static VALUE +ruby_whisper_parakeet_params_abort_on(VALUE self) +{ + return Qnil; +} static VALUE ruby_whisper_parakeet_params_s_allocate(VALUE klass) @@ -240,8 +386,10 @@ init_ruby_whisper_parakeet_params(VALUE *mParakeet) ITERATE_CALLBACK_PARAMS(REGISTER_CALLBACK_PARAM_ATTR) ITERATE_CALLBACK_PARAMS(REGISTER_USER_DATA_PARAM_ATTR) -#define REGISTER_HOOK(name) \ +#define REGISTER_HOOK(name, data) \ rb_define_method(cParakeetParams, "on_" #name, ruby_whisper_parakeet_params_on_##name, 0); - ITERATE_CALLBACK_PARAMS(REGISTER_HOOK) + ITERATE_NORMAL_CALLBACK_NAMES(REGISTER_HOOK, _) + + rb_define_method(cParakeetParams, "abort_on", ruby_whisper_parakeet_params_abort_on, 0); } diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_segment.c b/bindings/ruby/ext/ruby_whisper_parakeet_segment.c index 4479b6cda3c..b1e81ba930c 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_segment.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_segment.c @@ -33,7 +33,7 @@ extern VALUE sym_start_time; extern VALUE sym_end_time; extern VALUE sym_text; extern const rb_data_type_t ruby_whisper_parakeet_context_type; -extern VALUE ruby_whisper_parakeet_token_s_init(struct parakeet_context *context, int i_segment, int i_token); +extern VALUE ruby_whisper_parakeet_token_s_from_index(struct parakeet_context *context, int i_segment, int i_token); static void rb_whisper_parakeet_segment_mark(void *p) @@ -96,7 +96,7 @@ ruby_whisper_parakeet_segment_each_token(VALUE self) const int n_tokens = parakeet_full_n_tokens(rwpc->context, rwps->index); for (int i = 0; i < n_tokens; i++) { - rb_yield(ruby_whisper_parakeet_token_s_init(rwpc->context, rwps->index, i)); + rb_yield(ruby_whisper_parakeet_token_s_from_index(rwpc->context, rwps->index, i)); } return self; diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_token.c b/bindings/ruby/ext/ruby_whisper_parakeet_token.c index 98c30de4dbc..e1da3e413c1 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_token.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_token.c @@ -108,19 +108,27 @@ ruby_whisper_parakeet_token_s_allocate(VALUE klass) } VALUE -ruby_whisper_parakeet_token_s_init(struct parakeet_context *context, int i_segment, int i_token) +ruby_whisper_parakeet_token_s_from_token_data(struct parakeet_context *context, const parakeet_token_data *token_data) { const VALUE token = ruby_whisper_parakeet_token_s_allocate(cParakeetToken); ruby_whisper_parakeet_token *rwpt; TypedData_Get_Struct(token, ruby_whisper_parakeet_token, &ruby_whisper_parakeet_token_type, rwpt); - *rwpt->token_data = parakeet_full_get_token_data(context, i_segment, i_token); - rwpt->text = rb_utf8_str_new_cstr(parakeet_full_get_token_text(context, i_segment, i_token)); + *rwpt->token_data = *token_data; + rwpt->text = rb_utf8_str_new_cstr(parakeet_token_to_str(context, token_data->id)); return token; } +VALUE +ruby_whisper_parakeet_token_s_from_index(struct parakeet_context *context, int i_segment, int i_token) +{ + parakeet_token_data token_data = parakeet_full_get_token_data(context, i_segment, i_token); + return ruby_whisper_parakeet_token_s_from_token_data(context, &token_data); +} + ITERATE_MEMBERS(DEF_MEMBER_ATTR) +// Define #text using parakeet_token_to_str or parakeet_token_to_text ITERATE_ATTRS(DEF_ATTR) static VALUE diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp b/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp index 89244c77c4d..11439547331 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp +++ b/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp @@ -10,7 +10,7 @@ extern "C" { extern const rb_data_type_t ruby_whisper_parakeet_context_type; extern const rb_data_type_t ruby_whisper_parakeet_params_type; -extern void ruby_whisper_parakeet_prepare_transcription(ruby_whisper_parakeet_params *rwpp, ruby_whisper_parakeet_abort_callback_user_data *abort_callback_user_data); +extern void ruby_whisper_parakeet_prepare_transcription(ruby_whisper_parakeet_params *rwpp, VALUE *context, ruby_whisper_parakeet_abort_callback_user_data *abort_callback_user_data); extern ID id_to_s; extern ID id_to_path; @@ -70,8 +70,9 @@ ruby_whisper_parakeet_transcribe(VALUE self, VALUE audio_path, VALUE params) ruby_whisper_parakeet_abort_callback_user_data abort_callback_user_data = { 0, + NULL, }; - ruby_whisper_parakeet_prepare_transcription(rwpp, &abort_callback_user_data); + ruby_whisper_parakeet_prepare_transcription(rwpp, &self, &abort_callback_user_data); struct transcribe_without_gvl_args args = { rwpc->context, diff --git a/bindings/ruby/ext/ruby_whisper_params.c b/bindings/ruby/ext/ruby_whisper_params.c index 7ec0ac107d8..7447cf5af27 100644 --- a/bindings/ruby/ext/ruby_whisper_params.c +++ b/bindings/ruby/ext/ruby_whisper_params.c @@ -119,7 +119,7 @@ rb_whisper_abort_callback_container_allocate() { return container; } -static bool +bool ruby_whisper_callback_container_is_present(const ruby_whisper_callback_container *container) { return !NIL_P(container->callback) || !NIL_P(container->callbacks); } diff --git a/bindings/ruby/ext/ruby_whisper_segment.c b/bindings/ruby/ext/ruby_whisper_segment.c index cf0372797d3..50974a6f3d8 100644 --- a/bindings/ruby/ext/ruby_whisper_segment.c +++ b/bindings/ruby/ext/ruby_whisper_segment.c @@ -15,7 +15,7 @@ extern const rb_data_type_t ruby_whisper_type; extern VALUE cSegment; -extern VALUE ruby_whisper_token_s_init(struct whisper_context *context, int i_segment, int index); +extern VALUE ruby_whisper_token_s_from_index(struct whisper_context *context, int i_segment, int index); static void rb_whisper_segment_mark(void *p) @@ -190,7 +190,7 @@ ruby_whisper_segment_each_token(VALUE self) const int n_tokens = whisper_full_n_tokens(rw->context, rws->index); for (int i = 0; i < n_tokens; ++i) { - rb_yield(ruby_whisper_token_s_init(rw->context, rws->index, i)); + rb_yield(ruby_whisper_token_s_from_index(rw->context, rws->index, i)); } return self; From de505d23ad0ddb03c5f58ba9f0f0bb2fe6cd26a2 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 20 May 2026 16:47:04 +0900 Subject: [PATCH 035/118] Convert int to VALUE --- bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp b/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp index 11439547331..119668c3c40 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp +++ b/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp @@ -90,7 +90,7 @@ ruby_whisper_parakeet_transcribe(VALUE self, VALUE audio_path, VALUE params) if (args.result == 0) { return self; } else { - rb_exc_raise(rb_funcall(eError, id_new, 1, args.result)); + rb_exc_raise(rb_funcall(eError, id_new, 1, INT2NUM(args.result))); } } From b3b9af63b2fa56575e34fc55b9298421927faec2 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 20 May 2026 16:47:21 +0900 Subject: [PATCH 036/118] Implement hooks for Parakeet --- .../ruby/ext/ruby_whisper_parakeet_params.c | 158 +++++++++++++++++- bindings/ruby/ext/ruby_whisper_segment.c | 4 +- 2 files changed, 157 insertions(+), 5 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c index 740df507e8c..0063889b50f 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_params.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -47,7 +47,7 @@ extern void ruby_whisper_callback_container_mark(ruby_whisper_callback_container extern ruby_whisper_callback_container* ruby_whisper_callback_container_allocate(void); extern bool ruby_whisper_callback_container_is_present(const ruby_whisper_callback_container *container); extern VALUE ruby_whisper_parakeet_segment_init(VALUE context, int index); -extern VALUE ruby_whisper_parakeet_token_s_from_token_data(struct parakeet_context *context, parakeet_token_data *token_data); +extern VALUE ruby_whisper_parakeet_token_s_from_token_data(struct parakeet_context *context, const parakeet_token_data *token_data); static ID param_names[RUBY_WHISPER_PARAKEET_NUM_PARAMS]; typedef VALUE (*param_writer_t)(VALUE, VALUE); @@ -157,15 +157,146 @@ ruby_whisper_parakeet_new_token_callback(struct parakeet_context *context, struc rb_thread_call_with_gvl(call_parakeet_new_token_callbacks, (void *)&args); } +typedef struct { + const ruby_whisper_callback_container *container; + struct parakeet_state *state; + int progress; +} call_parakeet_progress_callbacks_args; + +static void* +call_parakeet_progress_callback(void *v_args) +{ + call_parakeet_progress_callbacks_args *args = (call_parakeet_progress_callbacks_args *)v_args; + const ruby_whisper_callback_container *container = args->container; + + if (!NIL_P(container->callback)) { + rb_funcall(container->callback, id_call, 4, *container->context, Qnil, INT2NUM(args->progress), container->user_data); + } + if (NIL_P(container->callbacks)) { + return NULL; + } + const long n_callbacks = RARRAY_LEN(container->callbacks); + if (n_callbacks == 0) { + return NULL; + } + for (long i = 0; i < n_callbacks; i++) { + VALUE cb = rb_ary_entry(container->callbacks, i); + rb_funcall(cb, id_call, 1, INT2NUM(args->progress)); + } + + return NULL; +} + static void ruby_whisper_parakeet_progress_callback(struct parakeet_context *context, struct parakeet_state *state, int progress, void *user_data) { + const ruby_whisper_callback_container *container = (ruby_whisper_callback_container *)user_data; + if (!ruby_whisper_callback_container_is_present(container)) { + return; + } + + call_parakeet_progress_callbacks_args args = { + container, + state, + progress, + }; + rb_thread_call_with_gvl(call_parakeet_progress_callback, (void *)&args); +} + +typedef struct { + const ruby_whisper_callback_container *container; + struct parakeet_state *state; + bool is_continued; +} call_parakeet_encoder_begin_callbacks_args; + +static void* +call_parakeet_encoder_begin_callbacks(void *v_args) +{ + call_parakeet_encoder_begin_callbacks_args *args = (call_parakeet_encoder_begin_callbacks_args *)v_args; + const ruby_whisper_callback_container *container = args->container; + VALUE result = Qnil; + + if (!NIL_P(container->callback)) { + result = rb_funcall(container->callback, id_call, 3, *container->context, Qnil, container->user_data); + if (result == Qfalse) { + args->is_continued = false; + return NULL; + } + } + if (NIL_P(container->callbacks)) { + return NULL; + } + const long n_callbacks = RARRAY_LEN(container->callbacks); + if (n_callbacks == 0) { + return NULL; + } + for (long i = 0; i < n_callbacks; i++) { + VALUE cb = rb_ary_entry(container->callbacks, i); + result = rb_funcall(cb, id_call, 0); + if (result == Qfalse) { + args->is_continued = false; + return NULL; + } + } + + return NULL; } static bool ruby_whisper_parakeet_encoder_begin_callback(struct parakeet_context *context, struct parakeet_state *state, void *user_data) { - return true; + const ruby_whisper_callback_container *container = (ruby_whisper_callback_container *)user_data; + if (!ruby_whisper_callback_container_is_present(container)) { + return true; + } + + call_parakeet_encoder_begin_callbacks_args args = { + container, + state, + true, + }; + rb_thread_call_with_gvl(call_parakeet_encoder_begin_callbacks, (void *)&args); + + return args.is_continued; +} + +typedef struct { + const ruby_whisper_callback_container *container; + bool is_interrupted; +} call_parakeet_abort_callbacks_args; + +static void* +call_parakeet_abort_callbacks(void *v_args) +{ + call_parakeet_abort_callbacks_args *args = (call_parakeet_abort_callbacks_args *)v_args; + const ruby_whisper_callback_container *container = args->container; + VALUE result = Qnil; + + if (!NIL_P(container->callback)) { + result = rb_funcall(container->callback, id_call, 1, container->user_data); + if (RTEST(result)) { + args->is_interrupted = true; + return NULL; + } + } + if (NIL_P(container->callbacks)) { + return NULL; + } + const long n_callbacks = RARRAY_LEN(container->callbacks); + if (n_callbacks == 0) { + return NULL; + } + VALUE cb; + for (long i = 0; i < n_callbacks; i++) { + cb = rb_ary_entry(container->callbacks, i); + result = rb_funcall(cb, id_call, 0); + if (RTEST(result)) { + args->is_interrupted = true; + return NULL; + } + } + + return NULL; } static bool @@ -174,8 +305,21 @@ ruby_whisper_parakeet_abort_callback(void *user_data) ruby_whisper_parakeet_abort_callback_user_data *data = (ruby_whisper_parakeet_abort_callback_user_data *)user_data; int is_interrupted = RUBY_ATOMIC_LOAD(data->is_interrupted); + if (is_interrupted) { + return true; + } + + if (!(data->callback_container) || !ruby_whisper_callback_container_is_present(data->callback_container)) { + return false; + } - return is_interrupted == 1; + call_parakeet_abort_callbacks_args args = { + data->callback_container, + false, + }; + rb_thread_call_with_gvl(call_parakeet_abort_callbacks, (void *)&args); + + return args.is_interrupted; } #define CALLBACK_CONTAINER_NAME(name) name ## _container @@ -317,6 +461,14 @@ ITERATE_NORMAL_CALLBACK_NAMES(DEF_HOOK, _) static VALUE ruby_whisper_parakeet_params_abort_on(VALUE self) { + ruby_whisper_parakeet_params *rwpp; + GetParakeetParams(self, rwpp); + const VALUE blk = rb_block_proc(); + if (NIL_P(rwpp->abort_callback_container->callbacks)) { + rwpp->abort_callback_container->callbacks = rb_ary_new(); + } + rb_ary_push(rwpp->abort_callback_container->callbacks, blk); + return Qnil; } diff --git a/bindings/ruby/ext/ruby_whisper_segment.c b/bindings/ruby/ext/ruby_whisper_segment.c index 50974a6f3d8..cf0372797d3 100644 --- a/bindings/ruby/ext/ruby_whisper_segment.c +++ b/bindings/ruby/ext/ruby_whisper_segment.c @@ -15,7 +15,7 @@ extern const rb_data_type_t ruby_whisper_type; extern VALUE cSegment; -extern VALUE ruby_whisper_token_s_from_index(struct whisper_context *context, int i_segment, int index); +extern VALUE ruby_whisper_token_s_init(struct whisper_context *context, int i_segment, int index); static void rb_whisper_segment_mark(void *p) @@ -190,7 +190,7 @@ ruby_whisper_segment_each_token(VALUE self) const int n_tokens = whisper_full_n_tokens(rw->context, rws->index); for (int i = 0; i < n_tokens; ++i) { - rb_yield(ruby_whisper_token_s_from_index(rw->context, rws->index, i)); + rb_yield(ruby_whisper_token_s_init(rw->context, rws->index, i)); } return self; From fd550f77cee958ec74b71c6bf846aa1675155d0b Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 21 May 2026 00:46:35 +0900 Subject: [PATCH 037/118] Implement Parakeet::Context#full --- bindings/ruby/ext/ruby_whisper_context.c | 2 +- .../ruby/ext/ruby_whisper_parakeet_context.c | 98 +++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/bindings/ruby/ext/ruby_whisper_context.c b/bindings/ruby/ext/ruby_whisper_context.c index 26058fc07e6..0f2246dd9b7 100644 --- a/bindings/ruby/ext/ruby_whisper_context.c +++ b/bindings/ruby/ext/ruby_whisper_context.c @@ -379,7 +379,7 @@ fill_samples(VALUE rb_args) return Qnil; } -struct parsed_samples_t +parsed_samples_t parse_samples(VALUE *samples, VALUE *n_samples) { bool memview_available = rb_memory_view_available_p(*samples); diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_context.c b/bindings/ruby/ext/ruby_whisper_parakeet_context.c index 88c39aa8df0..74693599d1f 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_context.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_context.c @@ -3,12 +3,18 @@ extern ID id_to_s; extern ID id___method__; extern ID id_to_enum; +extern ID id_new; extern VALUE cParakeetContext; +extern VALUE eError; extern VALUE ruby_whisper_normalize_model_path(VALUE model_path); extern VALUE ruby_whisper_parakeet_transcribe(VALUE self, VALUE audio_path, VALUE params); extern VALUE ruby_whisper_parakeet_segment_init(VALUE context, int index); +extern parsed_samples_t parse_samples(VALUE *samples, VALUE *n_samples); +extern VALUE release_samples(VALUE rb_parsed_args); +extern void ruby_whisper_parakeet_prepare_transcription(ruby_whisper_parakeet_params *rwpp, VALUE *context, ruby_whisper_parakeet_abort_callback_user_data *abort_callback_user_data); +extern rb_data_type_t ruby_whisper_parakeet_params_type; static void ruby_whisper_parakeet_context_free(void *p) @@ -106,6 +112,97 @@ ruby_whisper_parakeet_context_each_segment(VALUE self) return self; } +typedef struct { + struct parakeet_context *context; + struct parakeet_full_params *params; + float *samples; + int n_samples; + int result; +} parakeet_full_without_gvl_args; + +static void* +parakeet_full_without_gvl(void *rb_args) +{ + parakeet_full_without_gvl_args *args = (parakeet_full_without_gvl_args *)rb_args; + args->result = parakeet_full(args->context, *args->params, args->samples, args->n_samples); + + return NULL; +} + +typedef struct { + ruby_whisper_parakeet_abort_callback_user_data *abort_callback_user_data; +} parakeet_full_ubf_args; + +static void +parakeet_full_ubf(void *rb_args) +{ + parakeet_full_ubf_args *args = (parakeet_full_ubf_args *)rb_args; + + RUBY_ATOMIC_SET(args->abort_callback_user_data->is_interrupted, 1); +} + +typedef struct { + VALUE *context; + VALUE *params; + float *samples; + int n_samples; +} parakeet_full_args; + +static VALUE +parakeet_full_body(VALUE rb_args) +{ + parakeet_full_args *args = (parakeet_full_args *)rb_args; + ruby_whisper_parakeet_context *rwpc; + GetParakeetContext(*args->context, rwpc); + ruby_whisper_parakeet_params *rwpp; + GetParakeetParams(*args->params, rwpp); + + ruby_whisper_parakeet_abort_callback_user_data abort_callback_user_data = { + 0, + NULL, + }; + ruby_whisper_parakeet_prepare_transcription(rwpp, args->context, &abort_callback_user_data); + + parakeet_full_without_gvl_args full_without_gvl_args = { + rwpc->context, + &rwpp->params, + args->samples, + args->n_samples, + 0 + }; + parakeet_full_ubf_args full_ubf_args = { + &abort_callback_user_data, + }; + rb_thread_call_without_gvl(parakeet_full_without_gvl, (void *)&full_without_gvl_args, parakeet_full_ubf, (void *)&full_ubf_args); + + return INT2NUM(full_without_gvl_args.result); +} + +static VALUE +ruby_whisper_parakeet_context_full(int argc, VALUE *argv, VALUE self) +{ + if (argc < 2 || argc > 3) { + rb_raise(rb_eArgError, "wrong number of arguments (given %d, expected 2..3)", argc); + } + + VALUE n_samples = argc == 2 ? Qnil : argv[2]; + + struct parsed_samples_t parsed = parse_samples(&argv[1], &n_samples); + parakeet_full_args args = { + &self, + &argv[0], + parsed.samples, + parsed.n_samples, + }; + VALUE rb_result = rb_ensure(parakeet_full_body, (VALUE)&args, release_samples, (VALUE)&parsed); + const int result = NUM2INT(rb_result); + if (result == 0) { + return self; + } else { + rb_exc_raise(rb_funcall(eError, id_new, 1, result)); + } +} + void init_ruby_whisper_parakeet_context(VALUE *mParakeet) { @@ -116,4 +213,5 @@ init_ruby_whisper_parakeet_context(VALUE *mParakeet) rb_define_method(cParakeetContext, "initialize", ruby_whisper_parakeet_context_initialize, -1); rb_define_method(cParakeetContext, "transcribe", ruby_whisper_parakeet_transcribe, 2); rb_define_method(cParakeetContext, "each_segment", ruby_whisper_parakeet_context_each_segment, 0); + rb_define_method(cParakeetContext, "full", ruby_whisper_parakeet_context_full, -1); } From a5a6884d6507e03541cc00ef1ed7c7caccbd627d Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 21 May 2026 00:46:47 +0900 Subject: [PATCH 038/118] Add tests for Parakeet::Context#full --- bindings/ruby/test/test_parakeet_context.rb | 65 +++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/bindings/ruby/test/test_parakeet_context.rb b/bindings/ruby/test/test_parakeet_context.rb index 2b50b0e44de..379ee0aa1ba 100644 --- a/bindings/ruby/test/test_parakeet_context.rb +++ b/bindings/ruby/test/test_parakeet_context.rb @@ -14,6 +14,71 @@ def test_new assert_instance_of Parakeet::Context, @parakeet end + sub_test_case "full" do + def setup + super + @samples = File.read(AUDIO, nil, 78).unpack("s<*").collect {|i| i.to_f / 2**15} + end + + def test_full + @parakeet.full @params, @samples, @samples.length + + segments = @parakeet.each_segment.to_a + assert_equal 2, segments.length + assert_match /ask not what your country can do for you, ask what you can do for your/, segments.first.text + end + + def test_full_without_length + @parakeet.full(@params, @samples) + + segments = @parakeet.each_segment.to_a + assert_equal 2, segments.length + assert_match /ask not what your country can do for you, ask what you can do for your/, @parakeet.each_segment.first.text + end + + def test_full_enumerator + samples = @samples.each + @parakeet.full @params, samples, @samples.length + + segments = @parakeet.each_segment.to_a + assert_equal 2, segments.length + assert_match /ask not what your country can do for you, ask what you can do for your/, @parakeet.each_segment.first.text + end + + def test_full_enumerator_without_length + samples = @samples.each + assert_raise ArgumentError do + @parakeet.full @params, samples + end + end + + def test_full_enumerator_with_too_large_length + samples = @samples.each.take(10).to_enum + assert_raise StopIteration do + @parakeet.full @params, samples, 11 + end + end + + def test_full_with_memory_view + samples = JFKReader.new(AUDIO) + @parakeet.full @params, samples + + segments = @parakeet.each_segment.to_a + assert_equal 2, segments.length + assert_match /ask not what your country can do for you, ask what you can do for your/, @parakeet.each_segment.first.text + end + + def test_full_with_memroy_view_gc + samples = JFKReader.new(AUDIO) + @parakeet.full(@params, samples) + GC.start + require "fiddle" + Fiddle::MemoryView.export samples do |view| + assert_equal 176000, view.to_s.unpack("#{view.format}*").length + end + end + end + def test_transcribe assert_nothing_raised do @parakeet.transcribe AUDIO, @params From 13bee24238fe5f2393d7ec080dd90b63a0978024 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 21 May 2026 01:03:40 +0900 Subject: [PATCH 039/118] Add Parakeet to RBS --- bindings/ruby/sig/whisper.rbs | 277 ++++++++++++++++++++++++++++++++++ 1 file changed, 277 insertions(+) diff --git a/bindings/ruby/sig/whisper.rbs b/bindings/ruby/sig/whisper.rbs index cbec4803820..ac474e32329 100644 --- a/bindings/ruby/sig/whisper.rbs +++ b/bindings/ruby/sig/whisper.rbs @@ -444,6 +444,283 @@ module Whisper def abort_on: { (Object user_data) -> boolish } -> void end + module Parakeet + # Control logging output. The default behavior is to print to stderr. + # + def self.log_set: (nil, Object? user_data) -> nil + | (^(Integer level, String message, Object user_data) -> void, Object? user_data) -> nil + + class Context + # Load a Parakeet model from the given file path. + # + def self.new: (String | path | ::URI::HTTP) -> instance + + # Transcribe a single audio file. + # + def transcribe: (path audio_file_path, Whisper::Parakeet::Params) -> self + + # Run the entire model: PCM -> log mel spectrogram -> encoder -> decoder -> text. + # Not thread safe for the same context. + # + # The second argument `samples` must be an array of samples, respond to `:length`, + # or be a MemoryView of an array of float. It must be 32 bit float PCM audio data. + # + def full: (Whisper::Parakeet::Params, Array[Float] samples, ?Integer n_samples) -> self + | (Whisper::Parakeet::Params, _Samples, ?Integer n_samples) -> self + + # Yields each Whisper::Parakeet::Segment: + # + # parakeet.transcribe("path/to/audio.wav", params) + # parakeet.each_segment do |segment| + # puts segment.text + # end + # + # Returns an `Enumerator` if no block given: + # + # parakeet.transcribe("path/to/audio.wav", params) + # enum = parakeet.each_segment + # enum.to_a # => [#, ...] + # + def each_segment: { (Segment) -> void } -> void + | () -> Enumerator[Segment] + end + + class Params + def self.new: ( + ?n_threads: Integer, + ?offset_ms: Integer, + ?duration_ms: Integer, + ?no_context: boolish, + ?audio_ctx: Integer, + ?chunk_length_ms: Integer, + ?left_context_ms: Integer, + ?right_context_ms: Integer, + ?new_segment_callback: ^(Whisper::Parakeet::Context, untyped, Integer n_new, Object user_data) -> void, + ?new_segment_callback_user_data: Object, + ?new_token_callback: ^(Whisper::Parakeet::Context, untyped, Whisper::Parakeet::Token, Object user_data) -> void, + ?new_token_callback_user_data: Object, + ?progress_callback: ^(Whisper::Parakeet::Context, untyped, Integer progress, Object user_data) -> void, + ?progress_callback_user_data: Object, + ?encoder_begin_callback: ^(Whisper::Parakeet::Context, untyped, Object user_data) -> boolish, + ?encoder_begin_callback_user_data: Object, + ?abort_callback: ^(Object user_data) -> boolish, + ?abort_callback_user_data: Object + ) -> instance + + # Number of threads to use. + # + def n_threads=: (Integer) -> Integer + def n_threads: () -> Integer + + # Start offset in ms. + # + def offset_ms=: (Integer) -> Integer + def offset_ms: () -> Integer + + # Audio duration to process in ms. + # + def duration_ms=: (Integer) -> Integer + def duration_ms: () -> Integer + + # If `true`, does not use past transcription (if any) as context. + # + def no_context=: (boolish) -> boolish + def no_context: () -> (true | false) + + # Overwrite the audio context size. `0` uses the default value. + # + def audio_ctx=: (Integer) -> Integer + def audio_ctx: () -> Integer + + # Length of each chunk in ms. + # + def chunk_length_ms=: (Integer) -> Integer + def chunk_length_ms: () -> Integer + + # Left context in ms. + # + def left_context_ms=: (Integer) -> Integer + def left_context_ms: () -> Integer + + # Right context in ms. + # + def right_context_ms=: (Integer) -> Integer + def right_context_ms: () -> Integer + + # Sets new segment callback, called for every newly generated text segment. + # + # params.new_segment_callback = ->(context, _, n_new, user_data) { + # # ... + # } + # + def new_segment_callback=: (^(Whisper::Parakeet::Context, untyped, Integer n_new, Object user_data) -> void) -> (^(Whisper::Parakeet::Context, untyped, Integer n_new, Object user_data) -> void) + def new_segment_callback: () -> ((^(Whisper::Parakeet::Context, untyped, Integer n_new, Object user_data) -> void) | nil) + + # Sets user data passed to the last argument of new segment callback. + # + def new_segment_callback_user_data=: (Object?) -> Object? + def new_segment_callback_user_data: () -> Object? + + # Sets token callback, called for every newly predicted token. + # + def new_token_callback=: (^(Whisper::Parakeet::Context, untyped, Whisper::Parakeet::Token, Object user_data) -> void) -> (^(Whisper::Parakeet::Context, untyped, Whisper::Parakeet::Token, Object user_data) -> void) + def new_token_callback: () -> ((^(Whisper::Parakeet::Context, untyped, Whisper::Parakeet::Token, Object user_data) -> void) | nil) + + # Sets user data passed to the last argument of token callback. + # + def new_token_callback_user_data=: (Object?) -> Object? + def new_token_callback_user_data: () -> Object? + + # Sets progress callback, called on each progress update. + # + # +progress+ is an Integer between 0 and 100. + # + def progress_callback=: (^(Whisper::Parakeet::Context, untyped, Integer progress, Object user_data) -> void) -> (^(Whisper::Parakeet::Context, untyped, Integer progress, Object user_data) -> void) + def progress_callback: () -> ((^(Whisper::Parakeet::Context, untyped, Integer progress, Object user_data) -> void) | nil) + + # Sets user data passed to the last argument of progress callback. + # + def progress_callback_user_data=: (Object?) -> Object? + def progress_callback_user_data: () -> Object? + + # Sets encoder begin callback, called each time before the encoder starts. + # + # If it returns `false`, the computation is aborted. + # + def encoder_begin_callback=: (^(Whisper::Parakeet::Context, untyped, Object user_data) -> boolish) -> (^(Whisper::Parakeet::Context, untyped, Object user_data) -> boolish) + def encoder_begin_callback: () -> ((^(Whisper::Parakeet::Context, untyped, Object user_data) -> boolish) | nil) + + # Sets user data passed to the last argument of encoder begin callback. + # + def encoder_begin_callback_user_data=: (Object?) -> Object? + def encoder_begin_callback_user_data: () -> Object? + + # Sets abort callback, called each time before ggml computation starts. + # + def abort_callback=: (^(Object user_data) -> boolish) -> (^(Object user_data) -> boolish) + def abort_callback: () -> ((^(Object user_data) -> boolish) | nil) + + # Sets user data passed to the last argument of abort callback. + # + def abort_callback_user_data=: (Object?) -> Object? + def abort_callback_user_data: () -> Object? + + # Hook called on new segment. Yields each Whisper::Parakeet::Segment. + # + def on_new_segment: { (Segment) -> void } -> void + + # Hook called on new token. Yields each Whisper::Parakeet::Token. + # + def on_new_token: { (Token) -> void } -> void + + # Hook called on progress update. Yields each progress `Integer` between 0 and 100. + # + def on_progress: { (Integer progress) -> void } -> void + + # Hook called each time before the encoder starts. + # + def on_encoder_begin: { () -> boolish } -> void + + # Call block to determine whether abort or not. Return `true` when you want to abort. + # + def abort_on: { () -> boolish } -> void + end + + class Segment + type deconstructed_keys = { + start_time: (Integer | nil), + end_time: (Integer | nil), + text: (String | nil) + } + + # Start time in milliseconds. + # + def start_time: () -> Integer + + # End time in milliseconds. + # + def end_time: () -> Integer + + # Text of the segment. + # + def text: () -> String + + # Yields each Whisper::Parakeet::Token: + # + # parakeet.each_segment.first.each_token do |token| + # p token + # end + # + # Returns an `Enumerator` if no block is given: + # + # parakeet.each_segment.first.each_token.to_a # => [#, ...] + # + def each_token: { (Token) -> void } -> void + | () -> Enumerator[Token] + + # Possible keys: `:start_time`, `:end_time`, `:text` + # + def deconstruct_keys: (Array[:start_time | :end_time | :text] | nil) -> deconstructed_keys + end + + class Token + type deconstructed_keys = { + id: (Integer | nil), + duration_idx: (Integer | nil), + duration_value: (Integer | nil), + frame_index: (Integer | nil), + probability: (Float | nil), + log_probability: (Float | nil), + start_time: (Integer | nil), + end_time: (Integer | nil), + word_start: ((true | false) | nil), + text: (String | nil), + } + + # Token ID. + # + def id: () -> Integer + + # Index into the model's durations array. + # + def duration_idx: () -> Integer + + # Actual duration value. + # + def duration_value: () -> Integer + + # Frame index of the token. + # + def frame_index: () -> Integer + + # Probability of the token. + # + def probability: () -> Float + + # Log probability of the token. + # + def log_probability: () -> Float + + # Start time of the token in milliseconds. + # + def start_time: () -> Integer + + # End time of the token in milliseconds. + # + def end_time: () -> Integer + + # Whether this token is the start of a word. + # + def word_start?: () -> (true | false) + + # Get the token text of the token. + # + def text: () -> String + + def deconstruct_keys: (Array[:id | :duration_idx | :duration_value | :frame_index | :probability | :log_probability | :start_time | :end_time | :word_start | :text] | nil) -> deconstructed_keys + end + end + class Model def self.pre_converted_models: () -> Hash[String, Model::URI] def self.coreml_compiled_models: () -> Hash[Model::URI, Model::ZipURI] From cc143b6e10badad041e2c090ba7234e8c4c04fdb Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 21 May 2026 01:09:17 +0900 Subject: [PATCH 040/118] Fix ruby_whisper_parakeet_params_free --- bindings/ruby/ext/ruby_whisper_parakeet_params.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c index 0063889b50f..02d10aabd3f 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_params.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -359,7 +359,6 @@ static void ruby_whisper_parakeet_params_free(void *p) { ruby_whisper_parakeet_params *rwpp = (ruby_whisper_parakeet_params *)p; - parakeet_free_params(&rwpp->params); #define FREE_CONTAINER(name) \ if (rwpp->name##_container) { \ @@ -367,6 +366,8 @@ ruby_whisper_parakeet_params_free(void *p) } ITERATE_CALLBACK_PARAMS(FREE_CONTAINER) + + xfree(rwpp); } static size_t From 49fb119116cf7482bbb06995d43568f4604fc9de Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 21 May 2026 01:10:19 +0900 Subject: [PATCH 041/118] Free ruby_whisper_parakeet_context --- bindings/ruby/ext/ruby_whisper_parakeet_context.c | 1 + 1 file changed, 1 insertion(+) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_context.c b/bindings/ruby/ext/ruby_whisper_parakeet_context.c index 74693599d1f..dec13dc7c5c 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_context.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_context.c @@ -24,6 +24,7 @@ ruby_whisper_parakeet_context_free(void *p) parakeet_free(rwpc->context); rwpc->context = NULL; } + xfree(rwpc); } static size_t From 451fe6be4d862a5174bdca73d0fd2434ee4b801e Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 21 May 2026 01:21:14 +0900 Subject: [PATCH 042/118] Add tests for hooks --- bindings/ruby/test/test_parakeet_callback.rb | 108 +++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 bindings/ruby/test/test_parakeet_callback.rb diff --git a/bindings/ruby/test/test_parakeet_callback.rb b/bindings/ruby/test/test_parakeet_callback.rb new file mode 100644 index 00000000000..5e23e9319d9 --- /dev/null +++ b/bindings/ruby/test/test_parakeet_callback.rb @@ -0,0 +1,108 @@ +require_relative "helper" + +class TestParakeetCallback < TestBase + def setup + Whisper.instance_variable_set "@whisper", nil + GC.start + @params = Parakeet::Params.new + @parakeet = Parakeet::Context.new(File.join(__dir__, "../../../models/parakeet-tdt-0.6b-v3.bin")) + end + + def test_new_segment_callback + @params.new_segment_callback = ->(context, state, n_new, user_data) { + assert_kind_of Integer, n_new + assert n_new > 0 + assert_same @parakeet, context + + # n_segments = context.full_n_segments + # n_new.times do |i| + # i_segment = n_segments - 1 + i + # start_time = context.full_get_segment_t0(i_segment) * 10 + # end_time = context.full_get_segment_t1(i_segment) * 10 + # text = context.full_get_segment_text(i_segment) + + # assert_kind_of Integer, start_time + # assert start_time >= 0 + # assert_kind_of Integer, end_time + # assert end_time > 0 + # assert_match(/ask not what your country can do for you, ask what you can do for your country/, text) if i_segment == 0 + # end + } + + @parakeet.transcribe AUDIO, @params + end + + def test_on_new_segment + seg = nil + index = 0 + @params.on_new_segment do |segment| + assert_instance_of Parakeet::Segment, segment + if index == 0 + seg = segment + assert_equal 0, segment.start_time + assert_match(/ask not what your country can do for you, ask what you can do for your/, segment.text) + end + index += 1 + end + @parakeet.transcribe AUDIO, @params + assert_equal 0, seg.start_time + assert_match /ask not what your country can do for you, ask what you can do for your/, seg.text + end + + def test_on_new_token + index = 0 + @params.on_new_token do |token| + assert_instance_of Parakeet::Token, token + if index == 0 + assert_instance_of Integer, token.start_time + assert_match "▁And", token.text + end + index += 1 + end + + @parakeet.transcribe AUDIO, @params + end + + def test_on_progress + first = nil + last = nil + @params.on_progress do |progress| + assert_kind_of Integer, progress + assert 0 <= progress && progress <= 100 + first = progress if first.nil? + last = progress + end + + @parakeet.transcribe AUDIO, @params + + assert_equal 0, first + assert_equal 90, last + end + + def test_on_encoder_begin + i = 0 + @params.on_encoder_begin do + i += 1 + end + + @parakeet.transcribe AUDIO, @params + + assert i > 0 + end + + def test_abort_on + do_abort = false + @params.on_new_segment do |segment| + do_abort = true if segment.text.match?(/ask/) + end + i = 0 + @params.abort_on do + i += 1 + do_abort + end + + @parakeet.transcribe(AUDIO, @params) rescue nil + + assert i > 0 + end +end From 1d5e7e24332ae4467013981ff4ebf0dc7904cef5 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 21 May 2026 01:30:16 +0900 Subject: [PATCH 043/118] Add Parakeet section to README --- bindings/ruby/README.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/bindings/ruby/README.md b/bindings/ruby/README.md index 07b81830c58..116ef82c35d 100644 --- a/bindings/ruby/README.md +++ b/bindings/ruby/README.md @@ -396,6 +396,39 @@ whisper .full(Whisper::Params.new, samples) ``` +### Parakeet ### + +Now whispercpp supports NVIDIA's ASR model Parakeet. + +If you want to use Parakeet instead of Whisper, the API should feel familiar. +In most cases, replace `Whisper::Context` and `Whisper::Params` with `Whisper::Parakeet::Context` and `Whisper::Parakeet::Params`, then use `#transcribe`, `#full`, `#each_segment`, and `#each_token` in the same way. + +```ruby +require "whisper" + +# It's useful to assign Whisper::Parakeet to top-level Parakeet constant unless you use Parakeet gem. +Parakeet = Whisper::Parakeet + +parakeet = Parakeet::Context.new("path/to/model") + +params = Parakeet::Params.new( + no_context: true, + chunk_length_ms: 10_000 +) + +parakeet + .transcribe("path/to/audio.wav", params) + .each_segment do |segment| + puts "[#{segment.start_time} --> #{segment.end_time}] #{segment.text}" + end +``` + +The main differences are: + +* Namespace is `Whisper::Parakeet`. +* `Whisper::Parakeet::Params` has Parakeet-specific chunk and context settings such as `chunk_length_ms`, `left_context_ms`, and `right_context_ms`. +* Parakeet also supports `on_new_token` / `new_token_callback` in addition to segment and progress callbacks. + Custom context params --------------------- From ed5887818cec900363e305a84bc16e1b07923ad5 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 21 May 2026 02:51:39 +0900 Subject: [PATCH 044/118] Add more attributes of Parakeet::Context --- .../ruby/ext/ruby_whisper_parakeet_context.c | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_context.c b/bindings/ruby/ext/ruby_whisper_parakeet_context.c index dec13dc7c5c..8ad4b8f3a77 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_context.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_context.c @@ -1,5 +1,22 @@ #include "ruby_whisper.h" +#define ITERATE_SEGMENT_ATTRS(ITERATOR) \ + ITERATOR(get_segment_t0, LONG) \ + ITERATOR(get_segment_t1, LONG) \ + ITERATOR(get_segment_text, STRING) \ + ITERATOR(n_tokens, INT) + +#define ITERATE_TOKEN_ATTRS(ITERATOR) \ + ITERATOR(get_token_text, STRING) \ + ITERATOR(get_token_id, INT) \ + ITERATOR(get_token_p, FLOAT) + +#define VAL_FROM_LONG(v) LONG2NUM(v) +#define VAL_FROM_STRING(v) rb_utf8_str_new_cstr(v) +#define VAL_FROM_INT(v) INT2NUM(v) +#define VAL_FROM_FLOAT(v) DBL2NUM(v) +#define READER(type) VAL_FROM_##type + extern ID id_to_s; extern ID id___method__; extern ID id_to_enum; @@ -15,6 +32,7 @@ extern parsed_samples_t parse_samples(VALUE *samples, VALUE *n_samples); extern VALUE release_samples(VALUE rb_parsed_args); extern void ruby_whisper_parakeet_prepare_transcription(ruby_whisper_parakeet_params *rwpp, VALUE *context, ruby_whisper_parakeet_abort_callback_user_data *abort_callback_user_data); extern rb_data_type_t ruby_whisper_parakeet_params_type; +extern VALUE ruby_whisper_parakeet_token_s_from_token_data(struct parakeet_context *context, const parakeet_token_data *token_data); static void ruby_whisper_parakeet_context_free(void *p) @@ -94,6 +112,47 @@ ruby_whisper_parakeet_context_initialize(int argc, VALUE *argv, VALUE self) return Qnil; } +static VALUE +ruby_whisper_parakeet_context_full_n_segments(VALUE self) +{ + ruby_whisper_parakeet_context *rwpc; + GetParakeetContext(self, rwpc); + + return INT2NUM(parakeet_full_n_segments(rwpc->context)); +} + +#define DEF_SEGMENT_ATTR(name, type) \ + static VALUE \ + ruby_whisper_parakeet_context_full_##name(VALUE self, VALUE i_segment) \ + { \ + ruby_whisper_parakeet_context *rwpc; \ + GetParakeetContext(self, rwpc); \ + return READER(type)(parakeet_full_##name(rwpc->context, NUM2INT(i_segment))); \ + } + +ITERATE_SEGMENT_ATTRS(DEF_SEGMENT_ATTR) + +#define DEF_TOKEN_ATTR(name, type) \ + static VALUE \ + ruby_whisper_parakeet_context_full_##name(VALUE self, VALUE i_segment, VALUE i_token) \ + { \ + ruby_whisper_parakeet_context *rwpc; \ + GetParakeetContext(self, rwpc); \ + return READER(type)(parakeet_full_##name(rwpc->context, NUM2INT(i_segment), NUM2INT(i_token))); \ + } + +ITERATE_TOKEN_ATTRS(DEF_TOKEN_ATTR) + +static VALUE +ruby_whisper_parakeet_context_full_get_token_data(VALUE self, VALUE i_segment, VALUE i_token) +{ + ruby_whisper_parakeet_context *rwpc; + GetParakeetContext(self, rwpc); + parakeet_token_data token_data = parakeet_full_get_token_data(rwpc->context, NUM2INT(i_segment), NUM2INT(i_token)); + + return ruby_whisper_parakeet_token_s_from_token_data(rwpc->context, &token_data); +} + static VALUE ruby_whisper_parakeet_context_each_segment(VALUE self) { @@ -213,6 +272,19 @@ init_ruby_whisper_parakeet_context(VALUE *mParakeet) rb_define_method(cParakeetContext, "initialize", ruby_whisper_parakeet_context_initialize, -1); rb_define_method(cParakeetContext, "transcribe", ruby_whisper_parakeet_transcribe, 2); + rb_define_method(cParakeetContext, "full_n_segments", ruby_whisper_parakeet_context_full_n_segments, 0); + rb_define_method(cParakeetContext, "full_get_token_data", ruby_whisper_parakeet_context_full_get_token_data, 2); rb_define_method(cParakeetContext, "each_segment", ruby_whisper_parakeet_context_each_segment, 0); rb_define_method(cParakeetContext, "full", ruby_whisper_parakeet_context_full, -1); + +#define REGISTER_SEGMENT_ATTR(name, type) \ + rb_define_method(cParakeetContext, "full_" #name, ruby_whisper_parakeet_context_full_##name, 1); + + ITERATE_SEGMENT_ATTRS(REGISTER_SEGMENT_ATTR) + +#define REGISTER_TOKEN_ATTR(name, type) \ + rb_define_method(cParakeetContext, "full_" #name, ruby_whisper_parakeet_context_full_##name, 2); + + ITERATE_TOKEN_ATTRS(REGISTER_TOKEN_ATTR) } + From fdd99ce57ee58f029000d4c5e6d2aea896d4e3b9 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 21 May 2026 02:51:50 +0900 Subject: [PATCH 045/118] Add tests for Parakeet::Context's attributes --- bindings/ruby/test/test_parakeet_callback.rb | 26 ++++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/bindings/ruby/test/test_parakeet_callback.rb b/bindings/ruby/test/test_parakeet_callback.rb index 5e23e9319d9..ee4aa733da7 100644 --- a/bindings/ruby/test/test_parakeet_callback.rb +++ b/bindings/ruby/test/test_parakeet_callback.rb @@ -14,19 +14,19 @@ def test_new_segment_callback assert n_new > 0 assert_same @parakeet, context - # n_segments = context.full_n_segments - # n_new.times do |i| - # i_segment = n_segments - 1 + i - # start_time = context.full_get_segment_t0(i_segment) * 10 - # end_time = context.full_get_segment_t1(i_segment) * 10 - # text = context.full_get_segment_text(i_segment) - - # assert_kind_of Integer, start_time - # assert start_time >= 0 - # assert_kind_of Integer, end_time - # assert end_time > 0 - # assert_match(/ask not what your country can do for you, ask what you can do for your country/, text) if i_segment == 0 - # end + n_segments = context.full_n_segments + n_new.times do |i| + i_segment = n_segments - 1 + i + start_time = context.full_get_segment_t0(i_segment) * 10 + end_time = context.full_get_segment_t1(i_segment) * 10 + text = context.full_get_segment_text(i_segment) + + assert_kind_of Integer, start_time + assert start_time >= 0 + assert_kind_of Integer, end_time + assert end_time > 0 + assert_match(/ask not what your country can do for you, ask what you can do for your/, text) if i_segment == 0 + end } @parakeet.transcribe AUDIO, @params From 9ed7a04394c2623a707cfa98e6541df68157aa87 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 21 May 2026 03:15:31 +0900 Subject: [PATCH 046/118] Update RBS --- bindings/ruby/sig/whisper.rbs | 42 +++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/bindings/ruby/sig/whisper.rbs b/bindings/ruby/sig/whisper.rbs index ac474e32329..91c92c84b96 100644 --- a/bindings/ruby/sig/whisper.rbs +++ b/bindings/ruby/sig/whisper.rbs @@ -468,6 +468,48 @@ module Whisper def full: (Whisper::Parakeet::Params, Array[Float] samples, ?Integer n_samples) -> self | (Whisper::Parakeet::Params, _Samples, ?Integer n_samples) -> self + # Number of generated text segments. + # + def full_n_segments: () -> Integer + + # Start time of a segment indexed by `segment_index` in centiseconds (10 times milliseconds). + # + # full_get_segment_t0(3) # => 1668 (16680 ms) + # + def full_get_segment_t0: (Integer segment_index) -> Integer + + # End time of a segment indexed by `segment_index` in centiseconds (10 times milliseconds). + # + # full_get_segment_t1(3) # => 1668 (16680 ms) + # + def full_get_segment_t1: (Integer segment_index) -> Integer + + # Text of a segment indexed by `segment_index`. + # + # full_get_segment_text(3) # => "ask not what your country can do for you, ..." + # + def full_get_segment_text: (Integer segment_index) -> String + + # Number of tokens in the segment indexed by `segment_index`. + # + def full_n_tokens: (Integer segment_index) -> Integer + + # Text of the token indexed by `token_index` in the segment indexed by `segment_index`. + # + def full_get_token_text: (Integer segment_index, Integer token_index) -> String + + # Token id of the token indexed by `token_index` in the segment indexed by `segment_index`. + # + def full_get_token_id: (Integer segment_index, Integer token_index) -> Integer + + # Probability of the token indexed by `token_index` in the segment indexed by `segment_index`. + # + def full_get_token_p: (Integer segment_index, Integer token_index) -> Float + + # Token data of the token indexed by `token_index` in the segment indexed by `segment_index`. + # + def full_get_token_data: (Integer segment_index, Integer token_index) -> Token + # Yields each Whisper::Parakeet::Segment: # # parakeet.transcribe("path/to/audio.wav", params) From 93af6b20802176e054497c5ac0cf46eec82f0b99 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 22 May 2026 23:45:01 +0900 Subject: [PATCH 047/118] Register parakeet-tdt-0.6b-v3 --- bindings/ruby/lib/whisper/model/uri.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bindings/ruby/lib/whisper/model/uri.rb b/bindings/ruby/lib/whisper/model/uri.rb index 8eb57e5e8cf..5cd24ddc5b5 100644 --- a/bindings/ruby/lib/whisper/model/uri.rb +++ b/bindings/ruby/lib/whisper/model/uri.rb @@ -216,8 +216,14 @@ def escaping(path) @pre_converted_models[name] = URI.new("https://huggingface.co/ggml-org/whisper-vad/resolve/main/ggml-#{name}.bin") end + %w[ + parakeet-tdt-0.6b-v3 + ].each do |name| + @pre_converted_models[name] = URI.new("https://huggingface.co/danbev/parakeet/resolve/main/#{name}.bin") + end + @coreml_compiled_models = @pre_converted_models.each_with_object({}) {|(name, uri), models| - next if name.end_with?("-tdrz") || name.start_with?("silero-") + next if name.end_with?("-tdrz") || name.start_with?("silero-") || name.start_with?("parakeet-") if matched = name.match(/\A(?.*)-q\d_\d\z/) name = matched[:name] From cb669ef90f4aac993b5db292e59e1b74ec59fc38 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sat, 23 May 2026 00:29:44 +0900 Subject: [PATCH 048/118] Narrow scope of log constants --- bindings/ruby/ext/ruby_whisper.h | 3 --- bindings/ruby/ext/ruby_whisper_log.c | 3 +++ bindings/ruby/test/test_parakeet.rb | 2 +- bindings/ruby/test/test_parakeet_callback.rb | 2 +- bindings/ruby/test/test_parakeet_context.rb | 2 +- bindings/ruby/test/test_parakeet_segment.rb | 2 +- bindings/ruby/test/test_parakeet_token.rb | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index c4839346e5e..de0c1e66227 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -16,9 +16,6 @@ int ruby_thread_has_gvl_p(void); #endif -#define LOG_QUEUE_CAPACITY 256 -#define LOG_DEFAULT_CAPACITY 1024 - typedef struct { VALUE *context; VALUE user_data; diff --git a/bindings/ruby/ext/ruby_whisper_log.c b/bindings/ruby/ext/ruby_whisper_log.c index ed4746fd603..0a0f052109c 100644 --- a/bindings/ruby/ext/ruby_whisper_log.c +++ b/bindings/ruby/ext/ruby_whisper_log.c @@ -1,5 +1,8 @@ #include "ruby_whisper.h" +#define LOG_QUEUE_CAPACITY 256 +#define LOG_DEFAULT_CAPACITY 1024 + void ruby_whisper_log_queue_initialize(ruby_whisper_log_queue *log_queue) { diff --git a/bindings/ruby/test/test_parakeet.rb b/bindings/ruby/test/test_parakeet.rb index 351ebed7b77..97e9cc7e1d1 100644 --- a/bindings/ruby/test/test_parakeet.rb +++ b/bindings/ruby/test/test_parakeet.rb @@ -8,7 +8,7 @@ def test_log_set $stdout = StringIO.new Parakeet.log_set proc {|level, message, _| puts [level, message].join(": ")}, nil - Parakeet::Context.new(File.join(__dir__, "../../../models/parakeet-tdt-0.6b-v3.bin")) + Parakeet::Context.new("parakeet-tdt-0.6b-v3") sleep 0.1 $stdout.rewind logs = $stdout.string diff --git a/bindings/ruby/test/test_parakeet_callback.rb b/bindings/ruby/test/test_parakeet_callback.rb index ee4aa733da7..1c2d7e47f96 100644 --- a/bindings/ruby/test/test_parakeet_callback.rb +++ b/bindings/ruby/test/test_parakeet_callback.rb @@ -5,7 +5,7 @@ def setup Whisper.instance_variable_set "@whisper", nil GC.start @params = Parakeet::Params.new - @parakeet = Parakeet::Context.new(File.join(__dir__, "../../../models/parakeet-tdt-0.6b-v3.bin")) + @parakeet = Parakeet::Context.new("parakeet-tdt-0.6b-v3") end def test_new_segment_callback diff --git a/bindings/ruby/test/test_parakeet_context.rb b/bindings/ruby/test/test_parakeet_context.rb index 379ee0aa1ba..c5b464c7e0b 100644 --- a/bindings/ruby/test/test_parakeet_context.rb +++ b/bindings/ruby/test/test_parakeet_context.rb @@ -6,7 +6,7 @@ def setup Whisper.instance_variable_set "@whisper", nil GC.start - @parakeet = Parakeet::Context.new(File.join(__dir__, "../../../models/parakeet-tdt-0.6b-v3.bin")) + @parakeet = Parakeet::Context.new("parakeet-tdt-0.6b-v3") @params = Parakeet::Params.new end diff --git a/bindings/ruby/test/test_parakeet_segment.rb b/bindings/ruby/test/test_parakeet_segment.rb index 3192fb7bf0b..b342fed3e9c 100644 --- a/bindings/ruby/test/test_parakeet_segment.rb +++ b/bindings/ruby/test/test_parakeet_segment.rb @@ -2,7 +2,7 @@ class TestParakeetSegment < TestBase def setup - @parakeet = Parakeet::Context.new(File.join(__dir__, "../../../models/parakeet-tdt-0.6b-v3.bin")) + @parakeet = Parakeet::Context.new("parakeet-tdt-0.6b-v3") @parakeet.transcribe AUDIO, Parakeet::Params.new end diff --git a/bindings/ruby/test/test_parakeet_token.rb b/bindings/ruby/test/test_parakeet_token.rb index 3d211446a98..c6b5e0fc03e 100644 --- a/bindings/ruby/test/test_parakeet_token.rb +++ b/bindings/ruby/test/test_parakeet_token.rb @@ -18,7 +18,7 @@ def setup Whisper.instance_variable_set "@whisper", nil GC.start - parakeet = Parakeet::Context.new(File.join(__dir__, "../../../models/parakeet-tdt-0.6b-v3.bin")) + parakeet = Parakeet::Context.new("parakeet-tdt-0.6b-v3") params = Parakeet::Params.new parakeet.transcribe AUDIO, params @segment = parakeet.each_segment.first From 49e6cf2dfb7827982a89f6b46b60f6efe5550f1a Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sat, 23 May 2026 00:44:20 +0900 Subject: [PATCH 049/118] Extract activate and deactivate of log_queue --- bindings/ruby/ext/ruby_whisper_log.c | 21 +++++++++++++++++++++ bindings/ruby/ext/ruby_whisper_parakeet.c | 12 ++++-------- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper_log.c b/bindings/ruby/ext/ruby_whisper_log.c index 0a0f052109c..615bf89d904 100644 --- a/bindings/ruby/ext/ruby_whisper_log.c +++ b/bindings/ruby/ext/ruby_whisper_log.c @@ -30,6 +30,27 @@ ruby_whisper_log_queue_initialize(ruby_whisper_log_queue *log_queue) } } +void +ruby_whisper_log_queue_activate(ruby_whisper_log_queue *log_queue) +{ + rb_nativethread_lock_lock(&log_queue->lock); + + log_queue->is_active = true; + + rb_nativethread_lock_unlock(&log_queue->lock); +} + +void +ruby_whisper_log_queue_deactivate(ruby_whisper_log_queue *log_queue) +{ + rb_nativethread_lock_lock(&log_queue->lock); + + log_queue->is_active = false; + rb_native_cond_broadcast(&log_queue->cond); + + rb_nativethread_lock_unlock(&log_queue->lock); +} + static size_t calc_enough_cap(size_t len) { diff --git a/bindings/ruby/ext/ruby_whisper_parakeet.c b/bindings/ruby/ext/ruby_whisper_parakeet.c index 72653b125b5..d5fbac6c8f5 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet.c @@ -5,6 +5,8 @@ extern VALUE mParakeet; extern void ruby_whisper_log_queue_initialize(ruby_whisper_log_queue *log_queue); +extern void ruby_whisper_log_queue_activate(ruby_whisper_log_queue *log_queue); +extern void ruby_whisper_log_queue_deactivate(ruby_whisper_log_queue *log_queue); extern void ruby_whisper_log_queue_enqueue(ruby_whisper_log_queue *log_queue, enum ggml_log_level level, const char *text); extern VALUE ruby_whisper_log_queue_drain(ruby_whisper_log_queue *log_queue); @@ -32,7 +34,7 @@ ruby_whisper_parakeet_s_log_set(VALUE self, VALUE log_callback, VALUE user_data) } else { rb_iv_set(self, "@log_callback", log_callback); rb_iv_set(self, "@log_callback_user_data", user_data); - parakeet_log_queue.is_active = true; + ruby_whisper_log_queue_activate(¶keet_log_queue); rb_funcall(mParakeet, id_start_log_callback_thread, 0); parakeet_log_set(ruby_whisper_parakeet_log_callback, NULL); } @@ -47,13 +49,7 @@ ruby_whisper_parakeet_end_proc(VALUE args) ID id_alive = rb_intern("alive?"); ID id_join = rb_intern("join"); - rb_nativethread_lock_lock(¶keet_log_queue.lock); - - parakeet_log_queue.is_active = false; - rb_native_cond_broadcast(¶keet_log_queue.cond); - - rb_nativethread_lock_unlock(¶keet_log_queue.lock); - + ruby_whisper_log_queue_deactivate(¶keet_log_queue); VALUE log_callback_thread = rb_ivar_get(mParakeet, id_log_callback_thread); if (!NIL_P(log_callback_thread) && RTEST(rb_funcall(log_callback_thread, id_alive, 0))) { rb_funcall(log_callback_thread, id_join, 0); From 1f713bc6333a1a0e68dddfdf8b77bb78a444b944 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sat, 23 May 2026 00:47:26 +0900 Subject: [PATCH 050/118] Make start_log_callback_thread private --- bindings/ruby/lib/whisper/parakeet.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bindings/ruby/lib/whisper/parakeet.rb b/bindings/ruby/lib/whisper/parakeet.rb index e856ad2bb37..df9ac927474 100644 --- a/bindings/ruby/lib/whisper/parakeet.rb +++ b/bindings/ruby/lib/whisper/parakeet.rb @@ -5,6 +5,8 @@ module Parakeet extend Mutex_m class << self + private + def start_log_callback_thread return if @log_callback_thread&.alive? From d5e20de1a4fd8bf17bd9e2aa7467e62e99f69b07 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sat, 23 May 2026 00:54:41 +0900 Subject: [PATCH 051/118] Don't call start_log_callback_thread unncecessarilly --- bindings/ruby/ext/ruby_whisper_parakeet.c | 1 - 1 file changed, 1 deletion(-) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet.c b/bindings/ruby/ext/ruby_whisper_parakeet.c index d5fbac6c8f5..44982408118 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet.c @@ -68,5 +68,4 @@ init_ruby_whisper_parakeet() rb_set_end_proc(ruby_whisper_parakeet_end_proc, Qnil); rb_require("whisper/parakeet"); - rb_funcall(mParakeet, id_start_log_callback_thread, 0); } From 620a78aaa07af860fa02877ae9ec3fa0e6c5439c Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sat, 23 May 2026 01:07:28 +0900 Subject: [PATCH 052/118] Early return from log_queue_enqueue when not active --- bindings/ruby/ext/ruby_whisper_log.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bindings/ruby/ext/ruby_whisper_log.c b/bindings/ruby/ext/ruby_whisper_log.c index 615bf89d904..674421574aa 100644 --- a/bindings/ruby/ext/ruby_whisper_log.c +++ b/bindings/ruby/ext/ruby_whisper_log.c @@ -65,6 +65,11 @@ ruby_whisper_log_queue_enqueue(ruby_whisper_log_queue *log_queue, enum ggml_log_ { rb_nativethread_lock_lock(&log_queue->lock); + if (!log_queue->is_active) { + rb_nativethread_lock_unlock(&log_queue->lock); + return; + } + size_t len = strlen(text); ruby_whisper_log *log = &log_queue->logs[log_queue->head]; if (len > log->capacity) { From 2864933f8b54c490f6dba1636b959ad9577d9d80 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sat, 23 May 2026 01:26:48 +0900 Subject: [PATCH 053/118] Gropu log_queue members --- bindings/ruby/ext/ruby_whisper.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index de0c1e66227..fed51a3f43b 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -46,12 +46,11 @@ typedef struct ruby_whisper_log { typedef struct ruby_whisper_log_queue { rb_nativethread_lock_t lock; rb_nativethread_cond_t cond; + bool is_active; size_t head; size_t tail; size_t size; - bool is_active; - ruby_whisper_log *logs; } ruby_whisper_log_queue; From ae4d63ccd3779a430842fd7d684fe0ae3c08ecf1 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sat, 23 May 2026 01:34:31 +0900 Subject: [PATCH 054/118] is_active -> is_open --- bindings/ruby/ext/ruby_whisper.h | 2 +- bindings/ruby/ext/ruby_whisper_log.c | 16 ++++++++-------- bindings/ruby/ext/ruby_whisper_parakeet.c | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index fed51a3f43b..ba2514bfc34 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -46,7 +46,7 @@ typedef struct ruby_whisper_log { typedef struct ruby_whisper_log_queue { rb_nativethread_lock_t lock; rb_nativethread_cond_t cond; - bool is_active; + bool is_open; size_t head; size_t tail; diff --git a/bindings/ruby/ext/ruby_whisper_log.c b/bindings/ruby/ext/ruby_whisper_log.c index 674421574aa..68377dee66e 100644 --- a/bindings/ruby/ext/ruby_whisper_log.c +++ b/bindings/ruby/ext/ruby_whisper_log.c @@ -11,7 +11,7 @@ ruby_whisper_log_queue_initialize(ruby_whisper_log_queue *log_queue) log_queue->head = 0; log_queue->tail = 0; log_queue->size = 0; - log_queue->is_active = true; + log_queue->is_open = true; log_queue->logs = ALLOC_N(ruby_whisper_log, LOG_QUEUE_CAPACITY); for (size_t i = 0; i < LOG_QUEUE_CAPACITY; i++) { // we cannot call Ruby API like ALLOC_N because this slot may realloced without GVL @@ -31,21 +31,21 @@ ruby_whisper_log_queue_initialize(ruby_whisper_log_queue *log_queue) } void -ruby_whisper_log_queue_activate(ruby_whisper_log_queue *log_queue) +ruby_whisper_log_queue_open(ruby_whisper_log_queue *log_queue) { rb_nativethread_lock_lock(&log_queue->lock); - log_queue->is_active = true; + log_queue->is_open = true; rb_nativethread_lock_unlock(&log_queue->lock); } void -ruby_whisper_log_queue_deactivate(ruby_whisper_log_queue *log_queue) +ruby_whisper_log_queue_close(ruby_whisper_log_queue *log_queue) { rb_nativethread_lock_lock(&log_queue->lock); - log_queue->is_active = false; + log_queue->is_open = false; rb_native_cond_broadcast(&log_queue->cond); rb_nativethread_lock_unlock(&log_queue->lock); @@ -65,7 +65,7 @@ ruby_whisper_log_queue_enqueue(ruby_whisper_log_queue *log_queue, enum ggml_log_ { rb_nativethread_lock_lock(&log_queue->lock); - if (!log_queue->is_active) { + if (!log_queue->is_open) { rb_nativethread_lock_unlock(&log_queue->lock); return; } @@ -133,11 +133,11 @@ ruby_whisper_log_queue_drain(ruby_whisper_log_queue *log_queue) rb_nativethread_lock_lock(&log_queue->lock); - while (log_queue->size == 0 && log_queue->is_active) { + while (log_queue->size == 0 && log_queue->is_open) { rb_thread_call_without_gvl(ruby_whisper_log_queue_wait, (void *)log_queue, ruby_whisper_log_queue_wait_ubf, (void *)log_queue); } - if (log_queue->size == 0 && !log_queue->is_active) { + if (log_queue->size == 0 && !log_queue->is_open) { rb_native_cond_broadcast(&log_queue->cond); rb_nativethread_lock_unlock(&log_queue->lock); return Qnil; diff --git a/bindings/ruby/ext/ruby_whisper_parakeet.c b/bindings/ruby/ext/ruby_whisper_parakeet.c index 44982408118..064e02416cb 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet.c @@ -5,8 +5,8 @@ extern VALUE mParakeet; extern void ruby_whisper_log_queue_initialize(ruby_whisper_log_queue *log_queue); -extern void ruby_whisper_log_queue_activate(ruby_whisper_log_queue *log_queue); -extern void ruby_whisper_log_queue_deactivate(ruby_whisper_log_queue *log_queue); +extern void ruby_whisper_log_queue_open(ruby_whisper_log_queue *log_queue); +extern void ruby_whisper_log_queue_close(ruby_whisper_log_queue *log_queue); extern void ruby_whisper_log_queue_enqueue(ruby_whisper_log_queue *log_queue, enum ggml_log_level level, const char *text); extern VALUE ruby_whisper_log_queue_drain(ruby_whisper_log_queue *log_queue); @@ -34,7 +34,7 @@ ruby_whisper_parakeet_s_log_set(VALUE self, VALUE log_callback, VALUE user_data) } else { rb_iv_set(self, "@log_callback", log_callback); rb_iv_set(self, "@log_callback_user_data", user_data); - ruby_whisper_log_queue_activate(¶keet_log_queue); + ruby_whisper_log_queue_open(¶keet_log_queue); rb_funcall(mParakeet, id_start_log_callback_thread, 0); parakeet_log_set(ruby_whisper_parakeet_log_callback, NULL); } @@ -49,7 +49,7 @@ ruby_whisper_parakeet_end_proc(VALUE args) ID id_alive = rb_intern("alive?"); ID id_join = rb_intern("join"); - ruby_whisper_log_queue_deactivate(¶keet_log_queue); + ruby_whisper_log_queue_close(¶keet_log_queue); VALUE log_callback_thread = rb_ivar_get(mParakeet, id_log_callback_thread); if (!NIL_P(log_callback_thread) && RTEST(rb_funcall(log_callback_thread, id_alive, 0))) { rb_funcall(log_callback_thread, id_join, 0); From 693fefde420bb74607da0e6e86ca5ba890864ee1 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sat, 23 May 2026 01:40:46 +0900 Subject: [PATCH 055/118] Fix English --- bindings/ruby/ext/ruby_whisper_log.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper_log.c b/bindings/ruby/ext/ruby_whisper_log.c index 68377dee66e..713ccf227a9 100644 --- a/bindings/ruby/ext/ruby_whisper_log.c +++ b/bindings/ruby/ext/ruby_whisper_log.c @@ -14,8 +14,8 @@ ruby_whisper_log_queue_initialize(ruby_whisper_log_queue *log_queue) log_queue->is_open = true; log_queue->logs = ALLOC_N(ruby_whisper_log, LOG_QUEUE_CAPACITY); for (size_t i = 0; i < LOG_QUEUE_CAPACITY; i++) { - // we cannot call Ruby API like ALLOC_N because this slot may realloced without GVL - // doesn't free this because log queue lives at the end of process + // we cannot call Ruby API like ALLOC_N because this slot may be realloced without GVL + // this doesn't be freed because log queue lives until the end of process char *slot = malloc(sizeof(char) * LOG_QUEUE_CAPACITY); if (!slot) { rb_raise(rb_eRuntimeError, "Could not allocate memory for log text"); From b765ac43bb0088548eb3f5d170dab8d8d32ff4a5 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sat, 23 May 2026 02:36:38 +0900 Subject: [PATCH 056/118] Share parakeet full body function --- bindings/ruby/ext/ruby_whisper.h | 7 +++ .../ruby/ext/ruby_whisper_parakeet_context.c | 20 ++----- .../ext/ruby_whisper_parakeet_transcribe.cpp | 59 +++---------------- 3 files changed, 22 insertions(+), 64 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index ba2514bfc34..80dc59d96d1 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -110,6 +110,13 @@ typedef struct parsed_samples_t { bool memview_exported; } parsed_samples_t; +typedef struct { + VALUE *context; + VALUE *params; + float *samples; + int n_samples; +} ruby_whisper_parakeet_context_full_args; + typedef struct { struct parakeet_full_params params; ruby_whisper_callback_container *new_segment_callback_container; diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_context.c b/bindings/ruby/ext/ruby_whisper_parakeet_context.c index 8ad4b8f3a77..bd19c21ac19 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_context.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_context.c @@ -201,17 +201,10 @@ parakeet_full_ubf(void *rb_args) RUBY_ATOMIC_SET(args->abort_callback_user_data->is_interrupted, 1); } -typedef struct { - VALUE *context; - VALUE *params; - float *samples; - int n_samples; -} parakeet_full_args; - -static VALUE -parakeet_full_body(VALUE rb_args) +VALUE +ruby_whisper_parakeet_context_full_body(VALUE rb_args) { - parakeet_full_args *args = (parakeet_full_args *)rb_args; + ruby_whisper_parakeet_context_full_args *args = (ruby_whisper_parakeet_context_full_args *)rb_args; ruby_whisper_parakeet_context *rwpc; GetParakeetContext(*args->context, rwpc); ruby_whisper_parakeet_params *rwpp; @@ -248,18 +241,18 @@ ruby_whisper_parakeet_context_full(int argc, VALUE *argv, VALUE self) VALUE n_samples = argc == 2 ? Qnil : argv[2]; struct parsed_samples_t parsed = parse_samples(&argv[1], &n_samples); - parakeet_full_args args = { + ruby_whisper_parakeet_context_full_args args = { &self, &argv[0], parsed.samples, parsed.n_samples, }; - VALUE rb_result = rb_ensure(parakeet_full_body, (VALUE)&args, release_samples, (VALUE)&parsed); + VALUE rb_result = rb_ensure(ruby_whisper_parakeet_context_full_body, (VALUE)&args, release_samples, (VALUE)&parsed); const int result = NUM2INT(rb_result); if (result == 0) { return self; } else { - rb_exc_raise(rb_funcall(eError, id_new, 1, result)); + rb_exc_raise(rb_funcall(eError, id_new, 1, rb_result)); } } @@ -287,4 +280,3 @@ init_ruby_whisper_parakeet_context(VALUE *mParakeet) ITERATE_TOKEN_ATTRS(REGISTER_TOKEN_ATTR) } - diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp b/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp index 119668c3c40..f287d20b89e 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp +++ b/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp @@ -10,43 +10,13 @@ extern "C" { extern const rb_data_type_t ruby_whisper_parakeet_context_type; extern const rb_data_type_t ruby_whisper_parakeet_params_type; -extern void ruby_whisper_parakeet_prepare_transcription(ruby_whisper_parakeet_params *rwpp, VALUE *context, ruby_whisper_parakeet_abort_callback_user_data *abort_callback_user_data); +extern VALUE ruby_whisper_parakeet_context_full_body(VALUE rb_args); -extern ID id_to_s; extern ID id_to_path; extern ID id_new; extern VALUE eError; -typedef struct transcribe_without_gvl_args { - struct parakeet_context *context; - struct parakeet_full_params params; - float *samples; - size_t n_samples; - int result; -} transcribe_without_gvl_args; - -typedef struct { - ruby_whisper_parakeet_abort_callback_user_data *abort_callback_user_data; -} ruby_whisper_parakeet_transcribe_ubf_args; - -static void -ruby_whisper_parakeet_transcribe_ubf(void *rb_args) -{ - ruby_whisper_parakeet_transcribe_ubf_args *args = (ruby_whisper_parakeet_transcribe_ubf_args *)rb_args; - - RUBY_ATOMIC_SET(args->abort_callback_user_data->is_interrupted, 1); -} - -static void* -transcribe_without_gvl(void *rb_args) -{ - struct transcribe_without_gvl_args *args = (struct transcribe_without_gvl_args *)rb_args; - args->result = parakeet_full(args->context, args->params, args->samples, args->n_samples); - - return NULL; -} - VALUE ruby_whisper_parakeet_transcribe(VALUE self, VALUE audio_path, VALUE params) { @@ -68,29 +38,18 @@ ruby_whisper_parakeet_transcribe(VALUE self, VALUE audio_path, VALUE params) GetParakeetContext(self, rwpc); GetParakeetParams(params, rwpp); - ruby_whisper_parakeet_abort_callback_user_data abort_callback_user_data = { - 0, - NULL, - }; - ruby_whisper_parakeet_prepare_transcription(rwpp, &self, &abort_callback_user_data); - - struct transcribe_without_gvl_args args = { - rwpc->context, - rwpp->params, + ruby_whisper_parakeet_context_full_args args = { + &self, + ¶ms, pcmf32.data(), - pcmf32.size(), - 0, + (int)pcmf32.size(), }; - - ruby_whisper_parakeet_transcribe_ubf_args ubf_args = { - &abort_callback_user_data, - }; - - rb_thread_call_without_gvl(transcribe_without_gvl, (void *)&args, ruby_whisper_parakeet_transcribe_ubf, (void *)&ubf_args); - if (args.result == 0) { + VALUE rb_result = ruby_whisper_parakeet_context_full_body((VALUE)&args); + const int result = NUM2INT(rb_result); + if (result == 0) { return self; } else { - rb_exc_raise(rb_funcall(eError, id_new, 1, INT2NUM(args.result))); + rb_exc_raise(rb_funcall(eError, id_new, 1, rb_result)); } } From 989cba609cd08b354ce361675d2867052c75cf25 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sat, 23 May 2026 02:47:03 +0900 Subject: [PATCH 057/118] ruby_whisper_parakeet_abort_callback_user_data -> ruby_whisper_abort_callback_user_data --- bindings/ruby/ext/ruby_whisper.h | 4 ++-- bindings/ruby/ext/ruby_whisper_parakeet_context.c | 6 +++--- bindings/ruby/ext/ruby_whisper_parakeet_params.c | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index 80dc59d96d1..67d27ec2383 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -31,10 +31,10 @@ typedef struct { bool is_interrupted; } ruby_whisper_abort_callback_container; -typedef struct ruby_whisper_parakeet_abort_callback_user_data { +typedef struct ruby_whisper_abort_callback_user_data { volatile rb_atomic_t is_interrupted; ruby_whisper_callback_container *callback_container; -} ruby_whisper_parakeet_abort_callback_user_data; +} ruby_whisper_abort_callback_user_data; typedef struct ruby_whisper_log { enum ggml_log_level level; diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_context.c b/bindings/ruby/ext/ruby_whisper_parakeet_context.c index bd19c21ac19..cdd808d75dc 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_context.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_context.c @@ -30,7 +30,7 @@ extern VALUE ruby_whisper_parakeet_transcribe(VALUE self, VALUE audio_path, VALU extern VALUE ruby_whisper_parakeet_segment_init(VALUE context, int index); extern parsed_samples_t parse_samples(VALUE *samples, VALUE *n_samples); extern VALUE release_samples(VALUE rb_parsed_args); -extern void ruby_whisper_parakeet_prepare_transcription(ruby_whisper_parakeet_params *rwpp, VALUE *context, ruby_whisper_parakeet_abort_callback_user_data *abort_callback_user_data); +extern void ruby_whisper_parakeet_prepare_transcription(ruby_whisper_parakeet_params *rwpp, VALUE *context, ruby_whisper_abort_callback_user_data *abort_callback_user_data); extern rb_data_type_t ruby_whisper_parakeet_params_type; extern VALUE ruby_whisper_parakeet_token_s_from_token_data(struct parakeet_context *context, const parakeet_token_data *token_data); @@ -190,7 +190,7 @@ parakeet_full_without_gvl(void *rb_args) } typedef struct { - ruby_whisper_parakeet_abort_callback_user_data *abort_callback_user_data; + ruby_whisper_abort_callback_user_data *abort_callback_user_data; } parakeet_full_ubf_args; static void @@ -210,7 +210,7 @@ ruby_whisper_parakeet_context_full_body(VALUE rb_args) ruby_whisper_parakeet_params *rwpp; GetParakeetParams(*args->params, rwpp); - ruby_whisper_parakeet_abort_callback_user_data abort_callback_user_data = { + ruby_whisper_abort_callback_user_data abort_callback_user_data = { 0, NULL, }; diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c index 02d10aabd3f..bd3e451de84 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_params.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -302,7 +302,7 @@ call_parakeet_abort_callbacks(void *v_args) static bool ruby_whisper_parakeet_abort_callback(void *user_data) { - ruby_whisper_parakeet_abort_callback_user_data *data = (ruby_whisper_parakeet_abort_callback_user_data *)user_data; + ruby_whisper_abort_callback_user_data *data = (ruby_whisper_abort_callback_user_data *)user_data; int is_interrupted = RUBY_ATOMIC_LOAD(data->is_interrupted); if (is_interrupted) { @@ -325,7 +325,7 @@ ruby_whisper_parakeet_abort_callback(void *user_data) #define CALLBACK_CONTAINER_NAME(name) name ## _container void -ruby_whisper_parakeet_prepare_transcription(ruby_whisper_parakeet_params *rwpp, VALUE *context, ruby_whisper_parakeet_abort_callback_user_data *abort_callback_user_data) +ruby_whisper_parakeet_prepare_transcription(ruby_whisper_parakeet_params *rwpp, VALUE *context, ruby_whisper_abort_callback_user_data *abort_callback_user_data) { #define PARAM_NAME(name) name #define USER_DATA_NAME(name) name##_user_data From 6b47842126f67e48ddba8cc61e35896cb853e747 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sat, 23 May 2026 06:52:04 +0900 Subject: [PATCH 058/118] NULL check for callback containers --- bindings/ruby/ext/ruby_whisper_parakeet_params.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c index bd3e451de84..6d9e7f1e0f7 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_params.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -350,7 +350,10 @@ ruby_whisper_parakeet_params_mark(void *p) { ruby_whisper_parakeet_params *rwpp = (ruby_whisper_parakeet_params *)p; -#define MARK_CONTAINER(name) ruby_whisper_callback_container_mark(rwpp->name##_container); +#define MARK_CONTAINER(name) \ + if (rwpp->name##_container) { \ + ruby_whisper_callback_container_mark(rwpp->name##_container); \ + } ITERATE_CALLBACK_PARAMS(MARK_CONTAINER) } From c60735207a09827c6ea95be8df0b031d3c54fa96 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sat, 23 May 2026 06:55:41 +0900 Subject: [PATCH 059/118] Fix Parakeet.log_set --- bindings/ruby/ext/ruby_whisper_parakeet.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet.c b/bindings/ruby/ext/ruby_whisper_parakeet.c index 064e02416cb..2d88aa3073f 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet.c @@ -29,11 +29,11 @@ ruby_whisper_parakeet_log_callback(enum ggml_log_level level, const char *text, static VALUE ruby_whisper_parakeet_s_log_set(VALUE self, VALUE log_callback, VALUE user_data) { + rb_iv_set(self, "@log_callback", log_callback); + rb_iv_set(self, "@log_callback_user_data", user_data); if (NIL_P(log_callback)) { parakeet_log_set(NULL, NULL); } else { - rb_iv_set(self, "@log_callback", log_callback); - rb_iv_set(self, "@log_callback_user_data", user_data); ruby_whisper_log_queue_open(¶keet_log_queue); rb_funcall(mParakeet, id_start_log_callback_thread, 0); parakeet_log_set(ruby_whisper_parakeet_log_callback, NULL); From 4b294934671a76c158982613efccf4d5d7b3434d Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 27 May 2026 05:59:01 +0900 Subject: [PATCH 060/118] Omit Parakeet tests on CI --- bindings/ruby/Rakefile | 17 ++++++++++++++++- bindings/ruby/test/test_parakeet.rb | 2 +- bindings/ruby/test/test_parakeet_callback.rb | 2 ++ bindings/ruby/test/test_parakeet_context.rb | 2 ++ bindings/ruby/test/test_parakeet_segment.rb | 2 ++ bindings/ruby/test/test_parakeet_token.rb | 2 ++ 6 files changed, 25 insertions(+), 2 deletions(-) diff --git a/bindings/ruby/Rakefile b/bindings/ruby/Rakefile index 7b521b3bdfa..2327651a06a 100644 --- a/bindings/ruby/Rakefile +++ b/bindings/ruby/Rakefile @@ -84,6 +84,21 @@ else end end +TEST_PARAKEET_MODEL = "test/fixtures/for-tests-ggml-parakeet-tdt.bin" +TEST_PARAKEET_MODEL_SRC = File.expand_path(File.join(__dir__, "..", "..", "models", "for-tests-ggml-parakeet-tdt.bin")) +TEST_PARAKEET_MODEL_DIR = TEST_PARAKEET_MODEL.pathmap("%d") +directory TEST_PARAKEET_MODEL_DIR +if File.exist? TEST_PARAKEET_MODEL_SRC + file TEST_PARAKEET_MODEL => [TEST_PARAKEET_MODEL_SRC, TEST_PARAKEET_MODEL_DIR] do |t| + symlink t.source, t.name + end +else + require "open-uri" + file TEST_PARAKEET_MODEL => TEST_PARAKEET_MODEL_DIR do |t| + File.write t.name, URI("https://github.com/ggml-org/whisper.cpp/raw/refs/heads/master/models/for-tests-ggml-parakeet-tdt.bin").read + end +end + TEST_MEMORY_VIEW = "test/jfk_reader/jfk_reader.#{RbConfig::CONFIG['DLEXT']}" file TEST_MEMORY_VIEW => "test/jfk_reader/jfk_reader.c" do |t| chdir "test/jfk_reader" do @@ -93,4 +108,4 @@ file TEST_MEMORY_VIEW => "test/jfk_reader/jfk_reader.c" do |t| end CLEAN.include TEST_MEMORY_VIEW -task test: [LIB_FILE, TEST_MEMORY_VIEW, TEST_FIXTURE_AUDIO] +task test: [LIB_FILE, TEST_MEMORY_VIEW, TEST_FIXTURE_AUDIO, TEST_PARAKEET_MODEL] diff --git a/bindings/ruby/test/test_parakeet.rb b/bindings/ruby/test/test_parakeet.rb index 97e9cc7e1d1..cc656dc90d0 100644 --- a/bindings/ruby/test/test_parakeet.rb +++ b/bindings/ruby/test/test_parakeet.rb @@ -8,7 +8,7 @@ def test_log_set $stdout = StringIO.new Parakeet.log_set proc {|level, message, _| puts [level, message].join(": ")}, nil - Parakeet::Context.new("parakeet-tdt-0.6b-v3") + Parakeet::Context.new("test/fixtures/for-tests-ggml-parakeet-tdt.bin") sleep 0.1 $stdout.rewind logs = $stdout.string diff --git a/bindings/ruby/test/test_parakeet_callback.rb b/bindings/ruby/test/test_parakeet_callback.rb index 1c2d7e47f96..dfec13799c3 100644 --- a/bindings/ruby/test/test_parakeet_callback.rb +++ b/bindings/ruby/test/test_parakeet_callback.rb @@ -2,6 +2,8 @@ class TestParakeetCallback < TestBase def setup + omit "Skip not to download large model" if ENV["CI"] + Whisper.instance_variable_set "@whisper", nil GC.start @params = Parakeet::Params.new diff --git a/bindings/ruby/test/test_parakeet_context.rb b/bindings/ruby/test/test_parakeet_context.rb index c5b464c7e0b..c5e151b52b7 100644 --- a/bindings/ruby/test/test_parakeet_context.rb +++ b/bindings/ruby/test/test_parakeet_context.rb @@ -3,6 +3,8 @@ class TestParakeetContext < TestBase def setup + omit "Skip not to download large model" if ENV["CI"] + Whisper.instance_variable_set "@whisper", nil GC.start diff --git a/bindings/ruby/test/test_parakeet_segment.rb b/bindings/ruby/test/test_parakeet_segment.rb index b342fed3e9c..37b5806d932 100644 --- a/bindings/ruby/test/test_parakeet_segment.rb +++ b/bindings/ruby/test/test_parakeet_segment.rb @@ -2,6 +2,8 @@ class TestParakeetSegment < TestBase def setup + omit "Skip not to download large model" if ENV["CI"] + @parakeet = Parakeet::Context.new("parakeet-tdt-0.6b-v3") @parakeet.transcribe AUDIO, Parakeet::Params.new end diff --git a/bindings/ruby/test/test_parakeet_token.rb b/bindings/ruby/test/test_parakeet_token.rb index c6b5e0fc03e..4e1455662f3 100644 --- a/bindings/ruby/test/test_parakeet_token.rb +++ b/bindings/ruby/test/test_parakeet_token.rb @@ -15,6 +15,8 @@ class TestParakeetToken < TestBase ] def setup + omit "Skip not to download large model" if ENV["CI"] + Whisper.instance_variable_set "@whisper", nil GC.start From 5287dddfb7d27b5342d9f24fe8fdc5e40acbe8ef Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 27 May 2026 06:29:15 +0900 Subject: [PATCH 061/118] Extract Whisper::LogSettable --- bindings/ruby/ext/ruby_whisper.c | 7 +++++ bindings/ruby/ext/ruby_whisper_parakeet.c | 8 +++-- bindings/ruby/lib/whisper/log_settable.rb | 36 +++++++++++++++++++++++ bindings/ruby/lib/whisper/parakeet.rb | 34 --------------------- 4 files changed, 48 insertions(+), 37 deletions(-) create mode 100644 bindings/ruby/lib/whisper/log_settable.rb delete mode 100644 bindings/ruby/lib/whisper/parakeet.rb diff --git a/bindings/ruby/ext/ruby_whisper.c b/bindings/ruby/ext/ruby_whisper.c index 13b7c999c7e..654a1900b28 100644 --- a/bindings/ruby/ext/ruby_whisper.c +++ b/bindings/ruby/ext/ruby_whisper.c @@ -1,6 +1,7 @@ #include "ruby_whisper.h" VALUE mWhisper; +VALUE mLogSettable; VALUE mVAD; VALUE mParakeet; VALUE cContext; @@ -31,6 +32,8 @@ ID id_pre_converted_models; ID id_coreml_compiled_models; ID id_cache; ID id_n_processors; +ID id_extended; +ID id_start_log_callback_thread; static bool is_log_callback_finalized = false; static bool is_ruby_log_callback_present = false; @@ -198,8 +201,12 @@ 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"); mWhisper = rb_define_module("Whisper"); + rb_require("whisper/log_settable"); + mLogSettable = rb_path2class("Whisper::LogSettable"); mVAD = rb_define_module_under(mWhisper, "VAD"); mParakeet = rb_define_module_under(mWhisper, "Parakeet"); diff --git a/bindings/ruby/ext/ruby_whisper_parakeet.c b/bindings/ruby/ext/ruby_whisper_parakeet.c index 2d88aa3073f..f805d547776 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet.c @@ -3,6 +3,9 @@ #include extern VALUE mParakeet; +extern VALUE mLogSettable; +extern ID id_extended; +extern ID id_start_log_callback_thread; extern void ruby_whisper_log_queue_initialize(ruby_whisper_log_queue *log_queue); extern void ruby_whisper_log_queue_open(ruby_whisper_log_queue *log_queue); @@ -10,8 +13,6 @@ extern void ruby_whisper_log_queue_close(ruby_whisper_log_queue *log_queue); extern void ruby_whisper_log_queue_enqueue(ruby_whisper_log_queue *log_queue, enum ggml_log_level level, const char *text); extern VALUE ruby_whisper_log_queue_drain(ruby_whisper_log_queue *log_queue); -ID id_start_log_callback_thread; - static ruby_whisper_log_queue parakeet_log_queue; static VALUE @@ -67,5 +68,6 @@ init_ruby_whisper_parakeet() rb_define_private_method(rb_singleton_class(mParakeet), "drain_logs", ruby_whisper_parakeet_s_drain_logs, 0); rb_set_end_proc(ruby_whisper_parakeet_end_proc, Qnil); - rb_require("whisper/parakeet"); + rb_extend_object(mParakeet, mLogSettable); + rb_funcall(mLogSettable, id_extended, 1, mParakeet); } diff --git a/bindings/ruby/lib/whisper/log_settable.rb b/bindings/ruby/lib/whisper/log_settable.rb new file mode 100644 index 00000000000..2f8218d26ee --- /dev/null +++ b/bindings/ruby/lib/whisper/log_settable.rb @@ -0,0 +1,36 @@ +require "mutex_m" + +module Whisper + module LogSettable + class << self + def extended(base) + base.extend Mutex_m + end + end + + private + + def start_log_callback_thread + return if @log_callback_thread&.alive? + + @log_callback_thread = Thread.new { + begin + while logs = drain_logs + begin + callback, user_data = synchronize {[@log_callback, @log_callback_user_data]} + next if callback.nil? + + logs.each do |(level, text)| + callback.call level, text, user_data + end + rescue => err + $stderr.puts err + end + end + rescue => err + $stderr.puts err + end + } + end + end +end diff --git a/bindings/ruby/lib/whisper/parakeet.rb b/bindings/ruby/lib/whisper/parakeet.rb deleted file mode 100644 index df9ac927474..00000000000 --- a/bindings/ruby/lib/whisper/parakeet.rb +++ /dev/null @@ -1,34 +0,0 @@ -require "mutex_m" - -module Whisper - module Parakeet - extend Mutex_m - - class << self - private - - def start_log_callback_thread - return if @log_callback_thread&.alive? - - @log_callback_thread = Thread.new { - begin - while logs = drain_logs - begin - callback, user_data = synchronize {[@log_callback, @log_callback_user_data]} - next if callback.nil? - - logs.each do |(level, text)| - callback.call level, text, user_data - end - rescue => err - $stderr.puts err - end - end - rescue => err - $stderr.puts err - end - } - end - end - end -end From 31d254cceb4ccad0dcd13be838ae787898f4e068 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 27 May 2026 07:01:02 +0900 Subject: [PATCH 062/118] Join log callback thread in a log queue function --- bindings/ruby/ext/ruby_whisper.c | 6 ++++++ ...ruby_whisper_log.c => ruby_whisper_log_queue.c} | 11 ++++++++++- bindings/ruby/ext/ruby_whisper_parakeet.c | 14 ++------------ 3 files changed, 18 insertions(+), 13 deletions(-) rename bindings/ruby/ext/{ruby_whisper_log.c => ruby_whisper_log_queue.c} (92%) diff --git a/bindings/ruby/ext/ruby_whisper.c b/bindings/ruby/ext/ruby_whisper.c index 654a1900b28..9a79fe6fe5a 100644 --- a/bindings/ruby/ext/ruby_whisper.c +++ b/bindings/ruby/ext/ruby_whisper.c @@ -34,6 +34,9 @@ ID id_cache; ID id_n_processors; ID id_extended; ID id_start_log_callback_thread; +ID id_log_callback_thread; +ID id_alive; +ID id_join; static bool is_log_callback_finalized = false; static bool is_ruby_log_callback_present = false; @@ -203,6 +206,9 @@ void Init_whisper() { 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 = rb_intern("alive?"); + id_join = rb_intern("join"); mWhisper = rb_define_module("Whisper"); rb_require("whisper/log_settable"); diff --git a/bindings/ruby/ext/ruby_whisper_log.c b/bindings/ruby/ext/ruby_whisper_log_queue.c similarity index 92% rename from bindings/ruby/ext/ruby_whisper_log.c rename to bindings/ruby/ext/ruby_whisper_log_queue.c index 713ccf227a9..6d623a2f3a1 100644 --- a/bindings/ruby/ext/ruby_whisper_log.c +++ b/bindings/ruby/ext/ruby_whisper_log_queue.c @@ -3,6 +3,10 @@ #define LOG_QUEUE_CAPACITY 256 #define LOG_DEFAULT_CAPACITY 1024 +extern ID id_log_callback_thread; +extern ID id_alive; +extern ID id_join; + void ruby_whisper_log_queue_initialize(ruby_whisper_log_queue *log_queue) { @@ -41,7 +45,7 @@ ruby_whisper_log_queue_open(ruby_whisper_log_queue *log_queue) } void -ruby_whisper_log_queue_close(ruby_whisper_log_queue *log_queue) +ruby_whisper_log_queue_close(ruby_whisper_log_queue *log_queue, VALUE *mod) { rb_nativethread_lock_lock(&log_queue->lock); @@ -49,6 +53,11 @@ ruby_whisper_log_queue_close(ruby_whisper_log_queue *log_queue) rb_native_cond_broadcast(&log_queue->cond); rb_nativethread_lock_unlock(&log_queue->lock); + + VALUE log_callback_thread = rb_ivar_get(*mod, id_log_callback_thread); + if (!NIL_P(log_callback_thread) && RTEST(rb_funcall(log_callback_thread, id_alive, 0))) { + rb_funcall(log_callback_thread, id_join, 0); + } } static size_t diff --git a/bindings/ruby/ext/ruby_whisper_parakeet.c b/bindings/ruby/ext/ruby_whisper_parakeet.c index f805d547776..cb123362261 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet.c @@ -9,7 +9,7 @@ extern ID id_start_log_callback_thread; extern void ruby_whisper_log_queue_initialize(ruby_whisper_log_queue *log_queue); extern void ruby_whisper_log_queue_open(ruby_whisper_log_queue *log_queue); -extern void ruby_whisper_log_queue_close(ruby_whisper_log_queue *log_queue); +extern void ruby_whisper_log_queue_close(ruby_whisper_log_queue *log_queue, VALUE *mod); extern void ruby_whisper_log_queue_enqueue(ruby_whisper_log_queue *log_queue, enum ggml_log_level level, const char *text); extern VALUE ruby_whisper_log_queue_drain(ruby_whisper_log_queue *log_queue); @@ -46,22 +46,12 @@ ruby_whisper_parakeet_s_log_set(VALUE self, VALUE log_callback, VALUE user_data) static void ruby_whisper_parakeet_end_proc(VALUE args) { - ID id_log_callback_thread = rb_intern("@log_callback_thread"); - ID id_alive = rb_intern("alive?"); - ID id_join = rb_intern("join"); - - ruby_whisper_log_queue_close(¶keet_log_queue); - VALUE log_callback_thread = rb_ivar_get(mParakeet, id_log_callback_thread); - if (!NIL_P(log_callback_thread) && RTEST(rb_funcall(log_callback_thread, id_alive, 0))) { - rb_funcall(log_callback_thread, id_join, 0); - } + ruby_whisper_log_queue_close(¶keet_log_queue, &mParakeet); } void init_ruby_whisper_parakeet() { - id_start_log_callback_thread = rb_intern("start_log_callback_thread"); - ruby_whisper_log_queue_initialize(¶keet_log_queue); rb_define_singleton_method(mParakeet, "log_set", ruby_whisper_parakeet_s_log_set, 2); From b6170aa6e49f880357e3ab984fad7e8eea689724 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 27 May 2026 07:25:32 +0900 Subject: [PATCH 063/118] Revert Join log callback thread in a log queue function --- bindings/ruby/ext/ruby_whisper_log_queue.c | 11 +---------- bindings/ruby/ext/ruby_whisper_parakeet.c | 12 ++++++++++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper_log_queue.c b/bindings/ruby/ext/ruby_whisper_log_queue.c index 6d623a2f3a1..713ccf227a9 100644 --- a/bindings/ruby/ext/ruby_whisper_log_queue.c +++ b/bindings/ruby/ext/ruby_whisper_log_queue.c @@ -3,10 +3,6 @@ #define LOG_QUEUE_CAPACITY 256 #define LOG_DEFAULT_CAPACITY 1024 -extern ID id_log_callback_thread; -extern ID id_alive; -extern ID id_join; - void ruby_whisper_log_queue_initialize(ruby_whisper_log_queue *log_queue) { @@ -45,7 +41,7 @@ ruby_whisper_log_queue_open(ruby_whisper_log_queue *log_queue) } void -ruby_whisper_log_queue_close(ruby_whisper_log_queue *log_queue, VALUE *mod) +ruby_whisper_log_queue_close(ruby_whisper_log_queue *log_queue) { rb_nativethread_lock_lock(&log_queue->lock); @@ -53,11 +49,6 @@ ruby_whisper_log_queue_close(ruby_whisper_log_queue *log_queue, VALUE *mod) rb_native_cond_broadcast(&log_queue->cond); rb_nativethread_lock_unlock(&log_queue->lock); - - VALUE log_callback_thread = rb_ivar_get(*mod, id_log_callback_thread); - if (!NIL_P(log_callback_thread) && RTEST(rb_funcall(log_callback_thread, id_alive, 0))) { - rb_funcall(log_callback_thread, id_join, 0); - } } static size_t diff --git a/bindings/ruby/ext/ruby_whisper_parakeet.c b/bindings/ruby/ext/ruby_whisper_parakeet.c index cb123362261..7d8588b6770 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet.c @@ -5,11 +5,14 @@ extern VALUE mParakeet; extern VALUE mLogSettable; extern ID id_extended; +extern ID id_log_callback_thread; extern ID id_start_log_callback_thread; +extern ID id_alive; +extern ID id_join; extern void ruby_whisper_log_queue_initialize(ruby_whisper_log_queue *log_queue); extern void ruby_whisper_log_queue_open(ruby_whisper_log_queue *log_queue); -extern void ruby_whisper_log_queue_close(ruby_whisper_log_queue *log_queue, VALUE *mod); +extern void ruby_whisper_log_queue_close(ruby_whisper_log_queue *log_queue); extern void ruby_whisper_log_queue_enqueue(ruby_whisper_log_queue *log_queue, enum ggml_log_level level, const char *text); extern VALUE ruby_whisper_log_queue_drain(ruby_whisper_log_queue *log_queue); @@ -46,7 +49,12 @@ ruby_whisper_parakeet_s_log_set(VALUE self, VALUE log_callback, VALUE user_data) static void ruby_whisper_parakeet_end_proc(VALUE args) { - ruby_whisper_log_queue_close(¶keet_log_queue, &mParakeet); + ruby_whisper_log_queue_close(¶keet_log_queue); + + VALUE log_callback_thread = rb_ivar_get(mParakeet, id_log_callback_thread); + if (!NIL_P(log_callback_thread) && RTEST(rb_funcall(log_callback_thread, id_alive, 0))) { + rb_funcall(log_callback_thread, id_join, 0); + } } void From f792bab200da9bd3122fc20b7db8b09b044f6fed Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 28 May 2026 04:45:22 +0900 Subject: [PATCH 064/118] Extract output methods to modules --- bindings/ruby/ext/ruby_whisper.c | 11 +++++- bindings/ruby/lib/whisper/context.rb | 15 ------- bindings/ruby/lib/whisper/segment.rb | 58 ---------------------------- 3 files changed, 9 insertions(+), 75 deletions(-) delete mode 100644 bindings/ruby/lib/whisper/context.rb delete mode 100644 bindings/ruby/lib/whisper/segment.rb diff --git a/bindings/ruby/ext/ruby_whisper.c b/bindings/ruby/ext/ruby_whisper.c index 9a79fe6fe5a..37ce9fe84eb 100644 --- a/bindings/ruby/ext/ruby_whisper.c +++ b/bindings/ruby/ext/ruby_whisper.c @@ -19,6 +19,9 @@ VALUE cSegment; VALUE cToken; VALUE cModel; +VALUE mOutputContext; +VALUE mOutputSegment; + ID id_to_s; ID id_call; ID id___method__; @@ -215,6 +218,9 @@ void Init_whisper() { mLogSettable = rb_path2class("Whisper::LogSettable"); mVAD = rb_define_module_under(mWhisper, "VAD"); mParakeet = rb_define_module_under(mWhisper, "Parakeet"); + rb_require("whisper/output"); + mOutputContext = rb_path2class("Whisper::Output::Context"); + mOutputSegment = rb_path2class("Whisper::Output::Segment"); rb_define_const(mWhisper, "VERSION", rb_str_new2(whisper_version())); rb_define_const(mWhisper, "LOG_LEVEL_NONE", INT2NUM(GGML_LOG_LEVEL_NONE)); @@ -265,7 +271,8 @@ void Init_whisper() { init_ruby_whisper_parakeet_segment(&mParakeet); init_ruby_whisper_parakeet_context(&mParakeet); - rb_require("whisper/context"); - rb_require("whisper/segment"); rb_require("whisper/model/uri"); + + rb_include_module(cContext, mOutputContext); + rb_include_module(cSegment, mOutputSegment); } diff --git a/bindings/ruby/lib/whisper/context.rb b/bindings/ruby/lib/whisper/context.rb deleted file mode 100644 index c3a134b773d..00000000000 --- a/bindings/ruby/lib/whisper/context.rb +++ /dev/null @@ -1,15 +0,0 @@ -module Whisper - class Context - def to_srt - each_segment.with_index.reduce("") {|srt, (segment, index)| - srt << "#{index + 1}\n#{segment.to_srt_cue}\n" - } - end - - def to_webvtt - each_segment.with_index.reduce("WEBVTT\n\n") {|webvtt, (segment, index)| - webvtt << "#{index + 1}\n#{segment.to_webvtt_cue}\n" - } - end - end -end diff --git a/bindings/ruby/lib/whisper/segment.rb b/bindings/ruby/lib/whisper/segment.rb deleted file mode 100644 index dc187dcac36..00000000000 --- a/bindings/ruby/lib/whisper/segment.rb +++ /dev/null @@ -1,58 +0,0 @@ -module Whisper - class Segment - SRT_ESCAPES = { - "&" => "&", - "<" => "<", - ">" => ">", - } - SRT_ESCAPES_RE = Regexp.union(SRT_ESCAPES.keys) - private_constant :SRT_ESCAPES, :SRT_ESCAPES_RE - - def to_srt_cue - "#{srt_start_time} --> #{srt_end_time}\n#{srt_text}\n" - end - - def to_webvtt_cue - "#{webvtt_start_time} --> #{webvtt_end_time}\n#{webvtt_text}\n" - end - - private - - def time_to_a(time) - sec, decimal_part = time.divmod(1000) - min, sec = sec.divmod(60) - hour, min = min.divmod(60) - [hour, min, sec, decimal_part] - end - - def srt_time(time) - "%02d:%02d:%02d,%03d" % time_to_a(time) - end - - def srt_start_time - srt_time(start_time) - end - - def srt_end_time - srt_time(end_time) - end - - def srt_text - text.gsub(SRT_ESCAPES_RE, SRT_ESCAPES) - end - - def webvtt_time(time) - "%02d:%02d:%02d.%03d" % time_to_a(time) - end - - def webvtt_start_time - webvtt_time(start_time) - end - - def webvtt_end_time - webvtt_time(end_time) - end - - alias webvtt_text srt_text - end -end From 35691631fb0d4f333c98e3679501fab8d6de558d Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 28 May 2026 04:53:00 +0900 Subject: [PATCH 065/118] Move Parakeet init functions into init_parakeet() --- bindings/ruby/ext/ruby_whisper.c | 13 ++----------- bindings/ruby/ext/ruby_whisper_parakeet.c | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper.c b/bindings/ruby/ext/ruby_whisper.c index 37ce9fe84eb..ece02732e19 100644 --- a/bindings/ruby/ext/ruby_whisper.c +++ b/bindings/ruby/ext/ruby_whisper.c @@ -58,11 +58,7 @@ extern void init_ruby_whisper_vad_params(VALUE *mVAD); extern void init_ruby_whisper_vad_context(VALUE *mVAD); extern void init_ruby_whisper_vad_segment(VALUE *mVAD); extern void init_ruby_whisper_vad_segments(VALUE *mVAD); -extern void init_ruby_whisper_parakeet(); -extern void init_ruby_whisper_parakeet_params(VALUE *mParakeet); -extern void init_ruby_whisper_parakeet_token(VALUE *mParakeet); -extern void init_ruby_whisper_parakeet_segment(VALUE *mParakeet); -extern void init_ruby_whisper_parakeet_context(VALUE *mParakeet); +extern void init_ruby_whisper_parakeet(VALUE *mWhisper); extern void register_callbacks(ruby_whisper_params *rwp, VALUE *context); /* @@ -217,7 +213,6 @@ void Init_whisper() { rb_require("whisper/log_settable"); mLogSettable = rb_path2class("Whisper::LogSettable"); mVAD = rb_define_module_under(mWhisper, "VAD"); - mParakeet = rb_define_module_under(mWhisper, "Parakeet"); rb_require("whisper/output"); mOutputContext = rb_path2class("Whisper::Output::Context"); mOutputSegment = rb_path2class("Whisper::Output::Segment"); @@ -265,11 +260,7 @@ void Init_whisper() { init_ruby_whisper_vad_segment(&mVAD); init_ruby_whisper_vad_segments(&mVAD); init_ruby_whisper_vad_context(&mVAD); - init_ruby_whisper_parakeet(); - init_ruby_whisper_parakeet_params(&mParakeet); - init_ruby_whisper_parakeet_token(&mParakeet); - init_ruby_whisper_parakeet_segment(&mParakeet); - init_ruby_whisper_parakeet_context(&mParakeet); + init_ruby_whisper_parakeet(&mWhisper); rb_require("whisper/model/uri"); diff --git a/bindings/ruby/ext/ruby_whisper_parakeet.c b/bindings/ruby/ext/ruby_whisper_parakeet.c index 7d8588b6770..f854e421987 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet.c @@ -4,12 +4,19 @@ extern VALUE mParakeet; extern VALUE mLogSettable; +extern VALUE mOutputContext; +extern VALUE mOutputSegment; extern ID id_extended; extern ID id_log_callback_thread; extern ID id_start_log_callback_thread; extern ID id_alive; extern ID id_join; +extern void init_ruby_whisper_parakeet_params(VALUE *mParakeet); +extern void init_ruby_whisper_parakeet_token(VALUE *mParakeet); +extern void init_ruby_whisper_parakeet_segment(VALUE *mParakeet); +extern void init_ruby_whisper_parakeet_context(VALUE *mParakeet); + extern void ruby_whisper_log_queue_initialize(ruby_whisper_log_queue *log_queue); extern void ruby_whisper_log_queue_open(ruby_whisper_log_queue *log_queue); extern void ruby_whisper_log_queue_close(ruby_whisper_log_queue *log_queue); @@ -58,8 +65,10 @@ ruby_whisper_parakeet_end_proc(VALUE args) } void -init_ruby_whisper_parakeet() +init_ruby_whisper_parakeet(VALUE *mWhisper) { + mParakeet = rb_define_module_under(*mWhisper, "Parakeet"); + ruby_whisper_log_queue_initialize(¶keet_log_queue); rb_define_singleton_method(mParakeet, "log_set", ruby_whisper_parakeet_s_log_set, 2); @@ -68,4 +77,9 @@ init_ruby_whisper_parakeet() rb_set_end_proc(ruby_whisper_parakeet_end_proc, Qnil); rb_extend_object(mParakeet, mLogSettable); rb_funcall(mLogSettable, id_extended, 1, mParakeet); + + init_ruby_whisper_parakeet_params(&mParakeet); + init_ruby_whisper_parakeet_token(&mParakeet); + init_ruby_whisper_parakeet_segment(&mParakeet); + init_ruby_whisper_parakeet_context(&mParakeet); } From aaeff7fce6cce228af843be0f82a32f89f9fbb9f Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 28 May 2026 04:58:24 +0900 Subject: [PATCH 066/118] Add output methods to Parakeet classes --- bindings/ruby/ext/ruby_whisper_parakeet.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet.c b/bindings/ruby/ext/ruby_whisper_parakeet.c index f854e421987..9141ba67dac 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet.c @@ -4,6 +4,8 @@ extern VALUE mParakeet; extern VALUE mLogSettable; +extern VALUE cParakeetContext; +extern VALUE cParakeetSegment; extern VALUE mOutputContext; extern VALUE mOutputSegment; extern ID id_extended; @@ -82,4 +84,7 @@ init_ruby_whisper_parakeet(VALUE *mWhisper) init_ruby_whisper_parakeet_token(&mParakeet); init_ruby_whisper_parakeet_segment(&mParakeet); init_ruby_whisper_parakeet_context(&mParakeet); + + rb_include_module(cParakeetContext, mOutputContext); + rb_include_module(cParakeetSegment, mOutputSegment); } From f76e8fda1b32e0ee952692480e42b8a4bcc6d5e8 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 28 May 2026 05:08:11 +0900 Subject: [PATCH 067/118] Add Parakeet's output methods to RBS --- bindings/ruby/sig/whisper.rbs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bindings/ruby/sig/whisper.rbs b/bindings/ruby/sig/whisper.rbs index 91c92c84b96..567fa58c438 100644 --- a/bindings/ruby/sig/whisper.rbs +++ b/bindings/ruby/sig/whisper.rbs @@ -525,6 +525,9 @@ module Whisper # def each_segment: { (Segment) -> void } -> void | () -> Enumerator[Segment] + + def to_srt: () -> String + def to_webvtt: () -> String end class Params From cbfe86f455bf8f1161b908f37696d8881f46b83b Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 28 May 2026 20:52:49 +0900 Subject: [PATCH 068/118] Use Whisper::Output in RBS --- bindings/ruby/sig/whisper.rbs | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/bindings/ruby/sig/whisper.rbs b/bindings/ruby/sig/whisper.rbs index 567fa58c438..5cdd95c994a 100644 --- a/bindings/ruby/sig/whisper.rbs +++ b/bindings/ruby/sig/whisper.rbs @@ -40,7 +40,21 @@ module Whisper def self.log_set: (log_callback?, Object? user_data) -> log_callback def self.system_info_str: () -> String + module Output + module Context + def to_srt: () -> String + def to_webvtt: () -> String + end + + module Segment + def to_srt_cue: () -> String + def to_webvtt_cue: () -> String + end + end + class Context + include Output::Context + def self.new: (String | path | ::URI::HTTP) -> instance # transcribe a single file @@ -139,9 +153,6 @@ module Whisper | (Whisper::Params, _Samples, ?Integer n_samples) -> self | (Whisper::Params, _Samples, ?Integer? n_samples, Integer n_processors) -> self - def to_srt: () -> String - def to_webvtt: () -> String - class Params def self.new: ( use_gpu: boolish, @@ -451,6 +462,8 @@ module Whisper | (^(Integer level, String message, Object user_data) -> void, Object? user_data) -> nil class Context + include Output::Context + # Load a Parakeet model from the given file path. # def self.new: (String | path | ::URI::HTTP) -> instance @@ -525,9 +538,6 @@ module Whisper # def each_segment: { (Segment) -> void } -> void | () -> Enumerator[Segment] - - def to_srt: () -> String - def to_webvtt: () -> String end class Params @@ -672,6 +682,8 @@ module Whisper end class Segment + include Output::Segment + type deconstructed_keys = { start_time: (Integer | nil), end_time: (Integer | nil), @@ -796,6 +808,8 @@ module Whisper end class Segment + include Output::Segment + type deconstructed_keys = { start_time: (Integer | nil), end_time: (Integer | nil), @@ -836,9 +850,6 @@ module Whisper # def each_token: { (Token) -> void } -> void | () -> Enumerator[Token] - def to_srt_cue: () -> String - def to_webvtt_cue: () -> String - # Possible keys: `:start_time`, `:end_time`, `:text`, `:no_speech_prob`, `:speaker_turn_next` # From b536fe60f46aa0323560fa3978d2af555bade7be Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 28 May 2026 21:03:53 +0900 Subject: [PATCH 069/118] Add LogSettable to RBS --- bindings/ruby/sig/whisper.rbs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bindings/ruby/sig/whisper.rbs b/bindings/ruby/sig/whisper.rbs index 5cdd95c994a..845f551c64f 100644 --- a/bindings/ruby/sig/whisper.rbs +++ b/bindings/ruby/sig/whisper.rbs @@ -455,7 +455,12 @@ module Whisper def abort_on: { (Object user_data) -> boolish } -> void end + module LogSettable + end + module Parakeet + extend LogSettable + # Control logging output. The default behavior is to print to stderr. # def self.log_set: (nil, Object? user_data) -> nil From 4f4224d954114cc1d69b326648ec148df3954267 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 28 May 2026 21:39:59 +0900 Subject: [PATCH 070/118] Fix module Token -> class Token --- bindings/ruby/sig/whisper.rbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/ruby/sig/whisper.rbs b/bindings/ruby/sig/whisper.rbs index 845f551c64f..0cba0cc51d3 100644 --- a/bindings/ruby/sig/whisper.rbs +++ b/bindings/ruby/sig/whisper.rbs @@ -866,7 +866,7 @@ module Whisper def deconstruct_keys: (Array[:start_time | :end_time | :text | :no_speech_prob | :speaker_turn_next | :n_tokens] | nil) -> deconstructed_keys end - module Token + class Token type deconstructed_keys = { id: (Integer | nil), tid: (Integer | nil), From 6cefb43f626cc1a89e829db6f13bdbb7c2765ce6 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 28 May 2026 22:29:35 +0900 Subject: [PATCH 071/118] Add Parakeet::Model --- bindings/ruby/ext/ruby_whisper.c | 1 + bindings/ruby/ext/ruby_whisper.h | 11 +++ bindings/ruby/ext/ruby_whisper_parakeet.c | 2 + .../ruby/ext/ruby_whisper_parakeet_context.c | 8 ++ .../ruby/ext/ruby_whisper_parakeet_model.c | 75 +++++++++++++++++++ 5 files changed, 97 insertions(+) create mode 100644 bindings/ruby/ext/ruby_whisper_parakeet_model.c diff --git a/bindings/ruby/ext/ruby_whisper.c b/bindings/ruby/ext/ruby_whisper.c index ece02732e19..b669e8197d3 100644 --- a/bindings/ruby/ext/ruby_whisper.c +++ b/bindings/ruby/ext/ruby_whisper.c @@ -13,6 +13,7 @@ VALUE cVADSegment; VALUE cParakeetContext; VALUE cParakeetParams; VALUE cParakeetSegment; +VALUE cParakeetModel; VALUE eError; VALUE cSegment; diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index 67d27ec2383..9fe3743e4d8 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -140,6 +140,10 @@ typedef struct { VALUE text; } ruby_whisper_parakeet_token; +typedef struct { + VALUE context; +} ruby_whisper_parakeet_model; + #define GetContext(obj, rw) do { \ TypedData_Get_Struct((obj), ruby_whisper, &ruby_whisper_type, (rw)); \ if ((rw)->context == NULL) { \ @@ -208,4 +212,11 @@ typedef struct { } \ } while (0) +#define GetParakeetModel(obj, rwpm) do { \ + TypedData_Get_Struct((obj), ruby_whisper_parakeet_model, &ruby_whisper_parakeet_model_type, (rwpm)); \ + if (NIL_P((rwpm)->context)) { \ + rb_raise(rb_eRuntimeError, "Not initialized"); \ + } \ +} while (0) + #endif diff --git a/bindings/ruby/ext/ruby_whisper_parakeet.c b/bindings/ruby/ext/ruby_whisper_parakeet.c index 9141ba67dac..af0ae658599 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet.c @@ -18,6 +18,7 @@ extern void init_ruby_whisper_parakeet_params(VALUE *mParakeet); extern void init_ruby_whisper_parakeet_token(VALUE *mParakeet); extern void init_ruby_whisper_parakeet_segment(VALUE *mParakeet); extern void init_ruby_whisper_parakeet_context(VALUE *mParakeet); +extern void init_ruby_whisper_parakeet_model(VALUE *mParakeet); extern void ruby_whisper_log_queue_initialize(ruby_whisper_log_queue *log_queue); extern void ruby_whisper_log_queue_open(ruby_whisper_log_queue *log_queue); @@ -84,6 +85,7 @@ init_ruby_whisper_parakeet(VALUE *mWhisper) init_ruby_whisper_parakeet_token(&mParakeet); init_ruby_whisper_parakeet_segment(&mParakeet); init_ruby_whisper_parakeet_context(&mParakeet); + init_ruby_whisper_parakeet_model(&mParakeet); rb_include_module(cParakeetContext, mOutputContext); rb_include_module(cParakeetSegment, mOutputSegment); diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_context.c b/bindings/ruby/ext/ruby_whisper_parakeet_context.c index cdd808d75dc..a7f0d7a75b4 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_context.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_context.c @@ -33,6 +33,7 @@ extern VALUE release_samples(VALUE rb_parsed_args); extern void ruby_whisper_parakeet_prepare_transcription(ruby_whisper_parakeet_params *rwpp, VALUE *context, ruby_whisper_abort_callback_user_data *abort_callback_user_data); extern rb_data_type_t ruby_whisper_parakeet_params_type; extern VALUE ruby_whisper_parakeet_token_s_from_token_data(struct parakeet_context *context, const parakeet_token_data *token_data); +extern VALUE ruby_whisper_parakeet_model_s_new(VALUE context); static void ruby_whisper_parakeet_context_free(void *p) @@ -256,6 +257,12 @@ ruby_whisper_parakeet_context_full(int argc, VALUE *argv, VALUE self) } } +static VALUE +ruby_whisper_parakeet_context_get_model(VALUE self) +{ + return ruby_whisper_parakeet_model_s_new(self); +} + void init_ruby_whisper_parakeet_context(VALUE *mParakeet) { @@ -267,6 +274,7 @@ init_ruby_whisper_parakeet_context(VALUE *mParakeet) rb_define_method(cParakeetContext, "transcribe", ruby_whisper_parakeet_transcribe, 2); rb_define_method(cParakeetContext, "full_n_segments", ruby_whisper_parakeet_context_full_n_segments, 0); rb_define_method(cParakeetContext, "full_get_token_data", ruby_whisper_parakeet_context_full_get_token_data, 2); + rb_define_method(cParakeetContext, "model", ruby_whisper_parakeet_context_get_model, 0); rb_define_method(cParakeetContext, "each_segment", ruby_whisper_parakeet_context_each_segment, 0); rb_define_method(cParakeetContext, "full", ruby_whisper_parakeet_context_full, -1); diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_model.c b/bindings/ruby/ext/ruby_whisper_parakeet_model.c new file mode 100644 index 00000000000..cc43ab917aa --- /dev/null +++ b/bindings/ruby/ext/ruby_whisper_parakeet_model.c @@ -0,0 +1,75 @@ +#include "ruby_whisper.h" + +#define ITERATE_ATTRS(ITERATOR) \ + ITERATOR(n_vocab) \ + ITERATOR(n_audio_ctx) \ + ITERATOR(n_audio_state) \ + ITERATOR(n_audio_head) \ + ITERATOR(n_audio_layer) \ + ITERATOR(n_mels) \ + ITERATOR(ftype) + +extern rb_data_type_t ruby_whisper_parakeet_context_type; +extern VALUE cParakeetModel; + +static void +ruby_whisper_parakeet_model_mark(void *p) +{ + ruby_whisper_parakeet_model *rwpm = (ruby_whisper_parakeet_model *)p; + if (rwpm->context) { + rb_gc_mark(rwpm->context); + } +} + +static const rb_data_type_t ruby_whisper_parakeet_model_type = { + "ruby_whisper_parakeet_model", + {ruby_whisper_parakeet_model_mark, RUBY_DEFAULT_FREE,}, + 0, 0, + 0 +}; + +static VALUE +ruby_whisper_parakeet_model_s_allocate(VALUE klass) +{ + ruby_whisper_parakeet_model *rwpm; + VALUE model = TypedData_Make_Struct(klass, ruby_whisper_parakeet_model, &ruby_whisper_parakeet_model_type, rwpm); + rwpm->context = Qnil; + + return model; +} + +VALUE +ruby_whisper_parakeet_model_s_new(VALUE context) +{ + const VALUE model = ruby_whisper_parakeet_model_s_allocate(cParakeetModel); + ruby_whisper_parakeet_model *rwpm; + TypedData_Get_Struct(model, ruby_whisper_parakeet_model, &ruby_whisper_parakeet_model_type, rwpm); + rwpm->context = context; + return model; +} + +#define DEF_ATTR(name) \ + static VALUE \ + ruby_whisper_parakeet_model_get_##name(VALUE self) \ + { \ + ruby_whisper_parakeet_model *rwpm; \ + GetParakeetModel(self, rwpm); \ + ruby_whisper_parakeet_context *rwpc; \ + GetParakeetContext(rwpm->context, rwpc); \ + return INT2NUM(parakeet_model_##name(rwpc->context)); \ + } + +ITERATE_ATTRS(DEF_ATTR) + +void +init_ruby_whisper_parakeet_model(VALUE *mParakeet) +{ + cParakeetModel = rb_define_class_under(*mParakeet, "Model", rb_cObject); + + rb_define_alloc_func(cParakeetModel, ruby_whisper_parakeet_model_s_allocate); + +#define REGISTER_ATTR(name) \ + rb_define_method(cParakeetModel, #name, ruby_whisper_parakeet_model_get_##name, 0); + + ITERATE_ATTRS(REGISTER_ATTR) +} From 5937d8c2b0b039d2c7ab7e85d4bb8655e6173871 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 28 May 2026 22:29:57 +0900 Subject: [PATCH 072/118] Add test for Parakeet::Model --- bindings/ruby/test/test_parakeet_model.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 bindings/ruby/test/test_parakeet_model.rb diff --git a/bindings/ruby/test/test_parakeet_model.rb b/bindings/ruby/test/test_parakeet_model.rb new file mode 100644 index 00000000000..17cd8ac3c75 --- /dev/null +++ b/bindings/ruby/test/test_parakeet_model.rb @@ -0,0 +1,21 @@ +require_relative "helper" + +class TestModel < TestBase + def test_model + parakeet = Parakeet::Context.new("test/fixtures/for-tests-ggml-parakeet-tdt.bin") + assert_instance_of Parakeet::Model, parakeet.model + end + + def test_attributes + parakeet = Parakeet::Context.new("test/fixtures/for-tests-ggml-parakeet-tdt.bin") + model = parakeet.model + + assert_equal 10, model.n_vocab + assert_equal 3200, model.n_audio_ctx + assert_equal 8, model.n_audio_state + assert_equal 2, model.n_audio_head + assert_equal 1, model.n_audio_layer + assert_equal 16, model.n_mels + assert_equal 0, model.ftype + end +end From fc561e8f1d63507435a116f350747c1d71698145 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 28 May 2026 22:33:53 +0900 Subject: [PATCH 073/118] Add Parakeet::Model to RBS --- bindings/ruby/sig/whisper.rbs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bindings/ruby/sig/whisper.rbs b/bindings/ruby/sig/whisper.rbs index 0cba0cc51d3..1b73ea9dc65 100644 --- a/bindings/ruby/sig/whisper.rbs +++ b/bindings/ruby/sig/whisper.rbs @@ -781,6 +781,16 @@ module Whisper def deconstruct_keys: (Array[:id | :duration_idx | :duration_value | :frame_index | :probability | :log_probability | :start_time | :end_time | :word_start | :text] | nil) -> deconstructed_keys end + + class Model + def n_vocab: () -> Integer + def n_audio_ctx: () -> Integer + def n_audio_state: () -> Integer + def n_audio_head: () -> Integer + def n_audio_layer: () -> Integer + def n_mels: () -> Integer + def ftype: () -> Integer + end end class Model From 351e8c3736e026e7342f8c59a1437806e4189718 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 28 May 2026 22:34:58 +0900 Subject: [PATCH 074/118] Move position of Parakeet::Model in RBS --- bindings/ruby/sig/whisper.rbs | 306 +++++++++++++++++----------------- 1 file changed, 153 insertions(+), 153 deletions(-) diff --git a/bindings/ruby/sig/whisper.rbs b/bindings/ruby/sig/whisper.rbs index 1b73ea9dc65..75aec6cf926 100644 --- a/bindings/ruby/sig/whisper.rbs +++ b/bindings/ruby/sig/whisper.rbs @@ -458,6 +458,159 @@ module Whisper module LogSettable end + class Model + def self.pre_converted_models: () -> Hash[String, Model::URI] + def self.coreml_compiled_models: () -> Hash[Model::URI, Model::ZipURI] + def self.new: () -> instance + def n_vocab: () -> Integer + def n_audio_ctx: () -> Integer + def n_audio_state: () -> Integer + def n_audio_head: () -> Integer + def n_audio_layer: () -> Integer + def n_text_ctx: () -> Integer + def n_text_state: () -> Integer + def n_text_head: () -> Integer + def n_text_layer: () -> Integer + def n_mels: () -> Integer + def ftype: () -> Integer + def type: () -> String + + class URI + def self.new: (string | ::URI::HTTP) -> instance + def to_path: -> String + def clear_cache: -> void + end + + class ZipURI < URI + def cache: () -> Pathname + def clear_cache: () -> void + end + end + + class Segment + include Output::Segment + + type deconstructed_keys = { + start_time: (Integer | nil), + end_time: (Integer | nil), + text: (String | nil), + no_speech_prob: (Float | nil), + speaker_turn_next: (true | false | nil), + n_tokens: (Integer | nil) + } + + # Start time in milliseconds. + # + def start_time: () -> Integer + + # End time in milliseconds. + # + def end_time: () -> Integer + + # Whether the next segment is predicted as a speaker turn. + # + def speaker_turn_next?: () -> (true | false) + + def text: () -> String + def no_speech_prob: () -> Float + + # Get number of tokens in the segment + # + def n_tokens: () -> Integer + + # Yields each Whisper::Token: + # + # whisper.each_segment.first.each_token do |token| + # p token + # end + # + # Returns an `Enumerator` if no block is given: + # + # whisper.each_segment.first.each_token.to_a # => [#, ...] + # + def each_token: { (Token) -> void } -> void + | () -> Enumerator[Token] + + # Possible keys: `:start_time`, `:end_time`, `:text`, `:no_speech_prob`, `:speaker_turn_next` + # + # whisper.each_segment do |segment| + # segment => {start_time:, end_time:, text:, no_speech_prob:, speaker_turn_next:} + # + # puts "[#{start_time} --> #{end_time}] #{text} (no speech prob: #{no_speech_prob}#{speaker_turn_next ? ', speaker turns next' : ''})" + # end + def deconstruct_keys: (Array[:start_time | :end_time | :text | :no_speech_prob | :speaker_turn_next | :n_tokens] | nil) -> deconstructed_keys + end + + class Token + type deconstructed_keys = { + id: (Integer | nil), + tid: (Integer | nil), + probability: (Float | nil), + log_probability: (Float | nil), + pt: (Float | nil), + ptsum: (Float | nil), + t_dtw: (Integer | nil), + voice_length: (Float | nil), + text: (String | nil), + start_time: (Integer | nil), + end_time: (Integer | nil), + } + + # Token ID. + # + def id: () -> Integer + + # Forced timestamp token ID. + # + def tid: () -> Integer + + # Probability of the token. + # + def probability: () -> Float + + # Log probability of the token. + # + def log_probability: () -> Float + + # Probability of the timestamp token. + # + def pt: () -> Float + + # Sum of probability of all timestamp tokens. + # + def ptsum: () -> Float + + # [EXPERIMENTAL] Token-level timestamps with DTW + # + # Do not use if you haven't computed token-level timestamps with dtw. + # Roughly corresponds to the moment in audio in which the token was output. + # + def t_dtw: () -> Integer + + # Voice length of the token. + # + def voice_length: () -> Float + + # Start time of the token. + # + # Token-level timestamp data. + # Do not use if you haven't computed token-level timestamps. + # + def start_time: () -> Integer + + # End time of the token. + # + # Token-level timestamp data. + # Do not use if you haven't computed token-level timestamps. + # + def end_time: () -> Integer + + # Get the token text of the token. + # + def text: () -> String + def deconstruct_keys: (Array[:id | :tid | :probability | :log_probability | :pt | :ptsum | :t_dtw | :voice_length | :start_time | :end_time | :text] | nil) -> deconstructed_keys + end + module Parakeet extend LogSettable @@ -793,159 +946,6 @@ module Whisper end end - class Model - def self.pre_converted_models: () -> Hash[String, Model::URI] - def self.coreml_compiled_models: () -> Hash[Model::URI, Model::ZipURI] - def self.new: () -> instance - def n_vocab: () -> Integer - def n_audio_ctx: () -> Integer - def n_audio_state: () -> Integer - def n_audio_head: () -> Integer - def n_audio_layer: () -> Integer - def n_text_ctx: () -> Integer - def n_text_state: () -> Integer - def n_text_head: () -> Integer - def n_text_layer: () -> Integer - def n_mels: () -> Integer - def ftype: () -> Integer - def type: () -> String - - class URI - def self.new: (string | ::URI::HTTP) -> instance - def to_path: -> String - def clear_cache: -> void - end - - class ZipURI < URI - def cache: () -> Pathname - def clear_cache: () -> void - end - end - - class Segment - include Output::Segment - - type deconstructed_keys = { - start_time: (Integer | nil), - end_time: (Integer | nil), - text: (String | nil), - no_speech_prob: (Float | nil), - speaker_turn_next: (true | false | nil), - n_tokens: (Integer | nil) - } - - # Start time in milliseconds. - # - def start_time: () -> Integer - - # End time in milliseconds. - # - def end_time: () -> Integer - - # Whether the next segment is predicted as a speaker turn. - # - def speaker_turn_next?: () -> (true | false) - - def text: () -> String - def no_speech_prob: () -> Float - - # Get number of tokens in the segment - # - def n_tokens: () -> Integer - - # Yields each Whisper::Token: - # - # whisper.each_segment.first.each_token do |token| - # p token - # end - # - # Returns an `Enumerator` if no block is given: - # - # whisper.each_segment.first.each_token.to_a # => [#, ...] - # - def each_token: { (Token) -> void } -> void - | () -> Enumerator[Token] - - # Possible keys: `:start_time`, `:end_time`, `:text`, `:no_speech_prob`, `:speaker_turn_next` - # - # whisper.each_segment do |segment| - # segment => {start_time:, end_time:, text:, no_speech_prob:, speaker_turn_next:} - # - # puts "[#{start_time} --> #{end_time}] #{text} (no speech prob: #{no_speech_prob}#{speaker_turn_next ? ', speaker turns next' : ''})" - # end - def deconstruct_keys: (Array[:start_time | :end_time | :text | :no_speech_prob | :speaker_turn_next | :n_tokens] | nil) -> deconstructed_keys - end - - class Token - type deconstructed_keys = { - id: (Integer | nil), - tid: (Integer | nil), - probability: (Float | nil), - log_probability: (Float | nil), - pt: (Float | nil), - ptsum: (Float | nil), - t_dtw: (Integer | nil), - voice_length: (Float | nil), - text: (String | nil), - start_time: (Integer | nil), - end_time: (Integer | nil), - } - - # Token ID. - # - def id: () -> Integer - - # Forced timestamp token ID. - # - def tid: () -> Integer - - # Probability of the token. - # - def probability: () -> Float - - # Log probability of the token. - # - def log_probability: () -> Float - - # Probability of the timestamp token. - # - def pt: () -> Float - - # Sum of probability of all timestamp tokens. - # - def ptsum: () -> Float - - # [EXPERIMENTAL] Token-level timestamps with DTW - # - # Do not use if you haven't computed token-level timestamps with dtw. - # Roughly corresponds to the moment in audio in which the token was output. - # - def t_dtw: () -> Integer - - # Voice length of the token. - # - def voice_length: () -> Float - - # Start time of the token. - # - # Token-level timestamp data. - # Do not use if you haven't computed token-level timestamps. - # - def start_time: () -> Integer - - # End time of the token. - # - # Token-level timestamp data. - # Do not use if you haven't computed token-level timestamps. - # - def end_time: () -> Integer - - # Get the token text of the token. - # - def text: () -> String - def deconstruct_keys: (Array[:id | :tid | :probability | :log_probability | :pt | :ptsum | :t_dtw | :voice_length | :start_time | :end_time | :text] | nil) -> deconstructed_keys - end - module VAD class Params def self.new: ( From 15148c76198bf2e6694a8b2b24854277865586e7 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 28 May 2026 22:40:27 +0900 Subject: [PATCH 075/118] Parakeet -> TestBase::Parakeet --- bindings/ruby/test/helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/ruby/test/helper.rb b/bindings/ruby/test/helper.rb index 6dc4b6a319b..5e37ad98596 100644 --- a/bindings/ruby/test/helper.rb +++ b/bindings/ruby/test/helper.rb @@ -2,11 +2,11 @@ require "whisper" require_relative "jfk_reader/jfk_reader" -Parakeet = Whisper::Parakeet - class TestBase < Test::Unit::TestCase AUDIO = File.join(__dir__, "fixtures", "jfk.wav") + Parakeet = Whisper::Parakeet + class << self def whisper return @whisper if @whisper From 6778df2d626a6b939a33e4118de4f913c81e2cdf Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 28 May 2026 22:40:45 +0900 Subject: [PATCH 076/118] Add Parakeet::Context#model in RBS --- bindings/ruby/sig/whisper.rbs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bindings/ruby/sig/whisper.rbs b/bindings/ruby/sig/whisper.rbs index 75aec6cf926..1e2befc0af8 100644 --- a/bindings/ruby/sig/whisper.rbs +++ b/bindings/ruby/sig/whisper.rbs @@ -681,6 +681,8 @@ module Whisper # def full_get_token_data: (Integer segment_index, Integer token_index) -> Token + def model: () -> Model + # Yields each Whisper::Parakeet::Segment: # # parakeet.transcribe("path/to/audio.wav", params) From 3a8e4279b7fb21e7ceac0bc06787fda21d6b915f Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 28 May 2026 22:42:07 +0900 Subject: [PATCH 077/118] Add Whisper::Output --- bindings/ruby/lib/whisper/output.rb | 74 +++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 bindings/ruby/lib/whisper/output.rb diff --git a/bindings/ruby/lib/whisper/output.rb b/bindings/ruby/lib/whisper/output.rb new file mode 100644 index 00000000000..1781af17a33 --- /dev/null +++ b/bindings/ruby/lib/whisper/output.rb @@ -0,0 +1,74 @@ +module Whisper + module Output + module Context + def to_srt + each_segment.with_index.reduce("") {|srt, (segment, index)| + srt << "#{index + 1}\n#{segment.to_srt_cue}\n" + } + end + + def to_webvtt + each_segment.with_index.reduce("WEBVTT\n\n") {|webvtt, (segment, index)| + webvtt << "#{index + 1}\n#{segment.to_webvtt_cue}\n" + } + end + end + + module Segment + SRT_ESCAPES = { + "&" => "&", + "<" => "<", + ">" => ">", + } + SRT_ESCAPES_RE = Regexp.union(SRT_ESCAPES.keys) + private_constant :SRT_ESCAPES, :SRT_ESCAPES_RE + + def to_srt_cue + "#{srt_start_time} --> #{srt_end_time}\n#{srt_text}\n" + end + + def to_webvtt_cue + "#{webvtt_start_time} --> #{webvtt_end_time}\n#{webvtt_text}\n" + end + + private + + def time_to_a(time) + sec, decimal_part = time.divmod(1000) + min, sec = sec.divmod(60) + hour, min = min.divmod(60) + [hour, min, sec, decimal_part] + end + + def srt_time(time) + "%02d:%02d:%02d,%03d" % time_to_a(time) + end + + def srt_start_time + srt_time(start_time) + end + + def srt_end_time + srt_time(end_time) + end + + def srt_text + text.gsub(SRT_ESCAPES_RE, SRT_ESCAPES) + end + + def webvtt_time(time) + "%02d:%02d:%02d.%03d" % time_to_a(time) + end + + def webvtt_start_time + webvtt_time(start_time) + end + + def webvtt_end_time + webvtt_time(end_time) + end + + alias webvtt_text srt_text + end + end +end From e379e85b918ff9075678e78a2b4cc0a86e0caeb7 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 28 May 2026 23:00:46 +0900 Subject: [PATCH 078/118] Fix nil check --- bindings/ruby/ext/ruby_whisper_parakeet_model.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_model.c b/bindings/ruby/ext/ruby_whisper_parakeet_model.c index cc43ab917aa..658ad8b7e1a 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_model.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_model.c @@ -16,7 +16,7 @@ static void ruby_whisper_parakeet_model_mark(void *p) { ruby_whisper_parakeet_model *rwpm = (ruby_whisper_parakeet_model *)p; - if (rwpm->context) { + if (!NIL_P(rwpm->context)) { rb_gc_mark(rwpm->context); } } From 17bf01c950380142a0b06c0b9a3ca65acad20162 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 28 May 2026 23:03:22 +0900 Subject: [PATCH 079/118] Define ruby_whisper_parakeet_model_memsize --- bindings/ruby/ext/ruby_whisper_parakeet_model.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_model.c b/bindings/ruby/ext/ruby_whisper_parakeet_model.c index 658ad8b7e1a..6e652b7a88b 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_model.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_model.c @@ -21,9 +21,18 @@ ruby_whisper_parakeet_model_mark(void *p) } } +static size_t +ruby_whisper_parakeet_model_memsize(const void *p) +{ + if (!p) { + return 0; + } + return sizeof(ruby_whisper_parakeet_model); +} + static const rb_data_type_t ruby_whisper_parakeet_model_type = { "ruby_whisper_parakeet_model", - {ruby_whisper_parakeet_model_mark, RUBY_DEFAULT_FREE,}, + {ruby_whisper_parakeet_model_mark, RUBY_DEFAULT_FREE, ruby_whisper_parakeet_model_memsize}, 0, 0, 0 }; From 4f7ab94233d0420cd0e712e925ed4094bb790ad9 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Thu, 28 May 2026 23:04:34 +0900 Subject: [PATCH 080/118] Fix order of declaration in ruby_whisper_parakeet_model_get_xxx --- bindings/ruby/ext/ruby_whisper_parakeet_model.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_model.c b/bindings/ruby/ext/ruby_whisper_parakeet_model.c index 6e652b7a88b..dce43c688e7 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_model.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_model.c @@ -62,8 +62,8 @@ ruby_whisper_parakeet_model_s_new(VALUE context) ruby_whisper_parakeet_model_get_##name(VALUE self) \ { \ ruby_whisper_parakeet_model *rwpm; \ - GetParakeetModel(self, rwpm); \ ruby_whisper_parakeet_context *rwpc; \ + GetParakeetModel(self, rwpm); \ GetParakeetContext(rwpm->context, rwpc); \ return INT2NUM(parakeet_model_##name(rwpc->context)); \ } From 07dabf2e8329349e6a5ddb0cef9d338ec1940c80 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 29 May 2026 19:11:33 +0900 Subject: [PATCH 081/118] Define Parakeet.system_info_str --- bindings/ruby/ext/ruby_whisper_parakeet.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet.c b/bindings/ruby/ext/ruby_whisper_parakeet.c index af0ae658599..35c8d85f991 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet.c @@ -56,6 +56,12 @@ ruby_whisper_parakeet_s_log_set(VALUE self, VALUE log_callback, VALUE user_data) return Qnil; } +static VALUE +ruby_whisper_parakeet_s_system_info_str(VALUE self) +{ + return rb_str_new2(parakeet_print_system_info()); +} + static void ruby_whisper_parakeet_end_proc(VALUE args) { @@ -75,6 +81,7 @@ init_ruby_whisper_parakeet(VALUE *mWhisper) ruby_whisper_log_queue_initialize(¶keet_log_queue); rb_define_singleton_method(mParakeet, "log_set", ruby_whisper_parakeet_s_log_set, 2); + rb_define_singleton_method(mParakeet, "system_info_str", ruby_whisper_parakeet_s_system_info_str, 0); rb_define_private_method(rb_singleton_class(mParakeet), "drain_logs", ruby_whisper_parakeet_s_drain_logs, 0); rb_set_end_proc(ruby_whisper_parakeet_end_proc, Qnil); From aadd3682bca7124d6c72ef5d0926a7b1c1342a69 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 29 May 2026 19:11:40 +0900 Subject: [PATCH 082/118] Add test for Parakeet.system_info_str --- bindings/ruby/test/test_parakeet.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bindings/ruby/test/test_parakeet.rb b/bindings/ruby/test/test_parakeet.rb index cc656dc90d0..5a1a47872e9 100644 --- a/bindings/ruby/test/test_parakeet.rb +++ b/bindings/ruby/test/test_parakeet.rb @@ -17,4 +17,8 @@ def test_log_set $stdout = STDOUT Parakeet.log_set log_callback, user_data end + + def test_system_info_str + assert_match /\APARAKEET : MTL : EMBED_LIBRARY = \d \| CPU : NEON = \d \|/, Parakeet.system_info_str + end end From ff7fbfa19f96e59ef1779ac8823f1cc1e969aa38 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 29 May 2026 19:13:07 +0900 Subject: [PATCH 083/118] Add signature of Parakeet.system_info_str --- bindings/ruby/sig/whisper.rbs | 1 + 1 file changed, 1 insertion(+) diff --git a/bindings/ruby/sig/whisper.rbs b/bindings/ruby/sig/whisper.rbs index 1e2befc0af8..a05b2d1da0c 100644 --- a/bindings/ruby/sig/whisper.rbs +++ b/bindings/ruby/sig/whisper.rbs @@ -618,6 +618,7 @@ module Whisper # def self.log_set: (nil, Object? user_data) -> nil | (^(Integer level, String message, Object user_data) -> void, Object? user_data) -> nil + def self.system_info_str: () -> String class Context include Output::Context From 64ad43529122b2f9b1b18a7da45fe435557a7332 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 29 May 2026 19:15:03 +0900 Subject: [PATCH 084/118] Define Parakeet::VERSION --- bindings/ruby/ext/ruby_whisper_parakeet.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet.c b/bindings/ruby/ext/ruby_whisper_parakeet.c index 35c8d85f991..cd063fa9f2d 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet.c @@ -78,6 +78,8 @@ init_ruby_whisper_parakeet(VALUE *mWhisper) { mParakeet = rb_define_module_under(*mWhisper, "Parakeet"); + rb_define_const(mParakeet, "VERSION", rb_str_new2(parakeet_version())); + ruby_whisper_log_queue_initialize(¶keet_log_queue); rb_define_singleton_method(mParakeet, "log_set", ruby_whisper_parakeet_s_log_set, 2); From b31e4817802c188b1825fa34bc7bc6450f84893e Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 29 May 2026 19:15:16 +0900 Subject: [PATCH 085/118] Add test for Parakeet::VERSION --- bindings/ruby/test/test_parakeet.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bindings/ruby/test/test_parakeet.rb b/bindings/ruby/test/test_parakeet.rb index 5a1a47872e9..f2a71267c1a 100644 --- a/bindings/ruby/test/test_parakeet.rb +++ b/bindings/ruby/test/test_parakeet.rb @@ -21,4 +21,8 @@ def test_log_set def test_system_info_str assert_match /\APARAKEET : MTL : EMBED_LIBRARY = \d \| CPU : NEON = \d \|/, Parakeet.system_info_str end + + def test_version + assert_instance_of String, Parakeet::VERSION + end end From ada170a78637a507d00fee60a0b1f5cc52cad1bc Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 29 May 2026 19:16:47 +0900 Subject: [PATCH 086/118] Add signature of Parakeet::VERSION --- bindings/ruby/sig/whisper.rbs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bindings/ruby/sig/whisper.rbs b/bindings/ruby/sig/whisper.rbs index a05b2d1da0c..e14d6d98bc9 100644 --- a/bindings/ruby/sig/whisper.rbs +++ b/bindings/ruby/sig/whisper.rbs @@ -614,6 +614,8 @@ module Whisper module Parakeet extend LogSettable + VERSION: String + # Control logging output. The default behavior is to print to stderr. # def self.log_set: (nil, Object? user_data) -> nil From c6e854b3845a1acd97aaa5bc90edd2a7350ee772 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 29 May 2026 21:17:08 +0900 Subject: [PATCH 087/118] Add Parakeet::Context::Params --- bindings/ruby/ext/ruby_whisper.c | 1 + bindings/ruby/ext/ruby_whisper.h | 8 ++ bindings/ruby/ext/ruby_whisper_parakeet.c | 6 +- .../ruby/ext/ruby_whisper_parakeet_context.c | 4 +- .../ruby_whisper_parakeet_context_params.c | 117 ++++++++++++++++++ bindings/ruby/sig/whisper.rbs | 8 ++ .../ruby/test/test_parakeet_context_params.rb | 24 ++++ 7 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 bindings/ruby/ext/ruby_whisper_parakeet_context_params.c create mode 100644 bindings/ruby/test/test_parakeet_context_params.rb diff --git a/bindings/ruby/ext/ruby_whisper.c b/bindings/ruby/ext/ruby_whisper.c index b669e8197d3..70b637df65a 100644 --- a/bindings/ruby/ext/ruby_whisper.c +++ b/bindings/ruby/ext/ruby_whisper.c @@ -11,6 +11,7 @@ VALUE cVADParams; VALUE cVADSegments; VALUE cVADSegment; VALUE cParakeetContext; +VALUE cParakeetContextParams; VALUE cParakeetParams; VALUE cParakeetSegment; VALUE cParakeetModel; diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index 9fe3743e4d8..faed71190af 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -126,6 +126,10 @@ typedef struct { ruby_whisper_callback_container *abort_callback_container; } ruby_whisper_parakeet_params; +typedef struct { + struct parakeet_context_params params; +} ruby_whisper_parakeet_context_params; + typedef struct { struct parakeet_context *context; } ruby_whisper_parakeet_context; @@ -180,6 +184,10 @@ typedef struct { } \ } while (0) +#define GetParakeetContextParams(obj, rwpcp) do { \ + TypedData_Get_Struct((obj), ruby_whisper_parakeet_context_params, &ruby_whisper_parakeet_context_params_type, (rwpcp)); \ +} while (0) + #define GetParakeetContext(obj, rwpc) do { \ TypedData_Get_Struct((obj), ruby_whisper_parakeet_context, &ruby_whisper_parakeet_context_type, (rwpc)); \ if ((rwpc)->context == NULL) { \ diff --git a/bindings/ruby/ext/ruby_whisper_parakeet.c b/bindings/ruby/ext/ruby_whisper_parakeet.c index cd063fa9f2d..6d31cee62c4 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet.c @@ -17,7 +17,8 @@ extern ID id_join; extern void init_ruby_whisper_parakeet_params(VALUE *mParakeet); extern void init_ruby_whisper_parakeet_token(VALUE *mParakeet); extern void init_ruby_whisper_parakeet_segment(VALUE *mParakeet); -extern void init_ruby_whisper_parakeet_context(VALUE *mParakeet); +extern VALUE init_ruby_whisper_parakeet_context(VALUE *mParakeet); +extern void init_ruby_whisper_parakeet_context_params(VALUE *cParakeetContext); extern void init_ruby_whisper_parakeet_model(VALUE *mParakeet); extern void ruby_whisper_log_queue_initialize(ruby_whisper_log_queue *log_queue); @@ -93,7 +94,8 @@ init_ruby_whisper_parakeet(VALUE *mWhisper) init_ruby_whisper_parakeet_params(&mParakeet); init_ruby_whisper_parakeet_token(&mParakeet); init_ruby_whisper_parakeet_segment(&mParakeet); - init_ruby_whisper_parakeet_context(&mParakeet); + cParakeetContext = init_ruby_whisper_parakeet_context(&mParakeet); + init_ruby_whisper_parakeet_context_params(&cParakeetContext); init_ruby_whisper_parakeet_model(&mParakeet); rb_include_module(cParakeetContext, mOutputContext); diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_context.c b/bindings/ruby/ext/ruby_whisper_parakeet_context.c index a7f0d7a75b4..e8fd1934a7c 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_context.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_context.c @@ -263,7 +263,7 @@ ruby_whisper_parakeet_context_get_model(VALUE self) return ruby_whisper_parakeet_model_s_new(self); } -void +VALUE init_ruby_whisper_parakeet_context(VALUE *mParakeet) { cParakeetContext = rb_define_class_under(*mParakeet, "Context", rb_cObject); @@ -287,4 +287,6 @@ init_ruby_whisper_parakeet_context(VALUE *mParakeet) rb_define_method(cParakeetContext, "full_" #name, ruby_whisper_parakeet_context_full_##name, 2); ITERATE_TOKEN_ATTRS(REGISTER_TOKEN_ATTR) + + return cParakeetContext; } diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_context_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_context_params.c new file mode 100644 index 00000000000..38bd6d57ce1 --- /dev/null +++ b/bindings/ruby/ext/ruby_whisper_parakeet_context_params.c @@ -0,0 +1,117 @@ +#include "ruby_whisper.h" + +#define ITERATE_ATTRS(ITERATOR) \ + ITERATOR(use_gpu, BOOL) \ + ITERATOR(gpu_device, INT) + +#define VAL_FROM_BOOL(v) ((v) ? Qtrue : Qfalse) +#define VAL_TO_BOOL(v) (RTEST(v)) +#define VAL_FROM_INT(v) (INT2NUM(v)) +#define VAL_TO_INT(v) (NUM2INT(v)) +#define READER(type) VAL_FROM_##type +#define WRITER(type) VAL_TO_##type + +#define DEF_ATTR(name, type) \ + static VALUE \ + ruby_whisper_parakeet_context_params_get_##name(VALUE self) \ + { \ + ruby_whisper_parakeet_context_params *rwpcp; \ + GetParakeetContextParams(self, rwpcp); \ + return READER(type)(rwpcp->params.name); \ + } \ + static VALUE \ + ruby_whisper_parakeet_context_params_set_##name(VALUE self, VALUE val) \ + { \ + ruby_whisper_parakeet_context_params *rwpcp; \ + GetParakeetContextParams(self, rwpcp); \ + rwpcp->params.name = WRITER(type)(val); \ + return val; \ + } + +enum { +#define DEF_IDX(name, type) RUBY_WHISPER_PARAKEET_CONTEXT_PARAMS_##name, + + ITERATE_ATTRS(DEF_IDX) + RUBY_WHISPER_PARAKEET_NUM_CONTEXT_PARAMS +}; + +extern VALUE cParakeetContextParams; + +typedef VALUE (*param_writer_t)(VALUE, VALUE); + +static ID param_names[RUBY_WHISPER_PARAKEET_NUM_CONTEXT_PARAMS]; +static param_writer_t param_writers[RUBY_WHISPER_PARAKEET_NUM_CONTEXT_PARAMS]; + +static size_t +ruby_whisper_parakeet_context_params_memsize(const void *p) +{ + if (!p) { + return 0; + } + return sizeof(ruby_whisper_parakeet_context_params); +} + +const rb_data_type_t ruby_whisper_parakeet_context_params_type = { + "ruby_whisper_parakeet_context_params", + {0, RUBY_DEFAULT_FREE, ruby_whisper_parakeet_context_params_memsize,}, + 0, 0, + 0, +}; + +static VALUE +ruby_whisper_parakeet_context_params_s_allocate(VALUE klass) +{ + ruby_whisper_parakeet_context_params *rwpcp; + return TypedData_Make_Struct(klass, ruby_whisper_parakeet_context_params, &ruby_whisper_parakeet_context_params_type, rwpcp); +} + +static VALUE +ruby_whisper_parakeet_context_params_initialize(int argc, VALUE *argv, VALUE self) +{ + VALUE kw_hash; + VALUE values[RUBY_WHISPER_PARAKEET_NUM_CONTEXT_PARAMS] = {Qundef}; + VALUE value; + ruby_whisper_parakeet_context_params *rwpcp; + int i; + + TypedData_Get_Struct(self, ruby_whisper_parakeet_context_params, &ruby_whisper_parakeet_context_params_type, rwpcp); + rwpcp->params = parakeet_context_default_params(); + + rb_scan_args_kw(RB_SCAN_ARGS_KEYWORDS, argc, argv, ":", &kw_hash); + if (NIL_P(kw_hash)) { + return Qnil; + } + + rb_get_kwargs(kw_hash, param_names, 0, RUBY_WHISPER_PARAKEET_NUM_CONTEXT_PARAMS, values); + for (i = 0; i < RUBY_WHISPER_PARAKEET_NUM_CONTEXT_PARAMS; i++) { + value = values[i]; + if (value == Qundef) { + continue; + } + param_writers[i](self, value); + } + + return Qnil; +} + +ITERATE_ATTRS(DEF_ATTR) + +void +init_ruby_whisper_parakeet_context_params(VALUE *cParakeetContext) +{ + cParakeetContextParams = rb_define_class_under(*cParakeetContext, "Params", rb_cObject); + + rb_define_alloc_func(cParakeetContextParams, ruby_whisper_parakeet_context_params_s_allocate); + + rb_define_method(cParakeetContextParams, "initialize", ruby_whisper_parakeet_context_params_initialize, -1); + + int i = 0; +#define REGISTER_ATTR(name, type) \ + param_names[i] = rb_intern(#name); \ + param_writers[i] = ruby_whisper_parakeet_context_params_set_##name; \ + rb_define_method(cParakeetContextParams, #name, ruby_whisper_parakeet_context_params_get_##name, 0); \ + rb_define_method(cParakeetContextParams, #name "=", ruby_whisper_parakeet_context_params_set_##name, 1); \ + i++; + + ITERATE_ATTRS(REGISTER_ATTR) +} diff --git a/bindings/ruby/sig/whisper.rbs b/bindings/ruby/sig/whisper.rbs index e14d6d98bc9..0ce85961db6 100644 --- a/bindings/ruby/sig/whisper.rbs +++ b/bindings/ruby/sig/whisper.rbs @@ -701,6 +701,14 @@ module Whisper # def each_segment: { (Segment) -> void } -> void | () -> Enumerator[Segment] + + class Params + def self.new: (?use_gpu: boolish, ?gpu_device: Integer) -> instance + def use_gpu: () -> boolish + def use_gpu=: (boolish) -> boolish + def gpu_device: () -> Integer + def gpu_device=: (Integer) -> Integer + end end class Params diff --git a/bindings/ruby/test/test_parakeet_context_params.rb b/bindings/ruby/test/test_parakeet_context_params.rb new file mode 100644 index 00000000000..fcd0f2410f7 --- /dev/null +++ b/bindings/ruby/test/test_parakeet_context_params.rb @@ -0,0 +1,24 @@ +require_relative "helper" + +class TestParakeetContextParams < TestBase + def setup + @params = Parakeet::Context::Params.new + end + + def test_new + assert_instance_of Parakeet::Context::Params, @params + end + + def test_attributes + assert_true @params.use_gpu + assert_instance_of Integer, @params.gpu_device + end + + def test_attribute_writer + @params.use_gpu = false + assert_false @params.use_gpu + + @params.gpu_device = 2 + assert_equal 2, @params.gpu_device + end +end From 9b0213ff73e7bdde3937cbaac185017b79213c8e Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 29 May 2026 21:45:36 +0900 Subject: [PATCH 088/118] Make Parakeet::Context.new accept Context::Params --- .../ruby/ext/ruby_whisper_parakeet_context.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_context.c b/bindings/ruby/ext/ruby_whisper_parakeet_context.c index e8fd1934a7c..bc39e9a5528 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_context.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_context.c @@ -32,6 +32,7 @@ extern parsed_samples_t parse_samples(VALUE *samples, VALUE *n_samples); extern VALUE release_samples(VALUE rb_parsed_args); extern void ruby_whisper_parakeet_prepare_transcription(ruby_whisper_parakeet_params *rwpp, VALUE *context, ruby_whisper_abort_callback_user_data *abort_callback_user_data); extern rb_data_type_t ruby_whisper_parakeet_params_type; +extern rb_data_type_t ruby_whisper_parakeet_context_params_type; extern VALUE ruby_whisper_parakeet_token_s_from_token_data(struct parakeet_context *context, const parakeet_token_data *token_data); extern VALUE ruby_whisper_parakeet_model_s_new(VALUE context); @@ -78,13 +79,14 @@ ruby_whisper_parakeet_context_allocate(VALUE klass) typedef struct { struct parakeet_context **context; char *model_path; + struct parakeet_context_params params; } ruby_whisper_parakeet_context_init_args; static void* ruby_whisper_parakeet_context_init_without_gvl(void *args) { ruby_whisper_parakeet_context_init_args *init_args = (ruby_whisper_parakeet_context_init_args *)args; - *init_args->context = parakeet_init_from_file_with_params(init_args->model_path, parakeet_context_default_params()); + *init_args->context = parakeet_init_from_file_with_params(init_args->model_path, init_args->params); return NULL; } @@ -93,17 +95,27 @@ ruby_whisper_parakeet_context_initialize(int argc, VALUE *argv, VALUE self) { ruby_whisper_parakeet_context *rwpc; VALUE model_path; + VALUE context_params; + struct parakeet_context_params params; - rb_scan_args(argc, argv, "1", &model_path); + rb_scan_args(argc, argv, "11", &model_path, &context_params); TypedData_Get_Struct(self, ruby_whisper_parakeet_context, &ruby_whisper_parakeet_context_type, rwpc); model_path = ruby_whisper_normalize_model_path(model_path); if (!rb_respond_to(model_path, id_to_s)) { rb_raise(rb_eRuntimeError, "Expected file path to model to initialize Parakeet::Context"); } + if (NIL_P(context_params)) { + params = parakeet_context_default_params(); + } else { + ruby_whisper_parakeet_context_params *rwpcp; + GetParakeetContextParams(context_params, rwpcp); + params = rwpcp->params; + } ruby_whisper_parakeet_context_init_args init_args = { &rwpc->context, StringValueCStr(model_path), + params, }; rb_thread_call_without_gvl(ruby_whisper_parakeet_context_init_without_gvl, (void *)&init_args, NULL, NULL); if (rwpc->context == NULL) { From 4468a62ecfb578407216198238582309e9b4de3e Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 29 May 2026 21:45:51 +0900 Subject: [PATCH 089/118] Add test for Parakeet::Context.new with Context::Params --- bindings/ruby/test/test_parakeet_context.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/bindings/ruby/test/test_parakeet_context.rb b/bindings/ruby/test/test_parakeet_context.rb index c5e151b52b7..6aad3a18180 100644 --- a/bindings/ruby/test/test_parakeet_context.rb +++ b/bindings/ruby/test/test_parakeet_context.rb @@ -16,6 +16,21 @@ def test_new assert_instance_of Parakeet::Context, @parakeet end + def test_new_with_params + log_callback = Parakeet.instance_variable_get(:@log_callback) + user_data = Parakeet.instance_variable_get(:@log_callback_user_data) + begin + logs = "" + Parakeet.log_set proc {|level, message| logs << message}, nil + params = Parakeet::Context::Params.new(use_gpu: false) + parakeet = Parakeet::Context.new("parakeet-tdt-0.6b-v3", params) + assert_instance_of Parakeet::Context, parakeet + assert_match /use gpu\s+=\s+0/, logs + ensure + Parakeet.log_set log_callback, user_data + end + end + sub_test_case "full" do def setup super From 9029d9ceb3731ea5fef07a3a2e18bf2e0aaa4299 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 29 May 2026 21:47:19 +0900 Subject: [PATCH 090/118] Update RBS --- bindings/ruby/sig/whisper.rbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/ruby/sig/whisper.rbs b/bindings/ruby/sig/whisper.rbs index 0ce85961db6..1074ebee4ea 100644 --- a/bindings/ruby/sig/whisper.rbs +++ b/bindings/ruby/sig/whisper.rbs @@ -627,7 +627,7 @@ module Whisper # Load a Parakeet model from the given file path. # - def self.new: (String | path | ::URI::HTTP) -> instance + def self.new: (String | path | ::URI::HTTP, ?Params) -> instance # Transcribe a single audio file. # From 7172c88d0410052690b1a8c7d8d66d4685f0f4b0 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Tue, 9 Jun 2026 08:44:11 +0900 Subject: [PATCH 091/118] Remove params from Parakeet::Params which are moved from whisper_parakeet_full_params --- bindings/ruby/ext/ruby_whisper_parakeet_params.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_params.c b/bindings/ruby/ext/ruby_whisper_parakeet_params.c index 6d9e7f1e0f7..076e2a0cdfb 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_params.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_params.c @@ -5,10 +5,7 @@ ITERATOR(offset_ms, INT) \ ITERATOR(duration_ms, INT) \ ITERATOR(no_context, BOOL) \ - ITERATOR(audio_ctx, INT) \ - ITERATOR(chunk_length_ms, INT) \ - ITERATOR(left_context_ms, INT) \ - ITERATOR(right_context_ms, INT) + ITERATOR(audio_ctx, INT) #define ITERATE_NORMAL_CALLBACK_NAMES(ITERATOR, DATA) \ ITERATOR(new_segment, DATA) \ From cfd0dd1f0e8f1e0955dfc91e872f159cf366f905 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Tue, 9 Jun 2026 08:49:29 +0900 Subject: [PATCH 092/118] Remove tests for removed params --- bindings/ruby/test/test_parakeet_params.rb | 26 +--------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/bindings/ruby/test/test_parakeet_params.rb b/bindings/ruby/test/test_parakeet_params.rb index f00ca8eb31d..a3ff09d85f1 100644 --- a/bindings/ruby/test/test_parakeet_params.rb +++ b/bindings/ruby/test/test_parakeet_params.rb @@ -6,10 +6,7 @@ class TestParakeetParams < TestBase :offset_ms, :duration_ms, :no_context, - :audio_ctx, - :chunk_length_ms, - :left_context_ms, - :right_context_ms + :audio_ctx ] def setup @@ -55,27 +52,6 @@ def test_audio_ctx assert_equal 1, @params.audio_ctx end - def test_chunk_length_ms - assert_equal 10_000, @params.chunk_length_ms - - @params.chunk_length_ms = 30_000 - assert_equal 30_000, @params.chunk_length_ms - end - - def test_left_context_ms - assert_equal 10_000, @params.left_context_ms - - @params.left_context_ms = 30_000 - assert_equal 30_000, @params.left_context_ms - end - - def test_right_context_ms - assert_equal 4_960, @params.right_context_ms - - @params.right_context_ms = 5_000 - assert_equal 5_000, @params.right_context_ms - end - def test_new_with_kw_args params = Parakeet::Params.new(n_threads: 1) assert_equal 1, params.n_threads From ca3a565fefc093e1a0159beda0591a410dfcddf4 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Tue, 9 Jun 2026 14:42:50 +0900 Subject: [PATCH 093/118] Make Parakeet tests follow original behavior changes --- bindings/ruby/test/test_parakeet_callback.rb | 3 --- bindings/ruby/test/test_parakeet_context.rb | 8 ++++---- bindings/ruby/test/test_parakeet_token.rb | 4 ++-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/bindings/ruby/test/test_parakeet_callback.rb b/bindings/ruby/test/test_parakeet_callback.rb index dfec13799c3..ed04befecb1 100644 --- a/bindings/ruby/test/test_parakeet_callback.rb +++ b/bindings/ruby/test/test_parakeet_callback.rb @@ -67,18 +67,15 @@ def test_on_new_token def test_on_progress first = nil - last = nil @params.on_progress do |progress| assert_kind_of Integer, progress assert 0 <= progress && progress <= 100 first = progress if first.nil? - last = progress end @parakeet.transcribe AUDIO, @params assert_equal 0, first - assert_equal 90, last end def test_on_encoder_begin diff --git a/bindings/ruby/test/test_parakeet_context.rb b/bindings/ruby/test/test_parakeet_context.rb index 6aad3a18180..52d75dce2bc 100644 --- a/bindings/ruby/test/test_parakeet_context.rb +++ b/bindings/ruby/test/test_parakeet_context.rb @@ -41,7 +41,7 @@ def test_full @parakeet.full @params, @samples, @samples.length segments = @parakeet.each_segment.to_a - assert_equal 2, segments.length + assert_equal 1, segments.length assert_match /ask not what your country can do for you, ask what you can do for your/, segments.first.text end @@ -49,7 +49,7 @@ def test_full_without_length @parakeet.full(@params, @samples) segments = @parakeet.each_segment.to_a - assert_equal 2, segments.length + assert_equal 1, segments.length assert_match /ask not what your country can do for you, ask what you can do for your/, @parakeet.each_segment.first.text end @@ -58,7 +58,7 @@ def test_full_enumerator @parakeet.full @params, samples, @samples.length segments = @parakeet.each_segment.to_a - assert_equal 2, segments.length + assert_equal 1, segments.length assert_match /ask not what your country can do for you, ask what you can do for your/, @parakeet.each_segment.first.text end @@ -81,7 +81,7 @@ def test_full_with_memory_view @parakeet.full @params, samples segments = @parakeet.each_segment.to_a - assert_equal 2, segments.length + assert_equal 1, segments.length assert_match /ask not what your country can do for you, ask what you can do for your/, @parakeet.each_segment.first.text end diff --git a/bindings/ruby/test/test_parakeet_token.rb b/bindings/ruby/test/test_parakeet_token.rb index 4e1455662f3..db4751690c4 100644 --- a/bindings/ruby/test/test_parakeet_token.rb +++ b/bindings/ruby/test/test_parakeet_token.rb @@ -32,7 +32,7 @@ def test_each_token i += 1 assert_instance_of Parakeet::Token, token end - assert_equal 33, i + assert_equal 38, i end def test_each_token_without_block @@ -55,7 +55,7 @@ def test_token end def test_text - assert_equal ["▁And", "▁so", ",", "▁my", "▁f", "ell", "ow", "▁Amer", "ic", "ans", ",", "▁a", "sk", "▁not", "▁what", "▁your", "▁co", "un", "tr", "y", "▁can", "▁do", "▁for", "▁you", ",", "▁a", "sk", "▁what", "▁you", "▁can", "▁do", "▁for", "▁your"], + assert_equal ["▁And", "▁so", ",", "▁my", "▁f", "ell", "ow", "▁Amer", "ic", "ans", ",", "▁a", "sk", "▁not", "▁what", "▁your", "▁co", "un", "tr", "y", "▁can", "▁do", "▁for", "▁you", ",", "▁a", "sk", "▁what", "▁you", "▁can", "▁do", "▁for", "▁your", "▁co", "un", "tr", "y", "."], @segment.each_token.collect(&:text) end From 5b2bdc84fc70537185b2ae69e190b1a5ef8ac204 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 15 Jun 2026 04:11:09 +0900 Subject: [PATCH 094/118] Add Parakeet model shortcuts --- bindings/ruby/lib/whisper/model/uri.rb | 8 ++++++-- bindings/ruby/test/test_parakeet_callback.rb | 2 +- bindings/ruby/test/test_parakeet_context.rb | 4 ++-- bindings/ruby/test/test_parakeet_segment.rb | 2 +- bindings/ruby/test/test_parakeet_token.rb | 2 +- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/bindings/ruby/lib/whisper/model/uri.rb b/bindings/ruby/lib/whisper/model/uri.rb index 5cd24ddc5b5..fca750278d1 100644 --- a/bindings/ruby/lib/whisper/model/uri.rb +++ b/bindings/ruby/lib/whisper/model/uri.rb @@ -217,9 +217,13 @@ def escaping(path) end %w[ - parakeet-tdt-0.6b-v3 + parakeet-tdt-0.6b-v3-f16 + parakeet-tdt-0.6b-v3-f32 + parakeet-tdt-0.6b-v3-q4_0 + parakeet-tdt-0.6b-v3-q4_k + parakeet-tdt-0.6b-v3-q8_0 ].each do |name| - @pre_converted_models[name] = URI.new("https://huggingface.co/danbev/parakeet/resolve/main/#{name}.bin") + @pre_converted_models[name] = URI.new("https://huggingface.co/danbev/parakeet-GGUF/resolve/main/ggml-#{name}.bin") end @coreml_compiled_models = @pre_converted_models.each_with_object({}) {|(name, uri), models| diff --git a/bindings/ruby/test/test_parakeet_callback.rb b/bindings/ruby/test/test_parakeet_callback.rb index ed04befecb1..1209e960f09 100644 --- a/bindings/ruby/test/test_parakeet_callback.rb +++ b/bindings/ruby/test/test_parakeet_callback.rb @@ -7,7 +7,7 @@ def setup Whisper.instance_variable_set "@whisper", nil GC.start @params = Parakeet::Params.new - @parakeet = Parakeet::Context.new("parakeet-tdt-0.6b-v3") + @parakeet = Parakeet::Context.new("parakeet-tdt-0.6b-v3-q4_0") end def test_new_segment_callback diff --git a/bindings/ruby/test/test_parakeet_context.rb b/bindings/ruby/test/test_parakeet_context.rb index 52d75dce2bc..2d039ce75f5 100644 --- a/bindings/ruby/test/test_parakeet_context.rb +++ b/bindings/ruby/test/test_parakeet_context.rb @@ -8,7 +8,7 @@ def setup Whisper.instance_variable_set "@whisper", nil GC.start - @parakeet = Parakeet::Context.new("parakeet-tdt-0.6b-v3") + @parakeet = Parakeet::Context.new("parakeet-tdt-0.6b-v3-q4_0") @params = Parakeet::Params.new end @@ -23,7 +23,7 @@ def test_new_with_params logs = "" Parakeet.log_set proc {|level, message| logs << message}, nil params = Parakeet::Context::Params.new(use_gpu: false) - parakeet = Parakeet::Context.new("parakeet-tdt-0.6b-v3", params) + parakeet = Parakeet::Context.new("parakeet-tdt-0.6b-v3-q4_0", params) assert_instance_of Parakeet::Context, parakeet assert_match /use gpu\s+=\s+0/, logs ensure diff --git a/bindings/ruby/test/test_parakeet_segment.rb b/bindings/ruby/test/test_parakeet_segment.rb index 37b5806d932..d5b99bd5ee6 100644 --- a/bindings/ruby/test/test_parakeet_segment.rb +++ b/bindings/ruby/test/test_parakeet_segment.rb @@ -4,7 +4,7 @@ class TestParakeetSegment < TestBase def setup omit "Skip not to download large model" if ENV["CI"] - @parakeet = Parakeet::Context.new("parakeet-tdt-0.6b-v3") + @parakeet = Parakeet::Context.new("parakeet-tdt-0.6b-v3-q4_0") @parakeet.transcribe AUDIO, Parakeet::Params.new end diff --git a/bindings/ruby/test/test_parakeet_token.rb b/bindings/ruby/test/test_parakeet_token.rb index db4751690c4..6f0b8b5a37c 100644 --- a/bindings/ruby/test/test_parakeet_token.rb +++ b/bindings/ruby/test/test_parakeet_token.rb @@ -20,7 +20,7 @@ def setup Whisper.instance_variable_set "@whisper", nil GC.start - parakeet = Parakeet::Context.new("parakeet-tdt-0.6b-v3") + parakeet = Parakeet::Context.new("parakeet-tdt-0.6b-v3-q4_0") params = Parakeet::Params.new parakeet.transcribe AUDIO, params @segment = parakeet.each_segment.first From b655b3ddd4579932d22679fe44a85fadb2a0f749 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 15 Jun 2026 05:01:44 +0900 Subject: [PATCH 095/118] Alloc token data in factory instead of alloc func --- bindings/ruby/ext/ruby_whisper_parakeet_token.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_token.c b/bindings/ruby/ext/ruby_whisper_parakeet_token.c index e1da3e413c1..a00b7ae1cbb 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_token.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_token.c @@ -101,7 +101,7 @@ ruby_whisper_parakeet_token_s_allocate(VALUE klass) ruby_whisper_parakeet_token *rwpt; VALUE token = TypedData_Make_Struct(klass, ruby_whisper_parakeet_token, &ruby_whisper_parakeet_token_type, rwpt); - rwpt->token_data = ALLOC(parakeet_token_data); + rwpt->token_data = NULL; rwpt->text = Qnil; return token; @@ -114,6 +114,7 @@ ruby_whisper_parakeet_token_s_from_token_data(struct parakeet_context *context, ruby_whisper_parakeet_token *rwpt; TypedData_Get_Struct(token, ruby_whisper_parakeet_token, &ruby_whisper_parakeet_token_type, rwpt); + rwpt->token_data = ALLOC(parakeet_token_data); *rwpt->token_data = *token_data; rwpt->text = rb_utf8_str_new_cstr(parakeet_token_to_str(context, token_data->id)); From ee1e22a42dd6efead4ef927e7a760a0841132012 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 29 May 2026 03:07:26 +0900 Subject: [PATCH 096/118] Fix variable name --- bindings/ruby/test/test_vad_segment.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/ruby/test/test_vad_segment.rb b/bindings/ruby/test/test_vad_segment.rb index 7348562cb15..6d66c27fd32 100644 --- a/bindings/ruby/test/test_vad_segment.rb +++ b/bindings/ruby/test/test_vad_segment.rb @@ -9,7 +9,7 @@ def test_initialize end assert_raise do - segments.end_time + segment.end_time end assert_raise do From eb2f81076482ba74e565a721eac923a9bdeded5b Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 29 May 2026 03:07:29 +0900 Subject: [PATCH 097/118] Update RBS --- bindings/ruby/sig/whisper.rbs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bindings/ruby/sig/whisper.rbs b/bindings/ruby/sig/whisper.rbs index 1074ebee4ea..053e705976a 100644 --- a/bindings/ruby/sig/whisper.rbs +++ b/bindings/ruby/sig/whisper.rbs @@ -155,12 +155,12 @@ module Whisper class Params def self.new: ( - use_gpu: boolish, - flash_attn: boolish, - gpu_device: Integer, - dtw_token_timestamps: boolish, - dtw_aheads_preset: Integer, - dtw_n_top: Integer | nil, + ?use_gpu: boolish, + ?flash_attn: boolish, + ?gpu_device: Integer, + ?dtw_token_timestamps: boolish, + ?dtw_aheads_preset: Integer, + ?dtw_n_top: Integer | nil, ) -> instance def use_gpu=: (boolish) -> boolish From 318f85ac5f9a76d6cd081e76453f5351544fd4dc Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sun, 31 May 2026 20:54:39 +0900 Subject: [PATCH 098/118] Refactor log settable module --- bindings/ruby/ext/ruby_whisper.c | 4 +- bindings/ruby/ext/ruby_whisper.h | 12 ++++ bindings/ruby/ext/ruby_whisper_log_settable.h | 47 +++++++++++++++ bindings/ruby/ext/ruby_whisper_parakeet.c | 58 +------------------ 4 files changed, 63 insertions(+), 58 deletions(-) create mode 100644 bindings/ruby/ext/ruby_whisper_log_settable.h diff --git a/bindings/ruby/ext/ruby_whisper.c b/bindings/ruby/ext/ruby_whisper.c index 70b637df65a..627ecc5232d 100644 --- a/bindings/ruby/ext/ruby_whisper.c +++ b/bindings/ruby/ext/ruby_whisper.c @@ -40,7 +40,7 @@ ID id_n_processors; ID id_extended; ID id_start_log_callback_thread; ID id_log_callback_thread; -ID id_alive; +ID id_alive_p; ID id_join; static bool is_log_callback_finalized = false; @@ -208,7 +208,7 @@ void Init_whisper() { 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 = rb_intern("alive?"); + id_alive_p = rb_intern("alive?"); id_join = rb_intern("join"); mWhisper = rb_define_module("Whisper"); diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index faed71190af..094f1fce919 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -10,6 +10,7 @@ #include #include "whisper.h" #include "parakeet.h" +#include "ruby_whisper_log_settable.h" #if RUBY_API_VERSION_MAJOR < 4 // Exists but not declared as public API @@ -148,6 +149,17 @@ 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; +extern ID id_join; +extern void ruby_whisper_log_queue_initialize(ruby_whisper_log_queue *log_queue); +extern void ruby_whisper_log_queue_open(ruby_whisper_log_queue *log_queue); +extern void ruby_whisper_log_queue_close(ruby_whisper_log_queue *log_queue); +extern void ruby_whisper_log_queue_enqueue(ruby_whisper_log_queue *log_queue, enum ggml_log_level level, const char *text); +extern VALUE ruby_whisper_log_queue_drain(ruby_whisper_log_queue *log_queue); + #define GetContext(obj, rw) do { \ TypedData_Get_Struct((obj), ruby_whisper, &ruby_whisper_type, (rw)); \ if ((rw)->context == NULL) { \ diff --git a/bindings/ruby/ext/ruby_whisper_log_settable.h b/bindings/ruby/ext/ruby_whisper_log_settable.h new file mode 100644 index 00000000000..b98fbac826b --- /dev/null +++ b/bindings/ruby/ext/ruby_whisper_log_settable.h @@ -0,0 +1,47 @@ +#ifndef RUBY_WHISPER_LOG_SETTABLE_H +#define RUBY_WHISPER_LOG_SETTABLE_H + +#define LOG_SETTABLE_SETUP(log_queue, mod, log_set) \ + static VALUE \ + ruby_whisper_##log_queue##_s_drain_logs(VALUE self) \ + { \ + return ruby_whisper_log_queue_drain(&log_queue); \ + } \ + static void \ + ruby_whisper_##log_queue##_log_callback(enum ggml_log_level level, const char *text, void *user_data) \ + { \ + ruby_whisper_log_queue_enqueue(&log_queue, level, text); \ + } \ + static VALUE \ + ruby_whisper_##log_queue##_s_log_set(VALUE self, VALUE log_callback, VALUE user_data) \ + { \ + rb_iv_set(self, "@log_callback", log_callback); \ + rb_iv_set(self, "@log_callback_user_data", user_data); \ + if (NIL_P(log_callback)) { \ + log_set(NULL, NULL); \ + } else { \ + ruby_whisper_log_queue_open(&log_queue); \ + rb_funcall((mod), id_start_log_callback_thread, 0); \ + log_set(ruby_whisper_##log_queue##_log_callback, NULL); \ + } \ + return Qnil; \ + } \ + static void \ + ruby_whisper_##log_queue##_end_proc(VALUE args) \ + { \ + ruby_whisper_log_queue_close(&log_queue); \ + VALUE log_callback_thread = rb_ivar_get(mod, id_log_callback_thread); \ + if (!NIL_P(log_callback_thread) && RTEST(rb_funcall(log_callback_thread, id_alive_p, 0))) { \ + rb_funcall(log_callback_thread, id_join, 0); \ + } \ + } + +#define LOG_SETTABLE_INIT(log_queue, mod) \ + ruby_whisper_log_queue_initialize(&log_queue); \ + 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); + +#endif diff --git a/bindings/ruby/ext/ruby_whisper_parakeet.c b/bindings/ruby/ext/ruby_whisper_parakeet.c index 6d31cee62c4..d69369401d0 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet.c @@ -8,11 +8,6 @@ extern VALUE cParakeetContext; extern VALUE cParakeetSegment; extern VALUE mOutputContext; extern VALUE mOutputSegment; -extern ID id_extended; -extern ID id_log_callback_thread; -extern ID id_start_log_callback_thread; -extern ID id_alive; -extern ID id_join; extern void init_ruby_whisper_parakeet_params(VALUE *mParakeet); extern void init_ruby_whisper_parakeet_token(VALUE *mParakeet); @@ -21,41 +16,9 @@ extern VALUE init_ruby_whisper_parakeet_context(VALUE *mParakeet); extern void init_ruby_whisper_parakeet_context_params(VALUE *cParakeetContext); extern void init_ruby_whisper_parakeet_model(VALUE *mParakeet); -extern void ruby_whisper_log_queue_initialize(ruby_whisper_log_queue *log_queue); -extern void ruby_whisper_log_queue_open(ruby_whisper_log_queue *log_queue); -extern void ruby_whisper_log_queue_close(ruby_whisper_log_queue *log_queue); -extern void ruby_whisper_log_queue_enqueue(ruby_whisper_log_queue *log_queue, enum ggml_log_level level, const char *text); -extern VALUE ruby_whisper_log_queue_drain(ruby_whisper_log_queue *log_queue); - static ruby_whisper_log_queue parakeet_log_queue; -static VALUE -ruby_whisper_parakeet_s_drain_logs(VALUE self) -{ - return ruby_whisper_log_queue_drain(¶keet_log_queue); -} - -static void -ruby_whisper_parakeet_log_callback(enum ggml_log_level level, const char *text, void *user_data) -{ - ruby_whisper_log_queue_enqueue(¶keet_log_queue, level, text); -} - -static VALUE -ruby_whisper_parakeet_s_log_set(VALUE self, VALUE log_callback, VALUE user_data) -{ - rb_iv_set(self, "@log_callback", log_callback); - rb_iv_set(self, "@log_callback_user_data", user_data); - if (NIL_P(log_callback)) { - parakeet_log_set(NULL, NULL); - } else { - ruby_whisper_log_queue_open(¶keet_log_queue); - rb_funcall(mParakeet, id_start_log_callback_thread, 0); - parakeet_log_set(ruby_whisper_parakeet_log_callback, NULL); - } - - return Qnil; -} +LOG_SETTABLE_SETUP(parakeet_log_queue, mParakeet, parakeet_log_set) static VALUE ruby_whisper_parakeet_s_system_info_str(VALUE self) @@ -63,17 +26,6 @@ ruby_whisper_parakeet_s_system_info_str(VALUE self) return rb_str_new2(parakeet_print_system_info()); } -static void -ruby_whisper_parakeet_end_proc(VALUE args) -{ - ruby_whisper_log_queue_close(¶keet_log_queue); - - VALUE log_callback_thread = rb_ivar_get(mParakeet, id_log_callback_thread); - if (!NIL_P(log_callback_thread) && RTEST(rb_funcall(log_callback_thread, id_alive, 0))) { - rb_funcall(log_callback_thread, id_join, 0); - } -} - void init_ruby_whisper_parakeet(VALUE *mWhisper) { @@ -81,15 +33,9 @@ init_ruby_whisper_parakeet(VALUE *mWhisper) rb_define_const(mParakeet, "VERSION", rb_str_new2(parakeet_version())); - ruby_whisper_log_queue_initialize(¶keet_log_queue); + LOG_SETTABLE_INIT(parakeet_log_queue, mParakeet) - rb_define_singleton_method(mParakeet, "log_set", ruby_whisper_parakeet_s_log_set, 2); rb_define_singleton_method(mParakeet, "system_info_str", ruby_whisper_parakeet_s_system_info_str, 0); - rb_define_private_method(rb_singleton_class(mParakeet), "drain_logs", ruby_whisper_parakeet_s_drain_logs, 0); - - rb_set_end_proc(ruby_whisper_parakeet_end_proc, Qnil); - rb_extend_object(mParakeet, mLogSettable); - rb_funcall(mLogSettable, id_extended, 1, mParakeet); init_ruby_whisper_parakeet_params(&mParakeet); init_ruby_whisper_parakeet_token(&mParakeet); From ca2f6e75277cf81e2afe37eec6398a56f0ee28d9 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Sun, 31 May 2026 21:03:40 +0900 Subject: [PATCH 099/118] Use log settable for Whisper --- bindings/ruby/ext/ruby_whisper.c | 84 +++----------------------------- 1 file changed, 6 insertions(+), 78 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper.c b/bindings/ruby/ext/ruby_whisper.c index 627ecc5232d..7941b1a99dd 100644 --- a/bindings/ruby/ext/ruby_whisper.c +++ b/bindings/ruby/ext/ruby_whisper.c @@ -43,9 +43,6 @@ ID id_log_callback_thread; ID id_alive_p; ID id_join; -static bool is_log_callback_finalized = false; -static bool is_ruby_log_callback_present = false; - // High level API extern VALUE ruby_whisper_segment_allocate(VALUE klass); @@ -63,6 +60,10 @@ extern void init_ruby_whisper_vad_segments(VALUE *mVAD); extern void init_ruby_whisper_parakeet(VALUE *mWhisper); extern void register_callbacks(ruby_whisper_params *rwp, VALUE *context); +static ruby_whisper_log_queue whisper_log_queue; + +LOG_SETTABLE_SETUP(whisper_log_queue, mWhisper, whisper_log_set) + /* * call-seq: * lang_max_id -> Integer @@ -118,79 +119,6 @@ static VALUE ruby_whisper_s_system_info_str(VALUE self) { return rb_str_new2(whisper_print_system_info()); } -static VALUE ruby_whisper_s_finalize_log_callback(VALUE self, VALUE id) { - is_log_callback_finalized = true; - return Qnil; -} - -typedef struct { - int level; - const char * buffer; -} call_log_callbacks_args; - -static void* -call_log_callbacks(void *v_args) { - VALUE log_callback = rb_iv_get(mWhisper, "log_callback"); - if (NIL_P(log_callback)) { - return NULL; - } - - call_log_callbacks_args *args = (call_log_callbacks_args *)v_args; - VALUE user_data = rb_iv_get(mWhisper, "user_data"); - rb_funcall(log_callback, id_call, 3, INT2NUM(args->level), rb_str_new2(args->buffer), user_data); - - return NULL; -} - -static void -ruby_whisper_log_callback(enum ggml_log_level level, const char * buffer, void * user_data) { - if (is_log_callback_finalized) { - return; - } - if (!is_ruby_log_callback_present) { - return; - } - - call_log_callbacks_args args = { - level, - buffer, - }; - if (ruby_thread_has_gvl_p()) { - call_log_callbacks((void *)&args); - } else { - rb_thread_call_with_gvl(call_log_callbacks, (void *)&args); - } -} - -/* - * call-seq: - * log_set ->(level, buffer, user_data) { ... }, user_data -> nil - */ -static VALUE ruby_whisper_s_log_set(VALUE self, VALUE log_callback, VALUE user_data) { - VALUE old_callback = rb_iv_get(self, "log_callback"); - if (!NIL_P(old_callback)) { - rb_undefine_finalizer(old_callback); - } - - rb_iv_set(self, "log_callback", log_callback); - rb_iv_set(self, "user_data", user_data); - - if (!NIL_P(log_callback)) { - VALUE finalize_log_callback = rb_funcall(mWhisper, rb_intern("method"), 1, rb_str_new2("finalize_log_callback")); - rb_define_finalizer(log_callback, finalize_log_callback); - } - - if (NIL_P(log_callback)) { - whisper_log_set(NULL, NULL); - is_ruby_log_callback_present = false; - } else { - whisper_log_set(ruby_whisper_log_callback, NULL); - is_ruby_log_callback_present = true; - } - - return Qnil; -} - void Init_whisper() { id_to_s = rb_intern("to_s"); id_call = rb_intern("call"); @@ -248,8 +176,8 @@ void Init_whisper() { rb_define_singleton_method(mWhisper, "lang_str", ruby_whisper_s_lang_str, 1); rb_define_singleton_method(mWhisper, "lang_str_full", ruby_whisper_s_lang_str_full, 1); rb_define_singleton_method(mWhisper, "system_info_str", ruby_whisper_s_system_info_str, 0); - rb_define_singleton_method(mWhisper, "log_set", ruby_whisper_s_log_set, 2); - rb_define_private_method(rb_singleton_class(mWhisper), "finalize_log_callback", ruby_whisper_s_finalize_log_callback, 1); + + LOG_SETTABLE_INIT(whisper_log_queue, mWhisper) cContext = init_ruby_whisper_context(&mWhisper); init_ruby_whisper_context_params(&cContext); From d58055b8e70a489ea9590b9340e26434ecfa20bd Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 5 Jun 2026 09:12:40 +0900 Subject: [PATCH 100/118] Address deadlock --- bindings/ruby/ext/ruby_whisper_log_queue.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper_log_queue.c b/bindings/ruby/ext/ruby_whisper_log_queue.c index 713ccf227a9..6558a339c6f 100644 --- a/bindings/ruby/ext/ruby_whisper_log_queue.c +++ b/bindings/ruby/ext/ruby_whisper_log_queue.c @@ -37,6 +37,8 @@ ruby_whisper_log_queue_open(ruby_whisper_log_queue *log_queue) log_queue->is_open = true; + rb_native_cond_signal(&log_queue->cond); + rb_nativethread_lock_unlock(&log_queue->lock); } @@ -104,6 +106,7 @@ ruby_whisper_log_queue_wait(void *args) ruby_whisper_log_queue *log_queue = (ruby_whisper_log_queue *)args; rb_native_cond_wait(&log_queue->cond, &log_queue->lock); + rb_nativethread_lock_unlock(&log_queue->lock); return NULL; } @@ -113,11 +116,7 @@ ruby_whisper_log_queue_wait_ubf(void *args) { ruby_whisper_log_queue *log_queue = (ruby_whisper_log_queue *)args; - rb_nativethread_lock_lock(&log_queue->lock); - rb_native_cond_broadcast(&log_queue->cond); - - rb_nativethread_lock_unlock(&log_queue->lock); } typedef struct { @@ -135,6 +134,7 @@ ruby_whisper_log_queue_drain(ruby_whisper_log_queue *log_queue) while (log_queue->size == 0 && log_queue->is_open) { rb_thread_call_without_gvl(ruby_whisper_log_queue_wait, (void *)log_queue, ruby_whisper_log_queue_wait_ubf, (void *)log_queue); + rb_nativethread_lock_lock(&log_queue->lock); } if (log_queue->size == 0 && !log_queue->is_open) { From 2926922f64e220b4dec80e170c5d223b0eddf216 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 5 Jun 2026 10:38:17 +0900 Subject: [PATCH 101/118] Make test follow change of log queue implementation --- bindings/ruby/test/test_callback.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/bindings/ruby/test/test_callback.rb b/bindings/ruby/test/test_callback.rb index a7f49245ade..6490c8abb48 100644 --- a/bindings/ruby/test/test_callback.rb +++ b/bindings/ruby/test/test_callback.rb @@ -129,6 +129,7 @@ def test_encoder_begin_callback_abort return false } @whisper.transcribe(@audio, @params) + sleep 0.5 # wait for logs dequeued assert_match(/encoder_begin_callback returned false - aborting/, logs.join) Whisper.log_set ->(level, buffer, user_data) {}, nil end From 4f784eebe3f3c1cce8381d12b70b3fcffd9383dd Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 12 Jun 2026 17:03:27 +0900 Subject: [PATCH 102/118] Refactor to make abort callback use the same way to parakeet's way --- bindings/ruby/ext/ruby_whisper.h | 17 ++-- bindings/ruby/ext/ruby_whisper_context.c | 31 ++++---- bindings/ruby/ext/ruby_whisper_params.c | 78 ++++++------------- bindings/ruby/ext/ruby_whisper_transcribe.cpp | 45 +++-------- 4 files changed, 56 insertions(+), 115 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index 094f1fce919..7f643209a28 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -24,14 +24,6 @@ typedef struct { VALUE callbacks; } ruby_whisper_callback_container; -typedef struct { - VALUE *context; - VALUE user_data; - VALUE callback; - VALUE callbacks; - bool is_interrupted; -} ruby_whisper_abort_callback_container; - typedef struct ruby_whisper_abort_callback_user_data { volatile rb_atomic_t is_interrupted; ruby_whisper_callback_container *callback_container; @@ -69,7 +61,7 @@ typedef struct { ruby_whisper_callback_container *new_segment_callback_container; ruby_whisper_callback_container *progress_callback_container; ruby_whisper_callback_container *encoder_begin_callback_container; - ruby_whisper_abort_callback_container *abort_callback_container; + ruby_whisper_callback_container *abort_callback_container; VALUE vad_params; } ruby_whisper_params; @@ -111,6 +103,13 @@ typedef struct parsed_samples_t { bool memview_exported; } parsed_samples_t; +typedef struct full_args { + VALUE *context; + VALUE *params; + float *samples; + int n_samples; +} full_args; + typedef struct { VALUE *context; VALUE *params; diff --git a/bindings/ruby/ext/ruby_whisper_context.c b/bindings/ruby/ext/ruby_whisper_context.c index 0f2246dd9b7..c44f210a0eb 100644 --- a/bindings/ruby/ext/ruby_whisper_context.c +++ b/bindings/ruby/ext/ruby_whisper_context.c @@ -28,7 +28,7 @@ extern const rb_data_type_t ruby_whisper_context_params_type; extern VALUE ruby_whisper_transcribe(int argc, VALUE *argv, VALUE self); extern VALUE rb_whisper_model_s_new(VALUE context); extern VALUE rb_whisper_segment_s_new(VALUE context, int index); -extern void prepare_transcription(ruby_whisper_params *rwp, VALUE *context, int n_processors); +extern void prepare_transcription(ruby_whisper_params *rwp, VALUE *context, int n_processors, ruby_whisper_abort_callback_user_data *abort_callback_user_data); ID transcribe_option_names[1]; @@ -38,13 +38,6 @@ typedef struct fill_samples_args { int n_samples; } fill_samples_args; -typedef struct full_args { - VALUE *context; - VALUE *params; - float *samples; - int n_samples; -} full_args; - typedef struct full_parallel_args { VALUE *context; VALUE *params; @@ -71,7 +64,7 @@ typedef struct full_parallel_without_gvl_args { } full_parallel_without_gvl_args; typedef struct full_ubf_args { - ruby_whisper_abort_callback_container *abort_callback_container; + ruby_whisper_abort_callback_user_data *abort_callback_user_data; } full_ubf_args; static void @@ -480,10 +473,10 @@ full_ubf(void *rb_args) { full_ubf_args *args = (full_ubf_args *)rb_args; - args->abort_callback_container->is_interrupted = true; + RUBY_ATOMIC_SET(args->abort_callback_user_data->is_interrupted, 1); } -static VALUE +VALUE full_body(VALUE rb_args) { full_args *args = (full_args *)rb_args; @@ -493,7 +486,11 @@ full_body(VALUE rb_args) GetContext(*args->context, rw); TypedData_Get_Struct(*args->params, ruby_whisper_params, &ruby_whisper_params_type, rwp); - prepare_transcription(rwp, args->context, 1); + ruby_whisper_abort_callback_user_data abort_callback_user_data = { + 0, + NULL, + }; + prepare_transcription(rwp, args->context, 1, &abort_callback_user_data); struct full_without_gvl_args full_without_gvl_args = { rw->context, @@ -503,7 +500,7 @@ full_body(VALUE rb_args) 0, }; full_ubf_args full_ubf_args = { - rwp->abort_callback_container, + &abort_callback_user_data, }; rb_thread_call_without_gvl(full_without_gvl, (void *)&full_without_gvl_args, full_ubf, (void *)&full_ubf_args); return INT2NUM(full_without_gvl_args.result); @@ -562,7 +559,11 @@ full_parallel_body(VALUE rb_args) GetContext(*args->context, rw); TypedData_Get_Struct(*args->params, ruby_whisper_params, &ruby_whisper_params_type, rwp); - prepare_transcription(rwp, args->context, args->n_processors); + ruby_whisper_abort_callback_user_data abort_callback_user_data = { + 0, + NULL, + }; + prepare_transcription(rwp, args->context, args->n_processors, &abort_callback_user_data); struct full_parallel_without_gvl_args full_parallel_without_gvl_args = { rw->context, @@ -573,7 +574,7 @@ full_parallel_body(VALUE rb_args) 0, }; full_ubf_args full_ubf_args = { - rwp->abort_callback_container, + &abort_callback_user_data, }; rb_thread_call_without_gvl(full_parallel_without_gvl, (void *)&full_parallel_without_gvl_args, full_ubf, (void *)&full_ubf_args); return INT2NUM(full_parallel_without_gvl_args.result); diff --git a/bindings/ruby/ext/ruby_whisper_params.c b/bindings/ruby/ext/ruby_whisper_params.c index 7447cf5af27..4468b434867 100644 --- a/bindings/ruby/ext/ruby_whisper_params.c +++ b/bindings/ruby/ext/ruby_whisper_params.c @@ -97,38 +97,11 @@ ruby_whisper_callback_container_allocate() { return container; } -static void -rb_whisper_abort_callback_container_mark(ruby_whisper_abort_callback_container *rwc) -{ - if (rwc == NULL) return; - - rb_gc_mark(rwc->user_data); - rb_gc_mark(rwc->callback); - rb_gc_mark(rwc->callbacks); -} - -static ruby_whisper_abort_callback_container* -rb_whisper_abort_callback_container_allocate() { - ruby_whisper_abort_callback_container *container; - container = ALLOC(ruby_whisper_abort_callback_container); - container->context = NULL; - container->user_data = Qnil; - container->callback = Qnil; - container->callbacks = Qnil; - container->is_interrupted = false; - return container; -} - bool ruby_whisper_callback_container_is_present(const ruby_whisper_callback_container *container) { return !NIL_P(container->callback) || !NIL_P(container->callbacks); } -static bool -ruby_whisper_abort_callback_container_is_present(const ruby_whisper_abort_callback_container *container) { - return !NIL_P(container->callback) || !NIL_P(container->callbacks); -} - typedef struct { const ruby_whisper_callback_container *container; struct whisper_state *state; @@ -283,24 +256,19 @@ static bool encoder_begin_callback(struct whisper_context *ctx, struct whisper_s } typedef struct { - const ruby_whisper_abort_callback_container *container; - struct whisper_state *state; + const ruby_whisper_callback_container *container; bool is_interrupted; } call_abort_callbacks_args; static void* call_abort_callbacks(void *v_args) { call_abort_callbacks_args *args = (call_abort_callbacks_args *)v_args; - const ruby_whisper_abort_callback_container *container = args->container; - - if (container->is_interrupted) { - args->is_interrupted = true; - return NULL; - } + const ruby_whisper_callback_container *container = args->container; + VALUE result = Qnil; if (!NIL_P(container->callback)) { - VALUE result = rb_funcall(container->callback, id_call, 1, container->user_data); - if (!NIL_P(result) && Qfalse != result) { + result = rb_funcall(container->callback, id_call, 1, container->user_data); + if (RTEST(result)) { args->is_interrupted = true; return NULL; } @@ -308,14 +276,14 @@ call_abort_callbacks(void *v_args) { if (NIL_P(container->callbacks)) { return NULL; } - const long callbacks_len = RARRAY_LEN(container->callbacks); - if (0 == callbacks_len) { + const long n_callbacks = RARRAY_LEN(container->callbacks); + if (0 == n_callbacks) { return NULL; } - for (int j = 0; j < callbacks_len; j++) { + for (int j = 0; j < n_callbacks; j++) { VALUE cb = rb_ary_entry(container->callbacks, j); - VALUE result = rb_funcall(cb, id_call, 1, container->user_data); - if (!NIL_P(result) && Qfalse != result) { + VALUE result = rb_funcall(cb, id_call, 0); + if (RTEST(result)) { args->is_interrupted = true; return NULL; } @@ -325,19 +293,19 @@ call_abort_callbacks(void *v_args) { } static bool abort_callback(void * user_data) { - const ruby_whisper_abort_callback_container *container = (ruby_whisper_abort_callback_container *)user_data; + ruby_whisper_abort_callback_user_data *data = (ruby_whisper_abort_callback_user_data *)user_data; - if (container->is_interrupted) { + int is_interrupted = RUBY_ATOMIC_LOAD(data->is_interrupted); + if (is_interrupted) { return true; } - if (!ruby_whisper_abort_callback_container_is_present(container)) { + if (!(data->callback_container) || !ruby_whisper_callback_container_is_present(data->callback_container)) { return false; } call_abort_callbacks_args args = { - container, - NULL, + data->callback_container, false }; rb_thread_call_with_gvl(call_abort_callbacks, (void *)&args); @@ -364,7 +332,7 @@ check_thread_safety(ruby_whisper_params *rwp, int n_processors) rb_raise(rb_eRuntimeError, "encoder begin callback not supported on parallel transcription"); } - if (ruby_whisper_abort_callback_container_is_present(rwp->abort_callback_container)) { + if (ruby_whisper_callback_container_is_present(rwp->abort_callback_container)) { rb_raise(rb_eRuntimeError, "abort callback not supported on parallel transcription"); } @@ -374,7 +342,7 @@ check_thread_safety(ruby_whisper_params *rwp, int n_processors) } } -static void register_callbacks(ruby_whisper_params * rwp, VALUE * context) { +static void register_callbacks(ruby_whisper_params * rwp, VALUE * context, ruby_whisper_abort_callback_user_data *abort_callback_user_data) { if (ruby_whisper_callback_container_is_present(rwp->new_segment_callback_container)) { rwp->new_segment_callback_container->context = context; rwp->params.new_segment_callback = new_segment_callback; @@ -393,10 +361,10 @@ static void register_callbacks(ruby_whisper_params * rwp, VALUE * context) { rwp->params.encoder_begin_callback_user_data = rwp->encoder_begin_callback_container; } + abort_callback_user_data->callback_container = rwp->abort_callback_container; rwp->abort_callback_container->context = context; rwp->params.abort_callback = abort_callback; - rwp->abort_callback_container->is_interrupted = false; - rwp->params.abort_callback_user_data = rwp->abort_callback_container; + rwp->params.abort_callback_user_data = (void *)abort_callback_user_data; } static void set_vad_params(ruby_whisper_params *rwp) @@ -410,10 +378,10 @@ static void set_vad_params(ruby_whisper_params *rwp) TODO: Set abort callback to trap SIGINT and SIGTERM */ void -prepare_transcription(ruby_whisper_params *rwp, VALUE *context, int n_processors) +prepare_transcription(ruby_whisper_params *rwp, VALUE *context, int n_processors, ruby_whisper_abort_callback_user_data *abort_callback_user_data) { check_thread_safety(rwp, n_processors); - register_callbacks(rwp, context); + register_callbacks(rwp, context, abort_callback_user_data); set_vad_params(rwp); } @@ -424,7 +392,7 @@ rb_whisper_params_mark(void *p) ruby_whisper_callback_container_mark(rwp->new_segment_callback_container); ruby_whisper_callback_container_mark(rwp->progress_callback_container); ruby_whisper_callback_container_mark(rwp->encoder_begin_callback_container); - rb_whisper_abort_callback_container_mark(rwp->abort_callback_container); + ruby_whisper_callback_container_mark(rwp->abort_callback_container); rb_gc_mark(rwp->vad_params); } @@ -495,7 +463,7 @@ ruby_whisper_params_allocate(VALUE klass) 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(); - rwp->abort_callback_container = rb_whisper_abort_callback_container_allocate(); + rwp->abort_callback_container = ruby_whisper_callback_container_allocate(); return obj; } diff --git a/bindings/ruby/ext/ruby_whisper_transcribe.cpp b/bindings/ruby/ext/ruby_whisper_transcribe.cpp index 37656af1c44..9bd5dfbe48e 100644 --- a/bindings/ruby/ext/ruby_whisper_transcribe.cpp +++ b/bindings/ruby/ext/ruby_whisper_transcribe.cpp @@ -16,6 +16,7 @@ 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); typedef struct{ struct whisper_context *context; @@ -35,18 +36,6 @@ transcribe_without_gvl(void *rb_args) return NULL; } -typedef struct { - ruby_whisper_abort_callback_container *abort_callback_container; -} transcribe_ubf_args; - -static void -transcribe_ubf(void *rb_args) -{ - transcribe_ubf_args *args = (transcribe_ubf_args *)rb_args; - - args->abort_callback_container->is_interrupted = true; -} - /* * transcribe a single file * can emit to a block results @@ -91,32 +80,16 @@ ruby_whisper_transcribe(int argc, VALUE *argv, VALUE self) { fprintf(stderr, "error: failed to open '%s' as WAV file\n", fname_inp.c_str()); return self; } - // Commented out because it is work in progress - // { - // static bool is_aborted = false; // NOTE: this should be atomic to avoid data race - - // rwp->params.encoder_begin_callback = [](struct whisper_context * /*ctx*/, struct whisper_state * /*state*/, void * user_data) { - // bool is_aborted = *(bool*)user_data; - // return !is_aborted; - // }; - // rwp->params.encoder_begin_callback_user_data = &is_aborted; - // } - - prepare_transcription(rwp, &self, n_processors); - - transcribe_without_gvl_args args = { - rw->context, - &rwp->params, + + full_args args = { + &self, + ¶ms, pcmf32.data(), - pcmf32.size(), - n_processors, - 0, - }; - transcribe_ubf_args ubf_args = { - rwp->abort_callback_container, + (int)pcmf32.size(), }; - rb_thread_call_without_gvl(transcribe_without_gvl, (void *)&args, transcribe_ubf, (void *)&ubf_args); - if (args.result != 0) { + VALUE rb_result = full_body((VALUE)&args); + const int result = NUM2INT(rb_result); + if (result != 0) { fprintf(stderr, "failed to process audio\n"); return self; } From 31909b814c505fa4ad5fbb37414d23eb0679d600 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Fri, 12 Jun 2026 17:17:02 +0900 Subject: [PATCH 103/118] Remove redundant structs --- bindings/ruby/ext/ruby_whisper.h | 9 +-------- bindings/ruby/ext/ruby_whisper_context.c | 4 ++-- bindings/ruby/ext/ruby_whisper_parakeet_context.c | 4 ++-- bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp | 2 +- bindings/ruby/ext/ruby_whisper_transcribe.cpp | 2 +- 5 files changed, 7 insertions(+), 14 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index 7f643209a28..29ce6c47c43 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -103,19 +103,12 @@ typedef struct parsed_samples_t { bool memview_exported; } parsed_samples_t; -typedef struct full_args { - VALUE *context; - VALUE *params; - float *samples; - int n_samples; -} full_args; - typedef struct { VALUE *context; VALUE *params; float *samples; int n_samples; -} ruby_whisper_parakeet_context_full_args; +} ruby_whisper_full_args; typedef struct { struct parakeet_full_params params; diff --git a/bindings/ruby/ext/ruby_whisper_context.c b/bindings/ruby/ext/ruby_whisper_context.c index c44f210a0eb..2335741c33a 100644 --- a/bindings/ruby/ext/ruby_whisper_context.c +++ b/bindings/ruby/ext/ruby_whisper_context.c @@ -479,7 +479,7 @@ full_ubf(void *rb_args) VALUE full_body(VALUE rb_args) { - full_args *args = (full_args *)rb_args; + ruby_whisper_full_args *args = (ruby_whisper_full_args *)rb_args; ruby_whisper *rw; ruby_whisper_params *rwp; @@ -526,7 +526,7 @@ VALUE ruby_whisper_full(int argc, VALUE *argv, VALUE self) VALUE n_samples = argc == 2 ? Qnil : argv[2]; struct parsed_samples_t parsed = parse_samples(&argv[1], &n_samples); - full_args args = { + ruby_whisper_full_args args = { &self, &argv[0], parsed.samples, diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_context.c b/bindings/ruby/ext/ruby_whisper_parakeet_context.c index bc39e9a5528..b4a2fc5c4b7 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_context.c +++ b/bindings/ruby/ext/ruby_whisper_parakeet_context.c @@ -217,7 +217,7 @@ parakeet_full_ubf(void *rb_args) VALUE ruby_whisper_parakeet_context_full_body(VALUE rb_args) { - ruby_whisper_parakeet_context_full_args *args = (ruby_whisper_parakeet_context_full_args *)rb_args; + ruby_whisper_full_args *args = (ruby_whisper_full_args *)rb_args; ruby_whisper_parakeet_context *rwpc; GetParakeetContext(*args->context, rwpc); ruby_whisper_parakeet_params *rwpp; @@ -254,7 +254,7 @@ ruby_whisper_parakeet_context_full(int argc, VALUE *argv, VALUE self) VALUE n_samples = argc == 2 ? Qnil : argv[2]; struct parsed_samples_t parsed = parse_samples(&argv[1], &n_samples); - ruby_whisper_parakeet_context_full_args args = { + ruby_whisper_full_args args = { &self, &argv[0], parsed.samples, diff --git a/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp b/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp index f287d20b89e..c4deccce84a 100644 --- a/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp +++ b/bindings/ruby/ext/ruby_whisper_parakeet_transcribe.cpp @@ -38,7 +38,7 @@ ruby_whisper_parakeet_transcribe(VALUE self, VALUE audio_path, VALUE params) GetParakeetContext(self, rwpc); GetParakeetParams(params, rwpp); - ruby_whisper_parakeet_context_full_args args = { + ruby_whisper_full_args args = { &self, ¶ms, pcmf32.data(), diff --git a/bindings/ruby/ext/ruby_whisper_transcribe.cpp b/bindings/ruby/ext/ruby_whisper_transcribe.cpp index 9bd5dfbe48e..ecd27d3a37c 100644 --- a/bindings/ruby/ext/ruby_whisper_transcribe.cpp +++ b/bindings/ruby/ext/ruby_whisper_transcribe.cpp @@ -81,7 +81,7 @@ ruby_whisper_transcribe(int argc, VALUE *argv, VALUE self) { return self; } - full_args args = { + ruby_whisper_full_args args = { &self, ¶ms, pcmf32.data(), From c844b6e3b5d8256d9e2073c398b9eacfa55c9d3d Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 15 Jun 2026 02:34:37 +0900 Subject: [PATCH 104/118] Fix test name --- bindings/ruby/test/test_parakeet_model.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/ruby/test/test_parakeet_model.rb b/bindings/ruby/test/test_parakeet_model.rb index 17cd8ac3c75..5343b35ed8e 100644 --- a/bindings/ruby/test/test_parakeet_model.rb +++ b/bindings/ruby/test/test_parakeet_model.rb @@ -1,6 +1,6 @@ require_relative "helper" -class TestModel < TestBase +class TestParakeetModel < TestBase def test_model parakeet = Parakeet::Context.new("test/fixtures/for-tests-ggml-parakeet-tdt.bin") assert_instance_of Parakeet::Model, parakeet.model From 7bac5e4d7a0792e6a9e1b9e8c0a55179c57f757d Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 15 Jun 2026 02:37:35 +0900 Subject: [PATCH 105/118] Fix README --- bindings/ruby/README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bindings/ruby/README.md b/bindings/ruby/README.md index 116ef82c35d..6830bcf4e71 100644 --- a/bindings/ruby/README.md +++ b/bindings/ruby/README.md @@ -412,8 +412,7 @@ Parakeet = Whisper::Parakeet parakeet = Parakeet::Context.new("path/to/model") params = Parakeet::Params.new( - no_context: true, - chunk_length_ms: 10_000 + no_context: true ) parakeet @@ -426,7 +425,6 @@ parakeet The main differences are: * Namespace is `Whisper::Parakeet`. -* `Whisper::Parakeet::Params` has Parakeet-specific chunk and context settings such as `chunk_length_ms`, `left_context_ms`, and `right_context_ms`. * Parakeet also supports `on_new_token` / `new_token_callback` in addition to segment and progress callbacks. Custom context params From 6c1071fc8b5cbb509e2c6aca07fbef110614d1d5 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 15 Jun 2026 02:45:57 +0900 Subject: [PATCH 106/118] Add missing parallel transcription --- bindings/ruby/ext/ruby_whisper.h | 8 ++++ bindings/ruby/ext/ruby_whisper_context.c | 14 ++----- bindings/ruby/ext/ruby_whisper_transcribe.cpp | 39 +++++++++++++++---- 3 files changed, 42 insertions(+), 19 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper.h b/bindings/ruby/ext/ruby_whisper.h index 29ce6c47c43..10e90674953 100644 --- a/bindings/ruby/ext/ruby_whisper.h +++ b/bindings/ruby/ext/ruby_whisper.h @@ -110,6 +110,14 @@ typedef struct { int n_samples; } ruby_whisper_full_args; +typedef struct ruby_whisper_full_parallel_args { + VALUE *context; + VALUE *params; + float *samples; + int n_samples; + int n_processors; +} ruby_whisper_full_parallel_args; + typedef struct { struct parakeet_full_params params; ruby_whisper_callback_container *new_segment_callback_container; diff --git a/bindings/ruby/ext/ruby_whisper_context.c b/bindings/ruby/ext/ruby_whisper_context.c index 2335741c33a..9e5fc33e726 100644 --- a/bindings/ruby/ext/ruby_whisper_context.c +++ b/bindings/ruby/ext/ruby_whisper_context.c @@ -38,14 +38,6 @@ typedef struct fill_samples_args { int n_samples; } fill_samples_args; -typedef struct full_parallel_args { - VALUE *context; - VALUE *params; - float *samples; - int n_samples; - int n_processors; -} full_parallel_args; - typedef struct full_without_gvl_args { struct whisper_context *context; struct whisper_full_params *params; @@ -549,10 +541,10 @@ full_parallel_without_gvl(void *rb_args) return NULL; } -static VALUE +VALUE full_parallel_body(VALUE rb_args) { - full_parallel_args *args = (full_parallel_args *)rb_args; + ruby_whisper_full_parallel_args *args = (ruby_whisper_full_parallel_args *)rb_args; ruby_whisper *rw; ruby_whisper_params *rwp; @@ -614,7 +606,7 @@ ruby_whisper_full_parallel(int argc, VALUE *argv,VALUE self) break; } struct parsed_samples_t parsed = parse_samples(&argv[1], &n_samples); - const full_parallel_args args = { + const ruby_whisper_full_parallel_args args = { &self, &argv[0], parsed.samples, diff --git a/bindings/ruby/ext/ruby_whisper_transcribe.cpp b/bindings/ruby/ext/ruby_whisper_transcribe.cpp index ecd27d3a37c..d511953d141 100644 --- a/bindings/ruby/ext/ruby_whisper_transcribe.cpp +++ b/bindings/ruby/ext/ruby_whisper_transcribe.cpp @@ -17,6 +17,7 @@ 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; @@ -80,14 +81,36 @@ ruby_whisper_transcribe(int argc, VALUE *argv, VALUE self) { fprintf(stderr, "error: failed to open '%s' as WAV file\n", fname_inp.c_str()); return self; } - - ruby_whisper_full_args args = { - &self, - ¶ms, - pcmf32.data(), - (int)pcmf32.size(), - }; - VALUE rb_result = full_body((VALUE)&args); +/* + * + typedef struct full_parallel_args { + VALUE *context; + VALUE *params; + float *samples; + int n_samples; + int n_processors; + } full_parallel_args; + + */ + VALUE rb_result; + if (n_processors == 1) { + ruby_whisper_full_args args = { + &self, + ¶ms, + pcmf32.data(), + (int)pcmf32.size(), + }; + rb_result = full_body((VALUE)&args); + } else { + ruby_whisper_full_parallel_args parallel_args = { + &self, + ¶ms, + pcmf32.data(), + (int)pcmf32.size(), + n_processors, + }; + rb_result = full_parallel_body((VALUE)¶llel_args); + } const int result = NUM2INT(rb_result); if (result != 0) { fprintf(stderr, "failed to process audio\n"); From b85067b38274e8ccd4b738b57345b478f72663ae Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 15 Jun 2026 02:48:38 +0900 Subject: [PATCH 107/118] Fix test for parakeet info --- bindings/ruby/test/test_parakeet.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/ruby/test/test_parakeet.rb b/bindings/ruby/test/test_parakeet.rb index f2a71267c1a..bfd57076f56 100644 --- a/bindings/ruby/test/test_parakeet.rb +++ b/bindings/ruby/test/test_parakeet.rb @@ -19,7 +19,7 @@ def test_log_set end def test_system_info_str - assert_match /\APARAKEET : MTL : EMBED_LIBRARY = \d \| CPU : NEON = \d \|/, Parakeet.system_info_str + assert_match /\APARAKEET : /, Parakeet.system_info_str end def test_version From 519d3494158c95cb797e1e983aa66d9fcc31cb32 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 15 Jun 2026 02:54:47 +0900 Subject: [PATCH 108/118] Remove removed params --- bindings/ruby/sig/whisper.rbs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/bindings/ruby/sig/whisper.rbs b/bindings/ruby/sig/whisper.rbs index 053e705976a..c12e1fe55e5 100644 --- a/bindings/ruby/sig/whisper.rbs +++ b/bindings/ruby/sig/whisper.rbs @@ -718,9 +718,6 @@ module Whisper ?duration_ms: Integer, ?no_context: boolish, ?audio_ctx: Integer, - ?chunk_length_ms: Integer, - ?left_context_ms: Integer, - ?right_context_ms: Integer, ?new_segment_callback: ^(Whisper::Parakeet::Context, untyped, Integer n_new, Object user_data) -> void, ?new_segment_callback_user_data: Object, ?new_token_callback: ^(Whisper::Parakeet::Context, untyped, Whisper::Parakeet::Token, Object user_data) -> void, @@ -758,21 +755,6 @@ module Whisper def audio_ctx=: (Integer) -> Integer def audio_ctx: () -> Integer - # Length of each chunk in ms. - # - def chunk_length_ms=: (Integer) -> Integer - def chunk_length_ms: () -> Integer - - # Left context in ms. - # - def left_context_ms=: (Integer) -> Integer - def left_context_ms: () -> Integer - - # Right context in ms. - # - def right_context_ms=: (Integer) -> Integer - def right_context_ms: () -> Integer - # Sets new segment callback, called for every newly generated text segment. # # params.new_segment_callback = ->(context, _, n_new, user_data) { From 396a2e4e9c634dd1c543304714dfcfe375615960 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 15 Jun 2026 03:14:10 +0900 Subject: [PATCH 109/118] Wait for logs dequeued --- bindings/ruby/test/test_whisper.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/bindings/ruby/test/test_whisper.rb b/bindings/ruby/test/test_whisper.rb index f7e25239d5d..082547e7c08 100644 --- a/bindings/ruby/test/test_whisper.rb +++ b/bindings/ruby/test/test_whisper.rb @@ -149,6 +149,7 @@ def test_log_set } Whisper.log_set log_callback, user_data Whisper::Context.new("base.en") + sleep 0.1 # wait for logs dequeued assert logs.length > 30 logs.each do |log| From 280f90fa5b905c5140d60957324d1444a5d05789 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 15 Jun 2026 03:15:56 +0900 Subject: [PATCH 110/118] Fix instance variable name --- bindings/ruby/ext/ruby_whisper_params.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/ruby/ext/ruby_whisper_params.c b/bindings/ruby/ext/ruby_whisper_params.c index 4468b434867..94f10c38de6 100644 --- a/bindings/ruby/ext/ruby_whisper_params.c +++ b/bindings/ruby/ext/ruby_whisper_params.c @@ -336,7 +336,7 @@ check_thread_safety(ruby_whisper_params *rwp, int n_processors) rb_raise(rb_eRuntimeError, "abort callback not supported on parallel transcription"); } - VALUE log_callback = rb_iv_get(mWhisper, "log_callback"); + VALUE log_callback = rb_iv_get(mWhisper, "@log_callback"); if (!NIL_P(log_callback)) { rb_raise(rb_eRuntimeError, "log callback not supported for parallel transcription"); } From 3d2be3540e62f1946f9288d4eb05f959a60ee7af Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 15 Jun 2026 03:23:54 +0900 Subject: [PATCH 111/118] Load etc feature --- bindings/ruby/test/test_parakeet_params.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/bindings/ruby/test/test_parakeet_params.rb b/bindings/ruby/test/test_parakeet_params.rb index a3ff09d85f1..dc651f7ab12 100644 --- a/bindings/ruby/test/test_parakeet_params.rb +++ b/bindings/ruby/test/test_parakeet_params.rb @@ -1,4 +1,5 @@ require_relative "helper" +require "etc" class TestParakeetParams < TestBase PARAM_NAMES = [ From 1c0b1a2b62226dcf77583054de1d8194f8342104 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 15 Jun 2026 03:24:56 +0900 Subject: [PATCH 112/118] Remove unnecessary comment --- bindings/ruby/ext/ruby_whisper_transcribe.cpp | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper_transcribe.cpp b/bindings/ruby/ext/ruby_whisper_transcribe.cpp index d511953d141..73f606ca476 100644 --- a/bindings/ruby/ext/ruby_whisper_transcribe.cpp +++ b/bindings/ruby/ext/ruby_whisper_transcribe.cpp @@ -81,17 +81,7 @@ ruby_whisper_transcribe(int argc, VALUE *argv, VALUE self) { fprintf(stderr, "error: failed to open '%s' as WAV file\n", fname_inp.c_str()); return self; } -/* - * - typedef struct full_parallel_args { - VALUE *context; - VALUE *params; - float *samples; - int n_samples; - int n_processors; - } full_parallel_args; - - */ + VALUE rb_result; if (n_processors == 1) { ruby_whisper_full_args args = { From da08bfc52e788182278cb02c2fbcc09ace080d27 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 15 Jun 2026 05:40:03 +0900 Subject: [PATCH 113/118] Remove unnecessary thread safety check --- bindings/ruby/ext/ruby_whisper_params.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper_params.c b/bindings/ruby/ext/ruby_whisper_params.c index 94f10c38de6..bad3e0db11a 100644 --- a/bindings/ruby/ext/ruby_whisper_params.c +++ b/bindings/ruby/ext/ruby_whisper_params.c @@ -320,13 +320,8 @@ check_thread_safety(ruby_whisper_params *rwp, int n_processors) return; } - if (ruby_whisper_callback_container_is_present(rwp->new_segment_callback_container)) { - rb_raise(rb_eRuntimeError, "new segment callback not supported on parallel transcription"); - } - - if (ruby_whisper_callback_container_is_present(rwp->progress_callback_container)) { - rb_raise(rb_eRuntimeError, "progress callback not supported on parallel transcription"); - } + // new_segment_callback is called only after multiple threads are joined + // progress_callback is not called when parallel if (ruby_whisper_callback_container_is_present(rwp->encoder_begin_callback_container)) { rb_raise(rb_eRuntimeError, "encoder begin callback not supported on parallel transcription"); @@ -335,11 +330,6 @@ check_thread_safety(ruby_whisper_params *rwp, int n_processors) if (ruby_whisper_callback_container_is_present(rwp->abort_callback_container)) { rb_raise(rb_eRuntimeError, "abort callback not supported on parallel transcription"); } - - VALUE log_callback = rb_iv_get(mWhisper, "@log_callback"); - if (!NIL_P(log_callback)) { - rb_raise(rb_eRuntimeError, "log callback not supported for parallel transcription"); - } } static void register_callbacks(ruby_whisper_params * rwp, VALUE * context, ruby_whisper_abort_callback_user_data *abort_callback_user_data) { From b3a8675911a3269bdd54b3c4c840ef33c17970c0 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Mon, 15 Jun 2026 09:09:51 +0900 Subject: [PATCH 114/118] Remove outdated comment --- bindings/ruby/ext/ruby_whisper_params.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/bindings/ruby/ext/ruby_whisper_params.c b/bindings/ruby/ext/ruby_whisper_params.c index bad3e0db11a..f38e9bde3ea 100644 --- a/bindings/ruby/ext/ruby_whisper_params.c +++ b/bindings/ruby/ext/ruby_whisper_params.c @@ -364,9 +364,6 @@ static void set_vad_params(ruby_whisper_params *rwp) rwp->params.vad_params = rwvp->params; } -/* - TODO: Set abort callback to trap SIGINT and SIGTERM -*/ void prepare_transcription(ruby_whisper_params *rwp, VALUE *context, int n_processors, ruby_whisper_abort_callback_user_data *abort_callback_user_data) { From c476f9013000ded2ec62f7189474f899a1842bc3 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Tue, 16 Jun 2026 17:28:49 +0900 Subject: [PATCH 115/118] Skip downloading model if cache exists --- bindings/ruby/lib/whisper/model/uri.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bindings/ruby/lib/whisper/model/uri.rb b/bindings/ruby/lib/whisper/model/uri.rb index fca750278d1..27eaf794d05 100644 --- a/bindings/ruby/lib/whisper/model/uri.rb +++ b/bindings/ruby/lib/whisper/model/uri.rb @@ -41,6 +41,8 @@ def base_cache_dir def cache path = cache_path + return path if cache_path.exist? + headers = {} headers["if-modified-since"] = path.mtime.httpdate if path.exist? request @uri, headers From 7d0545473732fe66c2c2b1350ccef8b3afa19f04 Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 17 Jun 2026 08:32:37 +0900 Subject: [PATCH 116/118] Change Hugging Face URI for Parakeet models --- bindings/ruby/lib/whisper/model/uri.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/ruby/lib/whisper/model/uri.rb b/bindings/ruby/lib/whisper/model/uri.rb index 27eaf794d05..ef92eb901c4 100644 --- a/bindings/ruby/lib/whisper/model/uri.rb +++ b/bindings/ruby/lib/whisper/model/uri.rb @@ -225,7 +225,7 @@ def escaping(path) parakeet-tdt-0.6b-v3-q4_k parakeet-tdt-0.6b-v3-q8_0 ].each do |name| - @pre_converted_models[name] = URI.new("https://huggingface.co/danbev/parakeet-GGUF/resolve/main/ggml-#{name}.bin") + @pre_converted_models[name] = URI.new("https://huggingface.co/ggml-org/parakeet-GGUF/resolve/main/ggml-#{name}.bin") end @coreml_compiled_models = @pre_converted_models.each_with_object({}) {|(name, uri), models| From e0c4daf5a67bb23acae89d3ea9fffe1d9ed7b7fe Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 17 Jun 2026 11:09:11 +0900 Subject: [PATCH 117/118] Bump required Ruby version to 3.3 --- .github/workflows/bindings-ruby.yml | 2 +- bindings/ruby/whispercpp.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/bindings-ruby.yml b/.github/workflows/bindings-ruby.yml index 80a243e4c98..8cdb7a810f7 100644 --- a/.github/workflows/bindings-ruby.yml +++ b/.github/workflows/bindings-ruby.yml @@ -27,6 +27,6 @@ jobs: steps: - uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0 with: - ruby-version: '3.2' + ruby-version: '3.3' - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - run: rake test diff --git a/bindings/ruby/whispercpp.gemspec b/bindings/ruby/whispercpp.gemspec index 2d952222f29..301ecfcc13d 100644 --- a/bindings/ruby/whispercpp.gemspec +++ b/bindings/ruby/whispercpp.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |s| s.test_files = s.files.select {|file| file.start_with? "test/"} s.extensions << 'ext/extconf.rb' - s.required_ruby_version = '>= 3.1.0' + s.required_ruby_version = '>= 3.3.0' #### Documentation and testing. s.homepage = 'https://github.com/ggml-org/whisper.cpp' From e4bcb08d9c6eebaa51393b91fb0d7ba133554e2f Mon Sep 17 00:00:00 2001 From: Kitaiti Makoto Date: Wed, 17 Jun 2026 13:34:41 +0900 Subject: [PATCH 118/118] Fix English --- bindings/ruby/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/ruby/README.md b/bindings/ruby/README.md index 6830bcf4e71..7f6b7d92c09 100644 --- a/bindings/ruby/README.md +++ b/bindings/ruby/README.md @@ -398,7 +398,7 @@ whisper ### Parakeet ### -Now whispercpp supports NVIDIA's ASR model Parakeet. +whispercpp gem now supports NVIDIA's ASR model Parakeet. If you want to use Parakeet instead of Whisper, the API should feel familiar. In most cases, replace `Whisper::Context` and `Whisper::Params` with `Whisper::Parakeet::Context` and `Whisper::Parakeet::Params`, then use `#transcribe`, `#full`, `#each_segment`, and `#each_token` in the same way.