]> git.djapps.eu Git - pkg/ggml/sources/whisper.cpp/commitdiff
ruby : add VAD speech segments API (#3931)
authorKITAITI Makoto <redacted>
Tue, 28 Jul 2026 06:08:44 +0000 (15:08 +0900)
committerGitHub <redacted>
Tue, 28 Jul 2026 06:08:44 +0000 (15:08 +0900)
* Bump version

* Add test for VAD segments API

* Implement VAD segments API

* Add signatures of VAD segments API

* Stop to dependent on mutex_m

* Check segment range for vad_segment_t0/t1

* Remove needless check

* Remove dead codes

* Fix index check for VAD segments

* Remove needless line

* Use NUM2DBL instead of RFLOAT_VALUE for safety

* Fix document

* Check type of samples

* Arrange CMake cache notation for older version

* Remove unnecessary line break

* Check return value of whisper_vad_segments_from_samples

* Fix type of VAD::Segment#start_time and #end_time

* Prevent dangling pointer from Params to VAD::Params

* Fix typos

* Update README

* Make test follow fix of impl

* Use segments_from_samples_body in ruby_whisper_vad_detect

* Run segments_from_samples without GVL

* Remove unnecessary codes

* [skip ci]Remove trailing whitespace

Co-authored-by: Daniel Bevenius <redacted>
---------

Co-authored-by: Daniel Bevenius <redacted>
18 files changed:
bindings/ruby/README.md
bindings/ruby/ext/options.rb
bindings/ruby/ext/ruby_whisper.c
bindings/ruby/ext/ruby_whisper.h
bindings/ruby/ext/ruby_whisper_context.c
bindings/ruby/ext/ruby_whisper_log_settable.h
bindings/ruby/ext/ruby_whisper_params.c
bindings/ruby/ext/ruby_whisper_transcribe.cpp
bindings/ruby/ext/ruby_whisper_vad_context.c
bindings/ruby/ext/ruby_whisper_vad_context_detect.cpp
bindings/ruby/ext/ruby_whisper_vad_params.c
bindings/ruby/ext/ruby_whisper_vad_segment.c
bindings/ruby/lib/whisper/log_settable.rb
bindings/ruby/lib/whisper/model/uri.rb
bindings/ruby/sig/whisper.rbs
bindings/ruby/test/test_vad.rb
bindings/ruby/test/test_vad_context.rb
bindings/ruby/whispercpp.gemspec

index 7f6b7d92c09f8e7225f264e31856211a709ff433..d5ac2336af024637c5af631614b6279c03655371 100644 (file)
@@ -117,7 +117,7 @@ Whisper::Params.new(
     threshold: 1.0, # defaults to 0.5
     min_speech_duration_ms: 500, # defaults to 250
     min_silence_duration_ms: 200, # defaults to 100
-    max_speech_duration_s: 30000, # default is FLT_MAX,
+    max_speech_duration_s: 30000.0, # default is FLT_MAX,
     speech_pad_ms: 50, # defaults to 30
     samples_overlap: 0.5 # defaults to 0.1
   ),
@@ -176,7 +176,7 @@ See whisper.cpp's [README](https://github.com/ggml-org/whisper.cpp/blob/master/R
 Boolean options:
 
 * `-DGGML_BLAS=1` -> `--enable-ggml-blas`
-* `-DWHISER_COREML=OFF` -> `--disable-whisper-coreml`
+* `-DWHISPER_COREML=OFF` -> `--disable-whisper-coreml`
 
 Argument options:
 
@@ -184,7 +184,7 @@ Argument options:
 
 Combination:
 
-* `-DGGML_CUDA=1 -DCMAKE_CUDA_ARCHITECTURES="86"` -> `--enable-ggml-cuda --cmake_cuda-architectures="86"`
+* `-DGGML_CUDA=1 -DCMAKE_CUDA_ARCHITECTURES="86"` -> `--enable-ggml-cuda --cmake-cuda-architectures="86"`
 
 For boolean options like `GGML_CUDA`, the README says `-DGGML_CUDA=1`. You need strip `-D`, prepend `--enable-` for `1` or `ON` (`--disable-` for `0` or `OFF`) and make it kebab-case: `--enable-ggml-cuda`.  
 For options which require arguments like `CMAKE_CUDA_ARCHITECTURES`, the README says `-DCMAKE_CUDA_ARCHITECTURES="86"`. You need strip `-D`, prepend `--`, make it kebab-case, append `=` and append argument: `--cmake-cuda-architectures="86"`.
@@ -478,13 +478,14 @@ Development
     % cd whisper.cpp/bindings/ruby
     % rake test
 
-First call of `rake test` builds an extension and downloads a model for testing. After that, you add tests in `tests` directory and modify `ext/ruby_whisper.cpp`.
+First call of `rake test` builds an extension and downloads a model for testing. After that, you add tests in `test` directory and modify `ext/*.{h,c,cpp}`.
 
 If something seems wrong on build, running `rake clean` solves some cases.
 
 ### Need help ###
 
 * Windows support
+* Check of compilation with various hardware
 * Refinement of C/C++ code, especially memory management
 
 License
index e723af9fd9a2033e5d4a34024d76887b9590778b..c097ccb8ad67435fc2cd609b3ebe141f2d013187 100644 (file)
@@ -105,7 +105,7 @@ class Options
     FileUtils.mkpath File.dirname(cache_path)
     File.open cache_path, "w" do |file|
       @options.reject {|name, (type, value)| value.nil?}.each do |name, (type, value)|
-        line = "set(CACHE{%<name>s} TYPE %<type>s FORCE VALUE %<value>s)" % {
+        line = 'set(%<name>s %<value>s CACHE %<type>s "" FORCE)' %{
           name:,
           type:,
           value: value == true ? "ON" : value == false ? "OFF" : escape_cmake(value)
index 7941b1a99dd2a9fdd70ed1e532dda30a672ff883..bd852a86df729a9867b536d6d7fcabccd0380301 100644 (file)
@@ -133,7 +133,6 @@ void Init_whisper() {
   id_coreml_compiled_models = rb_intern("coreml_compiled_models");
   id_cache = rb_intern("cache");
   id_n_processors = rb_intern("n_processors");
-  id_extended = rb_intern("extended");
   id_start_log_callback_thread = rb_intern("start_log_callback_thread");
   id_log_callback_thread = rb_intern("@log_callback_thread");
   id_alive_p = rb_intern("alive?");
index 10e90674953a0f66c020c8e722136f6dca8efa9d..b977703eeee2c1b1bb82026666571af0a1fd3a57 100644 (file)
@@ -110,6 +110,13 @@ typedef struct {
   int n_samples;
 } ruby_whisper_full_args;
 
+typedef struct segments_from_samples_args {
+  VALUE *context;
+  VALUE *params;
+  float *samples;
+  int n_samples;
+} segments_from_samples_args;
+
 typedef struct ruby_whisper_full_parallel_args {
   VALUE *context;
   VALUE *params;
@@ -149,7 +156,6 @@ typedef struct {
   VALUE context;
 } ruby_whisper_parakeet_model;
 
-extern ID id_extended;
 extern ID id_log_callback_thread;
 extern ID id_start_log_callback_thread;
 extern ID id_alive_p;
index 9e5fc33e726e7ef47c43000ee1b688bd559026c1..0210ac8a98b650246e2bd21a7819c2e4e7c45eaa 100644 (file)
@@ -349,7 +349,11 @@ fill_samples(VALUE rb_args)
 
   if (RB_TYPE_P(*args->src, T_ARRAY)) {
     for (int i = 0; i < args->n_samples; i++) {
-      args->dest[i] = RFLOAT_VALUE(rb_ary_entry(*args->src, i));
+      VALUE sample = rb_ary_entry(*args->src, i);
+      if (!RB_FLOAT_TYPE_P(sample)) {
+        sample = rb_to_float(sample);
+      }
+      args->dest[i] = RFLOAT_VALUE(sample);
     }
   } else {
     // TODO: use rb_block_call
@@ -357,6 +361,9 @@ fill_samples(VALUE rb_args)
     for (int i = 0; i < args->n_samples; i++) {
       // TODO: check if iter is exhausted and raise ArgumentError appropriately
       VALUE sample = rb_funcall(iter, id_next, 0);
+      if (!RB_FLOAT_TYPE_P(sample)) {
+        sample = rb_to_float(sample);
+      }
       args->dest[i] = RFLOAT_VALUE(sample);
     }
   }
@@ -745,6 +752,45 @@ ruby_whisper_full_get_segment_no_speech_prob(VALUE self, VALUE i_segment)
   return DBL2NUM(no_speech_prob);
 }
 
+static VALUE
+ruby_whisper_full_n_vad_segments(VALUE self)
+{
+  ruby_whisper *rw;
+  GetContext(self, rw);
+
+  return INT2NUM(whisper_full_n_vad_segments(rw->context));
+}
+
+static int
+ruby_whisper_full_check_vad_segment_index(const ruby_whisper *rw, const VALUE i_segment)
+{
+    const int c_i_segment = NUM2INT(i_segment);
+    if (c_i_segment < 0 || c_i_segment >= whisper_full_n_vad_segments(rw->context)) {
+      rb_raise(rb_eIndexError, "segment index %d out of range", c_i_segment);
+    }
+    return c_i_segment;
+}
+
+static VALUE
+ruby_whisper_full_get_vad_segment_t0(VALUE self, VALUE i_segment)
+{
+  ruby_whisper *rw;
+  GetContext(self, rw);
+  const int c_i_segment = ruby_whisper_full_check_vad_segment_index(rw, i_segment);
+
+  return LONG2NUM(whisper_full_get_vad_segment_t0(rw->context, c_i_segment));
+}
+
+static VALUE
+ruby_whisper_full_get_vad_segment_t1(VALUE self, VALUE i_segment)
+{
+  ruby_whisper *rw;
+  GetContext(self, rw);
+  const int c_i_segment = ruby_whisper_full_check_vad_segment_index(rw, i_segment);
+
+  return LONG2NUM(whisper_full_get_vad_segment_t1(rw->context, c_i_segment));
+}
+
 // High level API
 
 static VALUE
@@ -830,6 +876,9 @@ init_ruby_whisper_context(VALUE *mWhisper)
   rb_define_method(cContext, "full_get_segment_speaker_turn_next", ruby_whisper_full_get_segment_speaker_turn_next, 1);
   rb_define_method(cContext, "full_get_segment_text", ruby_whisper_full_get_segment_text, 1);
   rb_define_method(cContext, "full_get_segment_no_speech_prob", ruby_whisper_full_get_segment_no_speech_prob, 1);
+  rb_define_method(cContext, "full_n_vad_segments", ruby_whisper_full_n_vad_segments, 0);
+  rb_define_method(cContext, "full_get_vad_segment_t0", ruby_whisper_full_get_vad_segment_t0, 1);
+  rb_define_method(cContext, "full_get_vad_segment_t1", ruby_whisper_full_get_vad_segment_t1, 1);
   rb_define_method(cContext, "full", ruby_whisper_full, -1);
   rb_define_method(cContext, "full_parallel", ruby_whisper_full_parallel, -1);
 
index b98fbac826b2665f843d7a722226f9d01a9f7cfa..04f9877b70afbd2d99f7100eefdce7fbc3251f48 100644 (file)
@@ -41,7 +41,6 @@
   rb_define_singleton_method(mod, "drain_logs", ruby_whisper_##log_queue##_s_drain_logs, 0); \
   rb_define_singleton_method(mod, "log_set", ruby_whisper_##log_queue##_s_log_set, 2); \
   rb_set_end_proc(ruby_whisper_##log_queue##_end_proc, Qnil); \
-  rb_extend_object(mod, mLogSettable); \
-  rb_funcall(mLogSettable, id_extended, 1, mod);
+  rb_extend_object(mod, mLogSettable);
 
 #endif
index f38e9bde3ea719d6daf52bc08be5a676d500d6c8..be874d51a5f631b905bce53be8af0db6cdd0d76b 100644 (file)
@@ -446,7 +446,7 @@ ruby_whisper_params_allocate(VALUE klass)
     rwp->params.vad_model_path = ruby_strdup(rwp->params.vad_model_path);
   }
   rwp->diarize = false;
-  rwp->vad_params = TypedData_Wrap_Struct(cVADParams, &ruby_whisper_vad_params_type, (void *)&rwp->params.vad_params);
+  rwp->vad_params = rb_class_new_instance(0, NULL, cVADParams);
   rwp->new_segment_callback_container = ruby_whisper_callback_container_allocate();
   rwp->progress_callback_container = ruby_whisper_callback_container_allocate();
   rwp->encoder_begin_callback_container = ruby_whisper_callback_container_allocate();
@@ -918,7 +918,7 @@ ruby_whisper_params_set_temperature(VALUE self, VALUE value)
 {
   ruby_whisper_params *rwp;
   TypedData_Get_Struct(self, ruby_whisper_params, &ruby_whisper_params_type, rwp);
-  rwp->params.temperature = RFLOAT_VALUE(value);
+  rwp->params.temperature = NUM2DBL(value);
   return value;
 }
 /*
@@ -943,7 +943,7 @@ ruby_whisper_params_set_max_initial_ts(VALUE self, VALUE value)
 {
   ruby_whisper_params *rwp;
   TypedData_Get_Struct(self, ruby_whisper_params, &ruby_whisper_params_type, rwp);
-  rwp->params.max_initial_ts = RFLOAT_VALUE(value);
+  rwp->params.max_initial_ts = NUM2DBL(value);
   return value;
 }
 /*
@@ -966,7 +966,7 @@ ruby_whisper_params_set_length_penalty(VALUE self, VALUE value)
 {
   ruby_whisper_params *rwp;
   TypedData_Get_Struct(self, ruby_whisper_params, &ruby_whisper_params_type, rwp);
-  rwp->params.length_penalty = RFLOAT_VALUE(value);
+  rwp->params.length_penalty = NUM2DBL(value);
   return value;
 }
 /*
@@ -989,7 +989,7 @@ ruby_whisper_params_set_temperature_inc(VALUE self, VALUE value)
 {
   ruby_whisper_params *rwp;
   TypedData_Get_Struct(self, ruby_whisper_params, &ruby_whisper_params_type, rwp);
-  rwp->params.temperature_inc = RFLOAT_VALUE(value);
+  rwp->params.temperature_inc = NUM2DBL(value);
   return value;
 }
 /*
@@ -1014,7 +1014,7 @@ ruby_whisper_params_set_entropy_thold(VALUE self, VALUE value)
 {
   ruby_whisper_params *rwp;
   TypedData_Get_Struct(self, ruby_whisper_params, &ruby_whisper_params_type, rwp);
-  rwp->params.entropy_thold = RFLOAT_VALUE(value);
+  rwp->params.entropy_thold = NUM2DBL(value);
   return value;
 }
 /*
@@ -1037,7 +1037,7 @@ ruby_whisper_params_set_logprob_thold(VALUE self, VALUE value)
 {
   ruby_whisper_params *rwp;
   TypedData_Get_Struct(self, ruby_whisper_params, &ruby_whisper_params_type, rwp);
-  rwp->params.logprob_thold = RFLOAT_VALUE(value);
+  rwp->params.logprob_thold = NUM2DBL(value);
   return value;
 }
 /*
@@ -1060,7 +1060,7 @@ ruby_whisper_params_set_no_speech_thold(VALUE self, VALUE value)
 {
   ruby_whisper_params *rwp;
   TypedData_Get_Struct(self, ruby_whisper_params, &ruby_whisper_params_type, rwp);
-  rwp->params.no_speech_thold = RFLOAT_VALUE(value);
+  rwp->params.no_speech_thold = NUM2DBL(value);
   return value;
 }
 static VALUE
@@ -1307,6 +1307,7 @@ static VALUE
 ruby_whisper_params_set_vad_params(VALUE self, VALUE value)
 {
   ruby_whisper_params *rwp;
+  rb_check_typeddata(value, &ruby_whisper_vad_params_type);
   TypedData_Get_Struct(self, ruby_whisper_params, &ruby_whisper_params_type, rwp);
   rwp->vad_params = value;
   return value;
index 73f606ca47610982cd6085190d760242f8b8d2cf..699f9c6a0ae0dfa8988cc10684ca6800394acefd 100644 (file)
@@ -15,28 +15,9 @@ extern ID id_call;
 extern ID id_to_path;
 extern ID transcribe_option_names[1];
 
-extern void prepare_transcription(ruby_whisper_params * rwp, VALUE * self, int n_processors);
 extern VALUE full_body(VALUE rb_args);
 extern VALUE full_parallel_body(VALUE rb_args);
 
-typedef struct{
-  struct whisper_context *context;
-  struct whisper_full_params *params;
-  float *samples;
-  size_t n_samples;
-  int n_processors;
-  int result;
-} transcribe_without_gvl_args;
-
-static void*
-transcribe_without_gvl(void *rb_args)
-{
-  transcribe_without_gvl_args *args = (transcribe_without_gvl_args *)rb_args;
-  args->result = whisper_full_parallel(args->context, *args->params, args->samples, args->n_samples, args->n_processors);
-
-  return NULL;
-}
-
 /*
  * transcribe a single file
  * can emit to a block results
index 97c9736b6f425efcdb6f07521b299042f6bef5c1..32320092472025a86d15753abb417a9298f79d8a 100644 (file)
@@ -12,13 +12,6 @@ extern VALUE release_samples(VALUE parsed);
 
 extern VALUE ruby_whisper_vad_segments_s_init(struct whisper_vad_segments *segments);
 
-typedef struct segments_from_samples_args {
-  VALUE *context;
-  VALUE *params;
-  float *samples;
-  int n_samples;
-} segments_from_samples_args;
-
 static size_t
 ruby_whisper_vad_context_memsize(const void *p)
 {
@@ -77,7 +70,24 @@ ruby_whisper_vad_context_initialize(VALUE self, VALUE model_path)
   return Qnil;
 }
 
-static VALUE
+typedef struct {
+  struct whisper_vad_context *context;
+  struct whisper_vad_params *params;
+  float *samples;
+  int n_samples;
+  struct whisper_vad_segments *result;
+} segments_from_samples_without_gvl_args;
+
+static void*
+segments_from_samples_without_gvl(void *rb_args)
+{
+  segments_from_samples_without_gvl_args *args = (segments_from_samples_without_gvl_args *)rb_args;
+  args->result = whisper_vad_segments_from_samples(args->context, *args->params, args->samples, args->n_samples);
+
+  return NULL;
+}
+
+VALUE
 segments_from_samples_body(VALUE rb_args)
 {
   segments_from_samples_args *args = (segments_from_samples_args *)rb_args;
@@ -87,9 +97,19 @@ segments_from_samples_body(VALUE rb_args)
   GetVADContext(*args->context, rwvc);
   GetVADParams(*args->params, rwvp);
 
-  struct whisper_vad_segments *segments = whisper_vad_segments_from_samples(rwvc->context, rwvp->params, args->samples, args->n_samples);
+  segments_from_samples_without_gvl_args segments_from_samples_without_gvl_args = {
+    rwvc->context,
+    &rwvp->params,
+    args->samples,
+    args->n_samples,
+    NULL
+  };
+  rb_thread_call_without_gvl(segments_from_samples_without_gvl, (void *)&segments_from_samples_without_gvl_args, NULL, NULL);
+  if (!segments_from_samples_without_gvl_args.result) {
+    rb_raise(rb_eRuntimeError, "Failed to process audio");
+  }
 
-  return ruby_whisper_vad_segments_s_init(segments);
+  return ruby_whisper_vad_segments_s_init(segments_from_samples_without_gvl_args.result);
 }
 
 static VALUE
index 802b0222dbd27d9497c55323aeac2f3d10170664..862da811aa9f296f1bc1ec0a2c9b5f7b0b35b299 100644 (file)
@@ -16,18 +16,16 @@ extern const rb_data_type_t ruby_whisper_vad_params_type;
 extern const rb_data_type_t ruby_whisper_vad_segments_type;
 
 extern VALUE ruby_whisper_vad_segments_s_init(struct whisper_vad_segments *segments);
+extern VALUE segments_from_samples_body(VALUE rb_args);
 
 VALUE
 ruby_whisper_vad_detect(VALUE self, VALUE file_path, VALUE params) {
   ruby_whisper_vad_context *rwvc;
-  ruby_whisper_vad_params *rwvp;
   std::string cpp_file_path;
   std::vector<float> pcmf32;
   std::vector<std::vector<float>> pcmf32s;
-  whisper_vad_segments *segments;
 
   GetVADContext(self, rwvc);
-  TypedData_Get_Struct(params, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
 
   if (rb_respond_to(file_path, id_to_path)) {
     file_path = rb_funcall(file_path, id_to_path, 0);
@@ -38,12 +36,13 @@ ruby_whisper_vad_detect(VALUE self, VALUE file_path, VALUE params) {
     rb_raise(rb_eRuntimeError, "Failed to open '%s' as WAV file\n", cpp_file_path.c_str());
   }
 
-  segments = whisper_vad_segments_from_samples(rwvc->context, rwvp->params, pcmf32.data(), pcmf32.size());
-  if (segments == nullptr) {
-    rb_raise(rb_eRuntimeError, "Failed to process audio\n");
-  }
-
-  return ruby_whisper_vad_segments_s_init(segments);
+  segments_from_samples_args args = {
+    &self,
+    &params,
+    pcmf32.data(),
+    (int)pcmf32.size(),
+  };
+  return segments_from_samples_body((VALUE)&args);
 }
 
 #ifdef __cplusplus
index 28256650e32beac425164ca03c73e08f76d66260..edcc25d763ebff8ad88cb158d6d38cd7f881b872 100644 (file)
@@ -31,7 +31,7 @@ static ID id_samples_overlap;
 
 const rb_data_type_t ruby_whisper_vad_params_type = {
   "ruby_whisper_vad_params",
-  {0, 0, ruby_whisper_vad_params_memsize,},
+  {0, RUBY_DEFAULT_FREE, ruby_whisper_vad_params_memsize,},
   0, 0,
   0
 };
@@ -56,7 +56,7 @@ ruby_whisper_vad_params_set_threshold(VALUE self, VALUE value)
 {
   ruby_whisper_vad_params *rwvp;
   TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
-  rwvp->params.threshold = RFLOAT_VALUE(value);
+  rwvp->params.threshold = NUM2DBL(value);
   return value;
 }
 
@@ -125,7 +125,7 @@ ruby_whisper_vad_params_set_max_speech_duration_s(VALUE self, VALUE value)
 {
   ruby_whisper_vad_params *rwvp;
   TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
-  rwvp->params.max_speech_duration_s = RFLOAT_VALUE(value);
+  rwvp->params.max_speech_duration_s = NUM2DBL(value);
   return value;
 }
 
@@ -171,7 +171,7 @@ ruby_whisper_vad_params_set_samples_overlap(VALUE self, VALUE value)
 {
   ruby_whisper_vad_params *rwvp;
   TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
-  rwvp->params.samples_overlap = RFLOAT_VALUE(value);
+  rwvp->params.samples_overlap = NUM2DBL(value);
   return value;
 }
 
index 84a007bb7257633652956f3aa686170f146553bf..0db6db6e253c76e2eed3e165d36575a7fb74e7b6 100644 (file)
@@ -68,7 +68,7 @@ ruby_whisper_vad_segment_get_start_time(VALUE self)
   TypedData_Get_Struct(self, ruby_whisper_vad_segment, &ruby_whisper_vad_segment_type, rwvs);
   TypedData_Get_Struct(rwvs->segments, ruby_whisper_vad_segments, &ruby_whisper_vad_segments_type, rwvss);
   t0 = whisper_vad_segments_get_segment_t0(rwvss->segments, rwvs->index);
-  return DBL2NUM(t0 * 10);
+  return LONG2NUM(t0 * 10);
 }
 
 static VALUE
@@ -81,7 +81,7 @@ ruby_whisper_vad_segment_get_end_time(VALUE self)
   TypedData_Get_Struct(self, ruby_whisper_vad_segment, &ruby_whisper_vad_segment_type, rwvs);
   TypedData_Get_Struct(rwvs->segments, ruby_whisper_vad_segments, &ruby_whisper_vad_segments_type, rwvss);
   t1 = whisper_vad_segments_get_segment_t1(rwvss->segments, rwvs->index);
-  return DBL2NUM(t1 * 10);
+  return LONG2NUM(t1 * 10);
 }
 
 static VALUE
index 2f8218d26eef7359b423c1e2cf708d2905ed00df..477aa1f408b4035da7672ed0164c8126b43a7d37 100644 (file)
@@ -1,13 +1,5 @@
-require "mutex_m"
-
 module Whisper
   module LogSettable
-    class << self
-      def extended(base)
-        base.extend Mutex_m
-      end
-    end
-
     private
 
     def start_log_callback_thread
@@ -32,5 +24,10 @@ module Whisper
         end
       }
     end
+
+    def synchronize(&block)
+      @mutex ||= Thread::Mutex.new
+      @mutex.synchronize &block
+    end
   end
 end
index ef92eb901c43560b52d771d5d6f5ff4259494fa8..58db3766dad320e173b2f756980327caba5e42e5 100644 (file)
@@ -44,7 +44,6 @@ module Whisper
         return path if cache_path.exist?
 
         headers = {}
-        headers["if-modified-since"] = path.mtime.httpdate if path.exist?
         request @uri, headers
         path
       end
@@ -54,17 +53,11 @@ module Whisper
           request = Net::HTTP::Get.new(uri, headers)
           http.request request do |response|
             case response
-            when Net::HTTPNotModified
-              # noop
             when Net::HTTPOK
-              return if !response.key?("last-modified") && cache_path.exist?
-
               download response
             when Net::HTTPRedirection
               request URI(response["location"]), headers
             else
-              return if headers.key?("if-modified-since") # Use cache file
-
               raise "#{response.code} #{response.message}\n#{response.body}"
             end
           end
index c12e1fe55e5110afa202f5aa7abf7395f08bb692..f2ebf6185fd4b4587db8866b96e123124d5de0f9 100644 (file)
@@ -131,6 +131,12 @@ module Whisper
 
     def full_get_segment_no_speech_prob: (Integer) -> Float
 
+    def full_n_vad_segments: () -> Integer
+
+    def full_get_vad_segment_t0: (Integer) -> Integer
+
+    def full_get_vad_segment_t1: (Integer) -> Integer
+
     # Run the entire model: PCM -> log mel spectrogram -> encoder -> decoder -> text  
     # Not thread safe for same context  
     # Uses the specified decoding strategy to obtain the text.
index 3b0aedd061adf1d95c16d0366c65daaa7520a221..8d3d94d9a49b590f87e9e3530df741147273836d 100644 (file)
@@ -16,4 +16,13 @@ class TestVAD < TestBase
       assert_match(/ask not what your country can do for you[,.] ask what you can do for your country/i, text)
     end
   end
+
+  def test_vad_segments_api
+    @whisper.transcribe TestBase::AUDIO, @params
+
+    assert_equal 4, @whisper.full_n_vad_segments
+    @whisper.full_n_vad_segments.times do |i|
+      assert @whisper.full_get_vad_segment_t0(i) < @whisper.full_get_vad_segment_t1(i)
+    end
+  end
 end
index b4558d34fafb8b502bb9473a05df54da4ec4a5ab..777724675059962349a8c1e8e3bbc4bee686a574 100644 (file)
@@ -44,8 +44,8 @@ class TestVADContext < TestBase
     assert_instance_of Enumerator, segments.each
 
     segment = segments.each.first
-    assert_instance_of Float, segment.start_time
-    assert_instance_of Float, segment.end_time
+    assert_instance_of Integer, segment.start_time
+    assert_instance_of Integer, segment.end_time
 
     segment => {start_time:, end_time:}
     assert_equal segment.start_time, start_time
index 301ecfcc13d3e0de0c520923fb1c4bfb3ed5dde0..20068aa162c0d09129df97434c9c0ddf4db74d93 100644 (file)
@@ -3,7 +3,7 @@ require_relative "extsources"
 Gem::Specification.new do |s|
   s.name    = "whispercpp"
   s.authors = ["Georgi Gerganov", "Todd A. Fisher"]
-  s.version = '1.3.7'
+  s.version = '1.3.8'
   s.description = %q{High-performance inference of OpenAI's Whisper automatic speech recognition (ASR) model via Ruby}
   s.email   = 'todd.fisher@gmail.com'
   s.extra_rdoc_files = ['LICENSE', 'README.md']