snapshot of https://github.com/ggml-org/whisper.cpp.git @ da54572229bcf64ba367d96c7ef15770376c4280 (2026-09-15)
Some checks failed
Bindings Tests (Go) / ubuntu-22 (push) Failing after 31s
CI (sycl) / ubuntu-22-cmake-sycl (linux/arm/v7, icx, icpx, ON) (push) Failing after 3s
CI (sycl) / ubuntu-22-cmake-sycl (linux/ppc64le, icx, icpx, ON) (push) Failing after 2s
CI (sycl) / ubuntu-22-cmake-sycl-fp16 (linux/amd64, icx, icpx, ON) (push) Failing after 3s
CI (sycl) / ubuntu-22-cmake-sycl-fp16 (linux/arm/v7, icx, icpx, ON) (push) Failing after 3s
CI (sycl) / ubuntu-22-cmake-sycl-fp16 (linux/arm64, icx, icpx, ON) (push) Failing after 3s
CI (sycl) / ubuntu-22-cmake-sycl-fp16 (linux/ppc64le, icx, icpx, ON) (push) Failing after 3s
CI (vad) / vad (push) Failing after 3s
CI (wasm) / emscripten (Release) (push) Failing after 4s
Bindings Tests (Ruby) / ubuntu-22 (push) Failing after 37s
CI (android) / android (push) Failing after 16s
CI (android) / android_java (push) Failing after 3s
CI (clang) / ubuntu-22-clang (linux/amd64, Debug) (push) Failing after 3s
CI (clang) / ubuntu-22-clang (linux/amd64, Release) (push) Failing after 2s
CI (clang) / ubuntu-22-clang (linux/ppc64le, Debug) (push) Failing after 3s
CI (clang) / ubuntu-22-clang (linux/ppc64le, Release) (push) Failing after 3s
CI (cpu) / ggml-ci-x64-cpu-low-perf (push) Failing after 3s
CI (cpu) / ggml-ci-x64-cpu-high-perf (push) Failing after 2s
CI (gcc) / ubuntu-22-gcc (linux/amd64, Debug) (push) Failing after 3s
CI (gcc) / ubuntu-22-gcc (linux/amd64, Release) (push) Failing after 3s
CI (gcc) / ubuntu-22-gcc (linux/ppc64le, Debug) (push) Failing after 3s
CI (gcc) / ubuntu-22-gcc (linux/ppc64le, Release) (push) Failing after 4s
CI (gcc) / ubuntu-22-gcc-arm-v7 (linux/arm/v7, Debug) (push) Failing after 3s
CI (gcc) / ubuntu-22-gcc-arm-v7 (linux/arm/v7, Release) (push) Failing after 3s
CI (quantize) / quantize (push) Failing after 3s
CI (sanitize) / ubuntu-22-gcc-sanitized (ADDRESS) (push) Failing after 3s
CI (sanitize) / ubuntu-22-gcc-sanitized (THREAD) (push) Failing after 3s
CI (sanitize) / ubuntu-22-gcc-sanitized (UNDEFINED) (push) Failing after 2s
CI (sycl) / ubuntu-22-cmake-sycl (linux/amd64, icx, icpx, ON) (push) Failing after 3s
CI (sycl) / ubuntu-22-cmake-sycl (linux/arm64, icx, icpx, ON) (push) Failing after 3s
Examples WASM / deploy-wasm-github-pages (push) Failing after 3s
Examples Tests / addon_node-ubuntu-22 (20.x) (push) Failing after 3s
Examples Tests / addon_node-ubuntu-22 (22.x) (push) Failing after 2s
CI (coreml) / coreml-base-en (push) Has been cancelled
CI (self-hosted) / gpu-vulkan (push) Has been cancelled
CI (self-hosted) / npu-amd-windows (push) Has been cancelled
CI (self-hosted) / gpu-vulkan-nvidia-cm2 (push) Has been cancelled
CI (clang) / ubuntu-22-clang-arm64 (Debug) (push) Has been cancelled
CI (clang) / ubuntu-22-clang-arm64 (Release) (push) Has been cancelled
CI (self-hosted) / gpu-cuda (push) Has been cancelled
CI (self-hosted) / gpu-vulkan-nvidia-cm (push) Has been cancelled
CI (cpu) / ggml-ci-arm64-cpu-low-perf (push) Has been cancelled
CI (cpu) / ggml-ci-arm64-cpu-high-perf (push) Has been cancelled
CI (cpu) / ggml-ci-arm64-cpu-high-perf-sve (push) Has been cancelled
CI (freebsd) / freeBSD-latest (push) Has been cancelled
CI (self-hosted) / gpu-metal (push) Has been cancelled
CI (gcc) / ubuntu-22-gcc-arm64 (Debug) (push) Has been cancelled
CI (gcc) / ubuntu-22-gcc-arm64 (Release) (push) Has been cancelled
CI (macOS) / macOS-latest (generic/platform=iOS) (push) Has been cancelled
CI (macOS) / macOS-latest (generic/platform=macOS) (push) Has been cancelled
CI (macOS) / macOS-latest (generic/platform=tvOS) (push) Has been cancelled
CI (self-hosted) / npu-amd-linux (push) Has been cancelled
CI (windows) / windows-msys2 (Release, clang, clang-x86_64, CLANG64) (push) Has been cancelled
CI (windows) / windows-msys2 (Release, gcc, ucrt-x86_64, UCRT64) (push) Has been cancelled
Publish Docker image / Push Docker image to Docker Hub (map[dockerfile:.devops/main-cuda.Dockerfile platform:linux/amd64 runs_on:ubuntu-24.04 tag:main-cuda]) (push) Has been cancelled
Publish Docker image / Push Docker image to Docker Hub (map[dockerfile:.devops/main-intel.Dockerfile platform:linux/amd64 runs_on:ubuntu-24.04 tag:main-intel]) (push) Has been cancelled
Publish Docker image / Push Docker image to Docker Hub (map[dockerfile:.devops/main-musa.Dockerfile platform:linux/amd64 runs_on:ubuntu-24.04 tag:main-musa]) (push) Has been cancelled
Publish Docker image / Push Docker image to Docker Hub (map[dockerfile:.devops/main-vulkan.Dockerfile platform:linux/amd64 runs_on:ubuntu-24.04 tag:main-vulkan]) (push) Has been cancelled
Publish Docker image / Push Docker image to Docker Hub (map[dockerfile:.devops/main-vulkan.Dockerfile platform:linux/arm64 runs_on:ubuntu-24.04-arm tag:main-vulkan-arm64]) (push) Has been cancelled
Publish Docker image / Push Docker image to Docker Hub (map[dockerfile:.devops/main.Dockerfile platform:linux/amd64 runs_on:ubuntu-24.04 tag:main]) (push) Has been cancelled
Publish Docker image / Push Docker image to Docker Hub (map[dockerfile:.devops/main.Dockerfile platform:linux/arm64 runs_on:ubuntu-24.04-arm tag:main-arm64]) (push) Has been cancelled
Mark inactive issues stale / mark-issues (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-15 17:23:35 +05:00
commit 948ef47106
1955 changed files with 719115 additions and 0 deletions

9
bindings/ruby/ext/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
Makefile
whisper.so
whisper.bundle
whisper.dll
*.o
*.a
sources/*
!sources/CMakeGraphVizOptions.cmake
mkmf.log

View File

@@ -0,0 +1,79 @@
require "tsort"
class Dependencies
include TSort
def initialize(cmake, options)
@cmake = cmake
@options = options
@static_lib_shape = nil
@nodes = {}
@graph = Hash.new {|h, k| h[k] = []}
generate_dot
parse_dot
end
def libs
tsort.filter_map {|node|
label, shape = @nodes[node]
if shape == @static_lib_shape
label.gsub(/\\n\([^)]+\)/, '')
else
nil
end
}.reverse.collect {|lib| "#{prefix(lib)}#{lib}.#{RbConfig::CONFIG['LIBEXT']}"}
end
def to_s
libs.join(" ")
end
def local_libs
to_s
end
private
def dot_path
File.join(__dir__, "build", "whisper.cpp.dot")
end
def generate_dot
system @cmake, "-S", "sources", "-B", "build", *@options.graphviz_cmake_args, "--graphviz", dot_path, *@options, exception: true
end
def parse_dot
File.open(dot_path).each_line do |line|
case line
when /\[\s*label\s*=\s*"Static Library"\s*,\s*shape\s*=\s*(?<shape>\w+)\s*\]/
@static_lib_shape = $~[:shape]
when /\A\s*"(?<node>\w+)"\s*\[\s*label\s*=\s*"(?<label>\S+)"\s*,\s*shape\s*=\s*(?<shape>\w+)\s*\]\s*;\s*\z/
node = $~[:node]
label = $~[:label]
shape = $~[:shape]
@nodes[node] = [label, shape]
when /\A\s*"(?<depender>\w+)"\s*->\s*"(?<dependee>\w+)"/
depender = $~[:depender]
dependee = $~[:dependee]
@graph[depender] << dependee
end
end
end
def prefix(lib)
"lib"
end
def tsort_each_node
@nodes.each_key do |node|
yield node
end
end
def tsort_each_child(node)
@graph[node].each do |child|
yield child
end
end
end

View File

@@ -0,0 +1,17 @@
require_relative "dependencies"
class DependenciesForWindows < Dependencies
def local_libs
libs.collect {|lib| %|"#{lib_path(lib)}"|}.join(" ")
end
private
def prefix(lib)
lib.start_with?("ggml") ? "" : "lib"
end
def lib_path(lib)
File.join(__dir__, lib).tr("\\", "/")
end
end

View File

@@ -0,0 +1,35 @@
require "mkmf"
if RUBY_PLATFORM.match? /mswin|mingw|ucrt/
require_relative "options_for_windows"
require_relative "dependencies_for_windows"
Opts = OptionsForWindows
Deps = DependenciesForWindows
else
require_relative "options"
require_relative "dependencies"
Opts = Options
Deps = Dependencies
end
cmake = find_executable("cmake") || abort
options = Opts.new(cmake)
have_library("gomp") rescue nil
libs = Deps.new(cmake, options)
append_cflags ["-O3", "-march=native"]
$INCFLAGS << " -Isources/include -Isources/ggml/include -Isources/examples"
$LOCAL_LIBS << " #{libs.local_libs}"
$cleanfiles << " build #{libs}"
create_makefile "whisper" do |conf|
conf << <<~EOF
$(TARGET_SO): #{libs}
#{libs}: cmake-targets
cmake-targets:
#{"\t"}"#{cmake}" -S sources -B build #{options}
#{"\t"}"#{cmake}" --build build --config Release --target common whisper parakeet
EOF
end

View File

@@ -0,0 +1,125 @@
require "fileutils"
class Options
def initialize(cmake="cmake")
@cmake = cmake
@options = {}
configure
write_cache_file
end
def to_a
[
"-D", "BUILD_SHARED_LIBS=OFF",
"-D", "WHISPER_BUILD_TESTS=OFF",
"-D", "CMAKE_ARCHIVE_OUTPUT_DIRECTORY=#{__dir__}",
"-D", "CMAKE_POSITION_INDEPENDENT_CODE=ON",
"-C", cache_path
]
end
def to_s
command_line(*to_a)
end
def graphviz_cmake_args
[]
end
private
def cmake_options
@cmake_options ||= cmake_options_output.lines.drop_while {|line| line.chomp != "-- Cache values"}.drop(1)
.filter_map {|line|
option, value = line.chomp.split("=", 2)
name, type = option.split(":", 2)
[
name,
[
type,
type == "BOOL" ? value == "ON" : value
]
]
}.to_h
end
def cmake_options_output
Dir.chdir(__dir__) do
IO.popen([@cmake, "-S", "sources", "-B", "build", "-L"]) {|io| io.read}
end
end
def configure
cmake_options.each_pair do |name, (type, default_value)|
option = option_name(name)
value = type == "BOOL" ? enable_config(option) : arg_config("--#{option}")
@options[name] = [type, value]
end
configure_accelerate
configure_metal
configure_coreml
end
# See ggml/src/ggml-cpu/CMakeLists.txt
def configure_accelerate
if RUBY_PLATFORM.match?(/darwin/) && enabled?("GGML_ACCELERATE")
$LDFLAGS << " -framework Accelerate"
end
end
# See ggml/src/ggml-metal/CMakeLists.txt
def configure_metal
$LDFLAGS << " -framework Foundation -framework Metal -framework MetalKit" if enabled?("GGML_METAL")
end
# See src/CmakeLists.txt
def configure_coreml
if enabled?("WHISPER_COREML")
$LDFLAGS << " -framework Foundation -framework CoreML"
$defs << "-DRUBY_WHISPER_USE_COREML"
end
end
def option_name(name)
name.downcase.gsub("_", "-")
end
def enabled?(option)
op = @options[option]
return false unless op
return false unless op[0] == "BOOL"
if op[1].nil?
cmake_options[option][1]
else
op[1]
end
end
def cache_path
File.join(__dir__, "sources", "Options.cmake")
end
def write_cache_file
FileUtils.mkpath File.dirname(cache_path)
File.open cache_path, "w" do |file|
@options.reject {|name, (type, value)| value.nil?}.each do |name, (type, value)|
line = 'set(%<name>s %<value>s CACHE %<type>s "" FORCE)' %{
name:,
type:,
value: value == true ? "ON" : value == false ? "OFF" : escape_cmake(value)
}
file.puts line
end
end
end
def escape_cmake(str)
str.gsub(/[\\"]/, '\\\\\&')
end
def command_line(*args)
args.collect {|arg| %|"#{arg.to_s.gsub(/[\\"]/, '\\\\\&')}"|}.join(" ")
end
end

View File

@@ -0,0 +1,51 @@
require_relative "options"
class OptionsForWindows < Options
def to_s
command_line(*generator_args, *to_a)
end
def graphviz_cmake_args
generator_args
end
private
def arm?
RbConfig::CONFIG["host_cpu"].to_s.downcase.match?(/\A(?:arm64|aarch64)\z/)
end
def cmake_options_output
Dir.chdir(__dir__) do
IO.popen([@cmake, "-S", "sources", "-B", "build", *generator_args, "-L"]) {|io| io.read}
end
end
def generator_args
generator = cmake_generator
["-G", generator] if generator && !generator.empty?
end
def cmake_generator
return @cmake_generator if defined?(@cmake_generator)
generator = ENV["CMAKE_GENERATOR"]
abort "CMAKE_GENERATOR=#{generator} is unsupported for mingw/ucrt Ruby" if visual_studio_generator_name?(generator)
return @cmake_generator = generator unless generator.nil? || generator.empty?
ninja = find_executable("ninja")
return @cmake_generator = "Ninja" if ninja
make = find_executable("make")
return @cmake_generator = "MSYS Makefiles" if make
mingw32_make = find_executable("mingw32-make")
return @cmake_generator = "MinGW Makefiles" if mingw32_make
@cmake_generator = nil
end
def visual_studio_generator_name?(generator)
generator && generator.start_with?("Visual Studio")
end
end

View File

@@ -0,0 +1,198 @@
#include "ruby_whisper.h"
VALUE mWhisper;
VALUE mLogSettable;
VALUE mVAD;
VALUE mParakeet;
VALUE cContext;
VALUE cParams;
VALUE cVADContext;
VALUE cVADParams;
VALUE cVADSegments;
VALUE cVADSegment;
VALUE cParakeetContext;
VALUE cParakeetContextParams;
VALUE cParakeetParams;
VALUE cParakeetSegment;
VALUE cParakeetModel;
VALUE eError;
VALUE cSegment;
VALUE cToken;
VALUE cModel;
VALUE mOutputContext;
VALUE mOutputSegment;
ID id_to_s;
ID id_call;
ID id___method__;
ID id_to_enum;
ID id_length;
ID id_next;
ID id_new;
ID id_to_path;
ID id_URI;
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;
ID id_log_callback_thread;
ID id_alive_p;
ID id_join;
// High level API
extern VALUE ruby_whisper_segment_allocate(VALUE klass);
extern VALUE init_ruby_whisper_context(VALUE *mWhisper);
extern void init_ruby_whisper_context_params(VALUE *cContext);
extern void init_ruby_whisper_params(VALUE *mWhisper);
extern void init_ruby_whisper_error(VALUE *mWhisper);
extern void init_ruby_whisper_segment(VALUE *mWhisper);
extern void init_ruby_whisper_token(VALUE *mWhisper);
extern void init_ruby_whisper_model(VALUE *mWhisper);
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(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
*/
static VALUE ruby_whisper_s_lang_max_id(VALUE self) {
return INT2NUM(whisper_lang_max_id());
}
/*
* call-seq:
* lang_id(lang_name) -> Integer
*/
static VALUE ruby_whisper_s_lang_id(VALUE self, VALUE lang) {
const char * lang_str = StringValueCStr(lang);
const int id = whisper_lang_id(lang_str);
if (-1 == id) {
rb_raise(rb_eArgError, "language not found: %s", lang_str);
}
return INT2NUM(id);
}
/*
* call-seq:
* lang_str(lang_id) -> String
*/
static VALUE ruby_whisper_s_lang_str(VALUE self, VALUE id) {
const int lang_id = NUM2INT(id);
const char * str = whisper_lang_str(lang_id);
if (NULL == str) {
rb_raise(rb_eIndexError, "id %d outside of language id", lang_id);
}
return rb_str_new2(str);
}
/*
* call-seq:
* lang_str(lang_id) -> String
*/
static VALUE ruby_whisper_s_lang_str_full(VALUE self, VALUE id) {
const int lang_id = NUM2INT(id);
const char * str_full = whisper_lang_str_full(lang_id);
if (NULL == str_full) {
rb_raise(rb_eIndexError, "id %d outside of language id", lang_id);
}
return rb_str_new2(str_full);
}
/*
* call-seq:
* system_info_str -> String
*/
static VALUE ruby_whisper_s_system_info_str(VALUE self) {
return rb_str_new2(whisper_print_system_info());
}
void Init_whisper() {
id_to_s = rb_intern("to_s");
id_call = rb_intern("call");
id___method__ = rb_intern("__method__");
id_to_enum = rb_intern("to_enum");
id_length = rb_intern("length");
id_next = rb_intern("next");
id_new = rb_intern("new");
id_to_path = rb_intern("to_path");
id_URI = rb_intern("URI");
id_pre_converted_models = rb_intern("pre_converted_models");
id_coreml_compiled_models = rb_intern("coreml_compiled_models");
id_cache = rb_intern("cache");
id_n_processors = rb_intern("n_processors");
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?");
id_join = rb_intern("join");
mWhisper = rb_define_module("Whisper");
rb_require("whisper/log_settable");
mLogSettable = rb_path2class("Whisper::LogSettable");
mVAD = rb_define_module_under(mWhisper, "VAD");
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));
rb_define_const(mWhisper, "LOG_LEVEL_INFO", INT2NUM(GGML_LOG_LEVEL_INFO));
rb_define_const(mWhisper, "LOG_LEVEL_WARN", INT2NUM(GGML_LOG_LEVEL_WARN));
rb_define_const(mWhisper, "LOG_LEVEL_ERROR", INT2NUM(GGML_LOG_LEVEL_ERROR));
rb_define_const(mWhisper, "LOG_LEVEL_DEBUG", INT2NUM(GGML_LOG_LEVEL_DEBUG));
rb_define_const(mWhisper, "LOG_LEVEL_CONT", INT2NUM(GGML_LOG_LEVEL_CONT));
rb_define_const(mWhisper, "AHEADS_NONE", INT2NUM(WHISPER_AHEADS_NONE));
rb_define_const(mWhisper, "AHEADS_N_TOP_MOST", INT2NUM(WHISPER_AHEADS_N_TOP_MOST));
rb_define_const(mWhisper, "AHEADS_CUSTOM", INT2NUM(WHISPER_AHEADS_CUSTOM));
rb_define_const(mWhisper, "AHEADS_TINY_EN", INT2NUM(WHISPER_AHEADS_TINY_EN));
rb_define_const(mWhisper, "AHEADS_TINY", INT2NUM(WHISPER_AHEADS_TINY));
rb_define_const(mWhisper, "AHEADS_BASE_EN", INT2NUM(WHISPER_AHEADS_BASE_EN));
rb_define_const(mWhisper, "AHEADS_BASE", INT2NUM(WHISPER_AHEADS_BASE));
rb_define_const(mWhisper, "AHEADS_SMALL_EN", INT2NUM(WHISPER_AHEADS_SMALL_EN));
rb_define_const(mWhisper, "AHEADS_SMALL", INT2NUM(WHISPER_AHEADS_SMALL));
rb_define_const(mWhisper, "AHEADS_MEDIUM_EN", INT2NUM(WHISPER_AHEADS_MEDIUM_EN));
rb_define_const(mWhisper, "AHEADS_MEDIUM", INT2NUM(WHISPER_AHEADS_MEDIUM));
rb_define_const(mWhisper, "AHEADS_LARGE_V1", INT2NUM(WHISPER_AHEADS_LARGE_V1));
rb_define_const(mWhisper, "AHEADS_LARGE_V2", INT2NUM(WHISPER_AHEADS_LARGE_V2));
rb_define_const(mWhisper, "AHEADS_LARGE_V3", INT2NUM(WHISPER_AHEADS_LARGE_V3));
rb_define_const(mWhisper, "AHEADS_LARGE_V3_TURBO", INT2NUM(WHISPER_AHEADS_LARGE_V3_TURBO));
rb_define_singleton_method(mWhisper, "lang_max_id", ruby_whisper_s_lang_max_id, 0);
rb_define_singleton_method(mWhisper, "lang_id", ruby_whisper_s_lang_id, 1);
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);
LOG_SETTABLE_INIT(whisper_log_queue, mWhisper)
cContext = init_ruby_whisper_context(&mWhisper);
init_ruby_whisper_context_params(&cContext);
init_ruby_whisper_params(&mWhisper);
init_ruby_whisper_error(&mWhisper);
init_ruby_whisper_segment(&mWhisper);
init_ruby_whisper_token(&mWhisper);
init_ruby_whisper_model(&mWhisper);
init_ruby_whisper_vad_params(&mVAD);
init_ruby_whisper_vad_segment(&mVAD);
init_ruby_whisper_vad_segments(&mVAD);
init_ruby_whisper_vad_context(&mVAD);
init_ruby_whisper_parakeet(&mWhisper);
rb_require("whisper/model/uri");
rb_include_module(cContext, mOutputContext);
rb_include_module(cSegment, mOutputSegment);
}

View File

@@ -0,0 +1,248 @@
#ifndef RUBY_WHISPER_H
#define RUBY_WHISPER_H
#include <ruby.h>
#include <ruby/version.h>
#include <ruby/util.h>
#include <ruby/thread.h>
#include <ruby/thread_native.h>
#include <ruby/atomic.h>
#include <ruby/memory_view.h>
#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
int ruby_thread_has_gvl_p(void);
#endif
typedef struct {
VALUE *context;
VALUE user_data;
VALUE callback;
VALUE callbacks;
} ruby_whisper_callback_container;
typedef struct ruby_whisper_abort_callback_user_data {
volatile rb_atomic_t is_interrupted;
ruby_whisper_callback_container *callback_container;
} ruby_whisper_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;
bool is_open;
size_t head;
size_t tail;
size_t size;
ruby_whisper_log *logs;
} ruby_whisper_log_queue;
typedef struct {
struct whisper_context *context;
} ruby_whisper;
typedef struct ruby_whisper_context_params {
struct whisper_context_params params;
} ruby_whisper_context_params;
typedef struct {
struct whisper_full_params params;
bool diarize;
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_callback_container *abort_callback_container;
VALUE vad_params;
} ruby_whisper_params;
typedef struct {
struct whisper_vad_params params;
} ruby_whisper_vad_params;
typedef struct {
VALUE context;
int index;
} ruby_whisper_segment;
typedef struct {
whisper_token_data *token_data;
VALUE text;
} ruby_whisper_token;
typedef struct {
VALUE context;
} ruby_whisper_model;
typedef struct {
struct whisper_vad_segments *segments;
} ruby_whisper_vad_segments;
typedef struct {
VALUE segments;
int index;
} ruby_whisper_vad_segment;
typedef struct {
struct whisper_vad_context *context;
} ruby_whisper_vad_context;
typedef struct parsed_samples_t {
float *samples;
int n_samples;
rb_memory_view_t memview;
bool memview_exported;
} parsed_samples_t;
typedef struct {
VALUE *context;
VALUE *params;
float *samples;
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;
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;
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;
typedef struct {
struct parakeet_context_params params;
} ruby_whisper_parakeet_context_params;
typedef struct {
struct parakeet_context *context;
} ruby_whisper_parakeet_context;
typedef struct {
VALUE context;
int index;
} ruby_whisper_parakeet_segment;
typedef struct {
parakeet_token_data *token_data;
VALUE text;
} ruby_whisper_parakeet_token;
typedef struct {
VALUE context;
} ruby_whisper_parakeet_model;
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) { \
rb_raise(rb_eRuntimeError, "Already freed or not initialized"); \
} \
} while (0)
#define GetContextParams(obj, rwcp) do { \
TypedData_Get_Struct((obj), ruby_whisper_context_params, &ruby_whisper_context_params_type, (rwcp)); \
} while (0)
#define GetToken(obj, rwt) do { \
TypedData_Get_Struct((obj), ruby_whisper_token, &ruby_whisper_token_type, (rwt)); \
if ((rwt)->token_data == NULL) { \
rb_raise(rb_eRuntimeError, "Not initialized"); \
} \
} while (0)
#define GetVADContext(obj, rwvc) do { \
TypedData_Get_Struct((obj), ruby_whisper_vad_context, &ruby_whisper_vad_context_type, (rwvc)); \
if ((rwvc)->context == NULL) { \
rb_raise(rb_eRuntimeError, "Already freed or not initialized"); \
} \
} while (0)
#define GetVADParams(obj, rwvp) do { \
TypedData_Get_Struct((obj), ruby_whisper_vad_params, &ruby_whisper_vad_params_type, (rwvp)); \
} while (0)
#define GetVADSegments(obj, rwvss) do { \
TypedData_Get_Struct((obj), ruby_whisper_vad_segments, &ruby_whisper_vad_segments_type, (rwvss)); \
if ((rwvss)->segments == NULL) { \
rb_raise(rb_eRuntimeError, "Not initialized"); \
} \
} 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) { \
rb_raise(rb_eRuntimeError, "Already freed or 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 || \
!(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)
#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)
#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)
#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

View File

@@ -0,0 +1,941 @@
#include "ruby_whisper.h"
#ifdef WORDS_BIGENDIAN
#define IS_BIGENDIAN true
#else
#define IS_BIGENDIAN false
#endif
extern ID id_to_s;
extern ID id___method__;
extern ID id_to_enum;
extern ID id_length;
extern ID id_next;
extern ID id_new;
extern ID id_to_path;
extern ID id_URI;
extern ID id_pre_converted_models;
extern ID id_coreml_compiled_models;
extern ID id_cache;
extern ID id_n_processors;
extern VALUE cContext;
extern VALUE eError;
extern VALUE cModel;
extern const rb_data_type_t ruby_whisper_params_type;
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, ruby_whisper_abort_callback_user_data *abort_callback_user_data);
ID transcribe_option_names[1];
typedef struct fill_samples_args {
float *dest;
VALUE *src;
int n_samples;
} fill_samples_args;
typedef struct full_without_gvl_args {
struct whisper_context *context;
struct whisper_full_params *params;
float *samples;
int n_samples;
int result;
} full_without_gvl_args;
typedef struct full_parallel_without_gvl_args {
struct whisper_context *context;
struct whisper_full_params *params;
float *samples;
int n_samples;
int n_processors;
int result;
} full_parallel_without_gvl_args;
typedef struct full_ubf_args {
ruby_whisper_abort_callback_user_data *abort_callback_user_data;
} full_ubf_args;
static void
ruby_whisper_free(ruby_whisper *rw)
{
if (rw->context) {
whisper_free(rw->context);
rw->context = NULL;
}
}
void
rb_whisper_mark(ruby_whisper *rw)
{
// call rb_gc_mark on any ruby references in rw
}
void
rb_whisper_free(void *p)
{
ruby_whisper *rw = (ruby_whisper *)p;
ruby_whisper_free(rw);
free(rw);
}
static size_t
ruby_whisper_memsize(const void *p)
{
const ruby_whisper *rw = (const ruby_whisper *)p;
size_t size = sizeof(*rw);
if (!rw) {
return 0;
}
if (rw->context) {
size += sizeof(rw->context);
}
return size;
}
const rb_data_type_t ruby_whisper_type = {
"ruby_whisper",
{0, rb_whisper_free, ruby_whisper_memsize,},
0, 0,
0
};
static VALUE
ruby_whisper_allocate(VALUE klass)
{
ruby_whisper *rw;
VALUE obj = TypedData_Make_Struct(klass, ruby_whisper, &ruby_whisper_type, rw);
rw->context = NULL;
return obj;
}
VALUE
ruby_whisper_normalize_model_path(VALUE model_path)
{
VALUE pre_converted_models = rb_funcall(cModel, id_pre_converted_models, 0);
VALUE pre_converted_model = rb_hash_aref(pre_converted_models, model_path);
if (!NIL_P(pre_converted_model)) {
model_path = pre_converted_model;
#ifdef RUBY_WHISPER_USE_COREML
VALUE coreml_converted_models = rb_funcall(cModel, id_coreml_compiled_models, 0);
VALUE coreml_converted_model = rb_hash_aref(coreml_converted_models, pre_converted_model);
if (!NIL_P(coreml_converted_model)) {
rb_funcall(coreml_converted_model, id_cache, 0);
}
#endif
}
else if (TYPE(model_path) == T_STRING) {
const char * model_path_str = StringValueCStr(model_path);
if (strncmp("http://", model_path_str, 7) == 0 || strncmp("https://", model_path_str, 8) == 0) {
VALUE uri_class = rb_const_get(cModel, id_URI);
model_path = rb_class_new_instance(1, &model_path, uri_class);
}
}
else if (rb_obj_is_kind_of(model_path, rb_path2class("URI::HTTP"))) {
VALUE uri_class = rb_const_get(cModel, id_URI);
model_path = rb_class_new_instance(1, &model_path, uri_class);
}
if (rb_respond_to(model_path, id_to_path)) {
model_path = rb_funcall(model_path, id_to_path, 0);
}
return model_path;
}
/*
* call-seq:
* new("base.en") -> Whisper::Context
* new("path/to/model.bin") -> Whisper::Context
* new(Whisper::Model::URI.new("https://example.net/uri/of/model.bin")) -> Whisper::Context
*/
static VALUE
ruby_whisper_initialize(int argc, VALUE *argv, VALUE self)
{
ruby_whisper *rw;
VALUE whisper_model_file_path;
VALUE context_params;
struct whisper_context_params params;
// TODO: we can support init from buffer here too maybe another ruby object to expose
rb_scan_args(argc, argv, "11", &whisper_model_file_path, &context_params);
TypedData_Get_Struct(self, ruby_whisper, &ruby_whisper_type, rw);
whisper_model_file_path = ruby_whisper_normalize_model_path(whisper_model_file_path);
if (!rb_respond_to(whisper_model_file_path, id_to_s)) {
rb_raise(rb_eRuntimeError, "Expected file path to model to initialize Whisper::Context");
}
if (NIL_P(context_params)) {
params = whisper_context_default_params();
} else {
ruby_whisper_context_params *rwcp;
GetContextParams(context_params, rwcp);
params = rwcp->params;
}
rw->context = whisper_init_from_file_with_params(StringValueCStr(whisper_model_file_path), params);
if (rw->context == NULL) {
rb_raise(rb_eRuntimeError, "error: failed to initialize whisper context");
}
return self;
}
/*
* call-seq:
* model_n_vocab -> Integer
*/
VALUE ruby_whisper_model_n_vocab(VALUE self)
{
ruby_whisper *rw;
GetContext(self, rw);
return INT2NUM(whisper_model_n_vocab(rw->context));
}
/*
* call-seq:
* model_n_audio_ctx -> Integer
*/
VALUE ruby_whisper_model_n_audio_ctx(VALUE self)
{
ruby_whisper *rw;
GetContext(self, rw);
return INT2NUM(whisper_model_n_audio_ctx(rw->context));
}
/*
* call-seq:
* model_n_audio_state -> Integer
*/
VALUE ruby_whisper_model_n_audio_state(VALUE self)
{
ruby_whisper *rw;
GetContext(self, rw);
return INT2NUM(whisper_model_n_audio_state(rw->context));
}
/*
* call-seq:
* model_n_audio_head -> Integer
*/
VALUE ruby_whisper_model_n_audio_head(VALUE self)
{
ruby_whisper *rw;
GetContext(self, rw);
return INT2NUM(whisper_model_n_audio_head(rw->context));
}
/*
* call-seq:
* model_n_audio_layer -> Integer
*/
VALUE ruby_whisper_model_n_audio_layer(VALUE self)
{
ruby_whisper *rw;
GetContext(self, rw);
return INT2NUM(whisper_model_n_audio_layer(rw->context));
}
/*
* call-seq:
* model_n_text_ctx -> Integer
*/
VALUE ruby_whisper_model_n_text_ctx(VALUE self)
{
ruby_whisper *rw;
GetContext(self, rw);
return INT2NUM(whisper_model_n_text_ctx(rw->context));
}
/*
* call-seq:
* model_n_text_state -> Integer
*/
VALUE ruby_whisper_model_n_text_state(VALUE self)
{
ruby_whisper *rw;
GetContext(self, rw);
return INT2NUM(whisper_model_n_text_state(rw->context));
}
/*
* call-seq:
* model_n_text_head -> Integer
*/
VALUE ruby_whisper_model_n_text_head(VALUE self)
{
ruby_whisper *rw;
GetContext(self, rw);
return INT2NUM(whisper_model_n_text_head(rw->context));
}
/*
* call-seq:
* model_n_text_layer -> Integer
*/
VALUE ruby_whisper_model_n_text_layer(VALUE self)
{
ruby_whisper *rw;
GetContext(self, rw);
return INT2NUM(whisper_model_n_text_layer(rw->context));
}
/*
* call-seq:
* model_n_mels -> Integer
*/
VALUE ruby_whisper_model_n_mels(VALUE self)
{
ruby_whisper *rw;
GetContext(self, rw);
return INT2NUM(whisper_model_n_mels(rw->context));
}
/*
* call-seq:
* model_ftype -> Integer
*/
VALUE ruby_whisper_model_ftype(VALUE self)
{
ruby_whisper *rw;
GetContext(self, rw);
return INT2NUM(whisper_model_ftype(rw->context));
}
/*
* call-seq:
* model_type -> String
*/
VALUE ruby_whisper_model_type(VALUE self)
{
ruby_whisper *rw;
GetContext(self, rw);
return rb_str_new2(whisper_model_type_readable(rw->context));
}
static bool
check_memory_view(rb_memory_view_t *memview)
{
rb_memory_view_prepare_item_desc(memview);
if (!memview->format) {
rb_warn("format is required");
return false;
}
if (memview->item_desc.length != 1) {
rb_warn("format must be exact one character");
return false;
}
rb_memory_view_item_component_t component = memview->item_desc.components[0];
if (component.offset) {
rb_warn("format has offset");
return false;
}
if (component.repeat != 1) {
rb_warn("format repeated");
return false;
}
switch (component.format) {
case 'f':
// accept
break;
case 'e':
if (IS_BIGENDIAN) {
rb_warn("currently format \"e\" is only supported on little-endian environment");
return false;
}
break;
case 'g':
if (!IS_BIGENDIAN) {
rb_warn("currently format \"g\" is only supported on big-endian environment");
return false;
}
break;
default:
rb_warn("currently only format \"f\", \"e\" on little-endian environment and \"g\" on big-endian environment are supported for MemoryView, but given: %c", component.format);
return false;
}
if (memview->ndim != 1 && !(memview->ndim == 2 && memview->shape[1] == 1)) {
// TODO: Accept ndim == 2 with shape [n_samples, channels] and channels > 1 by averaging the samples in different channels or just taking the first channel
rb_warn("currently only 1 dimensional MemoryView is supported, but given: %zd", memview->ndim);
return false;
}
return true;
}
static VALUE
fill_samples(VALUE rb_args)
{
fill_samples_args *args = (fill_samples_args *)rb_args;
if (RB_TYPE_P(*args->src, T_ARRAY)) {
for (int i = 0; i < args->n_samples; i++) {
VALUE sample = rb_ary_entry(*args->src, i);
if (!RB_FLOAT_TYPE_P(sample)) {
sample = rb_to_float(sample);
}
args->dest[i] = RFLOAT_VALUE(sample);
}
} else {
// TODO: use rb_block_call
VALUE iter = rb_funcall(*args->src, id_to_enum, 1, rb_str_new2("each"));
for (int i = 0; i < args->n_samples; i++) {
// TODO: check if iter is exhausted and raise ArgumentError appropriately
VALUE sample = rb_funcall(iter, id_next, 0);
if (!RB_FLOAT_TYPE_P(sample)) {
sample = rb_to_float(sample);
}
args->dest[i] = RFLOAT_VALUE(sample);
}
}
return Qnil;
}
parsed_samples_t
parse_samples(VALUE *samples, VALUE *n_samples)
{
bool memview_available = rb_memory_view_available_p(*samples);
struct parsed_samples_t parsed = {0};
parsed.memview_exported = false;
const bool is_array = RB_TYPE_P(*samples, T_ARRAY);
if (!NIL_P(*n_samples)) {
parsed.n_samples = NUM2INT(*n_samples);
if (is_array) {
if (RARRAY_LEN(*samples) < parsed.n_samples) {
rb_raise(rb_eArgError, "samples length %ld is less than n_samples %d", RARRAY_LEN(*samples), parsed.n_samples);
}
}
// Should check when samples.respond_to?(:length)?
} else {
if (is_array) {
if (RARRAY_LEN(*samples) > INT_MAX) {
rb_raise(rb_eArgError, "samples are too long");
}
parsed.n_samples = (int)RARRAY_LEN(*samples);
} else if (memview_available) {
bool memview_got = rb_memory_view_get(*samples, &parsed.memview, RUBY_MEMORY_VIEW_FORMAT | RUBY_MEMORY_VIEW_ROW_MAJOR);
if (memview_got) {
parsed.memview_exported = check_memory_view(&parsed.memview);
if (!parsed.memview_exported) {
rb_memory_view_release(&parsed.memview);
parsed.memview = (rb_memory_view_t){0};
}
} else {
// Sometimes MemoryView producers accept only SIMPLE flag even when they provide suitable MemoryView
parsed.memview = (rb_memory_view_t){0};
bool simple_memview_got = rb_memory_view_get(*samples, &parsed.memview, RUBY_MEMORY_VIEW_SIMPLE);
if (simple_memview_got) {
parsed.memview_exported = check_memory_view(&parsed.memview);
if (!parsed.memview_exported) {
rb_memory_view_release(&parsed.memview);
parsed.memview = (rb_memory_view_t){0};
}
}
}
if (parsed.memview_exported) {
ssize_t n_samples_size = parsed.memview.byte_size / parsed.memview.item_size;
if (n_samples_size > INT_MAX) {
rb_memory_view_release(&parsed.memview);
rb_raise(rb_eArgError, "samples are too long: %zd", n_samples_size);
}
parsed.n_samples = (int)n_samples_size;
} else {
rb_warn("unable to get a memory view. falls back to Ruby object");
if (rb_respond_to(*samples, id_length)) {
parsed.n_samples = NUM2INT(rb_funcall(*samples, id_length, 0));
} else {
rb_raise(rb_eArgError, "samples must respond to :length");
}
}
} else if (rb_respond_to(*samples, id_length)) {
parsed.n_samples = NUM2INT(rb_funcall(*samples, id_length, 0));
} else {
rb_raise(rb_eArgError, "samples must respond to :length or be a MemoryView of an array of float when n_samples is not given");
}
}
if (parsed.memview_exported) {
parsed.samples = (float *)parsed.memview.data;
} else {
parsed.samples = ALLOC_N(float, parsed.n_samples);
fill_samples_args args = {
parsed.samples,
samples,
parsed.n_samples,
};
int state;
rb_protect(fill_samples, (VALUE)&args, &state);
if (state) {
xfree(parsed.samples);
rb_jump_tag(state);
}
}
return parsed;
}
VALUE
release_samples(VALUE rb_parsed_args)
{
parsed_samples_t *parsed_args = (parsed_samples_t *)rb_parsed_args;
if (parsed_args->memview_exported) {
rb_memory_view_release(&parsed_args->memview);
} else {
xfree(parsed_args->samples);
}
*parsed_args = (parsed_samples_t){0};
return Qnil;
}
static void*
full_without_gvl(void *rb_args)
{
full_without_gvl_args *args = (full_without_gvl_args *)rb_args;
args->result = whisper_full(args->context, *args->params, args->samples, args->n_samples);
return NULL;
}
static void
full_ubf(void *rb_args)
{
full_ubf_args *args = (full_ubf_args *)rb_args;
RUBY_ATOMIC_SET(args->abort_callback_user_data->is_interrupted, 1);
}
VALUE
full_body(VALUE rb_args)
{
ruby_whisper_full_args *args = (ruby_whisper_full_args *)rb_args;
ruby_whisper *rw;
ruby_whisper_params *rwp;
GetContext(*args->context, rw);
TypedData_Get_Struct(*args->params, ruby_whisper_params, &ruby_whisper_params_type, rwp);
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,
&rwp->params,
args->samples,
args->n_samples,
0,
};
full_ubf_args full_ubf_args = {
&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);
}
/*
* 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.
*
* call-seq:
* full(params, samples, n_samples) -> nil
* full(params, samples) -> nil
*
* 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.
*/
VALUE ruby_whisper_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);
ruby_whisper_full_args args = {
&self,
&argv[0],
parsed.samples,
parsed.n_samples,
};
VALUE rb_result = rb_ensure(full_body, (VALUE)&args, release_samples, (VALUE)&parsed);
const int result = NUM2INT(rb_result);
if (0 == result) {
return self;
} else {
rb_exc_raise(rb_funcall(eError, id_new, 1, result));
}
}
static void*
full_parallel_without_gvl(void *rb_args)
{
full_parallel_without_gvl_args *args = (full_parallel_without_gvl_args *)rb_args;
args->result = whisper_full_parallel(args->context, *args->params, args->samples, args->n_samples, args->n_processors);
return NULL;
}
VALUE
full_parallel_body(VALUE rb_args)
{
ruby_whisper_full_parallel_args *args = (ruby_whisper_full_parallel_args *)rb_args;
ruby_whisper *rw;
ruby_whisper_params *rwp;
GetContext(*args->context, rw);
TypedData_Get_Struct(*args->params, ruby_whisper_params, &ruby_whisper_params_type, rwp);
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,
&rwp->params,
args->samples,
args->n_samples,
args->n_processors,
0,
};
full_ubf_args full_ubf_args = {
&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);
}
/*
* Split the input audio in chunks and process each chunk separately using whisper_full_with_state()
* Result is stored in the default state of the context
* Not thread safe if executed in parallel on the same context.
* It seems this approach can offer some speedup in some cases.
* However, the transcription accuracy can be worse at the beginning and end of each chunk.
*
* call-seq:
* full_parallel(params, samples) -> nil
* full_parallel(params, samples, n_samples) -> nil
* full_parallel(params, samples, n_samples, n_processors) -> nil
* full_parallel(params, samples, nil, n_processors) -> nil
*/
static VALUE
ruby_whisper_full_parallel(int argc, VALUE *argv,VALUE self)
{
if (argc < 2 || argc > 4) {
rb_raise(rb_eArgError, "wrong number of arguments (given %d, expected 2..4)", argc);
}
VALUE n_samples = argc == 2 ? Qnil : argv[2];
int n_processors;
switch (argc) {
case 2:
n_processors = 1;
break;
case 3:
n_processors = 1;
break;
case 4:
n_processors = NUM2INT(argv[3]);
break;
}
struct parsed_samples_t parsed = parse_samples(&argv[1], &n_samples);
const ruby_whisper_full_parallel_args args = {
&self,
&argv[0],
parsed.samples,
parsed.n_samples,
n_processors,
};
const VALUE rb_result = rb_ensure(full_parallel_body, (VALUE)&args, release_samples, (VALUE)&parsed);
const int result = NUM2INT(rb_result);
if (0 == result) {
return self;
} else {
rb_exc_raise(rb_funcall(eError, id_new, 1, result));
}
}
/*
* Number of segments.
*
* call-seq:
* full_n_segments -> Integer
*/
static VALUE
ruby_whisper_full_n_segments(VALUE self)
{
ruby_whisper *rw;
GetContext(self, rw);
return INT2NUM(whisper_full_n_segments(rw->context));
}
/*
* Language ID, which can be converted to string by Whisper.lang_str and Whisper.lang_str_full.
*
* call-seq:
* full_lang_id -> Integer
*/
static VALUE
ruby_whisper_full_lang_id(VALUE self)
{
ruby_whisper *rw;
GetContext(self, rw);
return INT2NUM(whisper_full_lang_id(rw->context));
}
static int ruby_whisper_full_check_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_segments(rw->context)) {
rb_raise(rb_eIndexError, "segment index %d out of range", c_i_segment);
}
return c_i_segment;
}
/*
* Start time of a segment indexed by +segment_index+ in centiseconds (10 times milliseconds).
*
* full_get_segment_t0(3) # => 1668 (16680 ms)
*
* call-seq:
* full_get_segment_t0(segment_index) -> Integer
*/
static VALUE
ruby_whisper_full_get_segment_t0(VALUE self, VALUE i_segment)
{
ruby_whisper *rw;
GetContext(self, rw);
const int c_i_segment = ruby_whisper_full_check_segment_index(rw, i_segment);
const int64_t t0 = whisper_full_get_segment_t0(rw->context, c_i_segment);
return LONG2NUM(t0);
}
/*
* End time of a segment indexed by +segment_index+ in centiseconds (10 times milliseconds).
*
* full_get_segment_t1(3) # => 1668 (16680 ms)
*
* call-seq:
* full_get_segment_t1(segment_index) -> Integer
*/
static VALUE
ruby_whisper_full_get_segment_t1(VALUE self, VALUE i_segment)
{
ruby_whisper *rw;
GetContext(self, rw);
const int c_i_segment = ruby_whisper_full_check_segment_index(rw, i_segment);
const int64_t t1 = whisper_full_get_segment_t1(rw->context, c_i_segment);
return LONG2NUM(t1);
}
/*
* Whether the next segment indexed by +segment_index+ is predicated as a speaker turn.
*
* full_get_segment_speacker_turn_next(3) # => true
*
* call-seq:
* full_get_segment_speacker_turn_next(segment_index) -> bool
*/
static VALUE
ruby_whisper_full_get_segment_speaker_turn_next(VALUE self, VALUE i_segment)
{
ruby_whisper *rw;
GetContext(self, rw);
const int c_i_segment = ruby_whisper_full_check_segment_index(rw, i_segment);
const bool speaker_turn_next = whisper_full_get_segment_speaker_turn_next(rw->context, c_i_segment);
return speaker_turn_next ? Qtrue : Qfalse;
}
/*
* Text of a segment indexed by +segment_index+.
*
* full_get_segment_text(3) # => "ask not what your country can do for you, ..."
*
* call-seq:
* full_get_segment_text(segment_index) -> String
*/
static VALUE
ruby_whisper_full_get_segment_text(VALUE self, VALUE i_segment)
{
ruby_whisper *rw;
GetContext(self, rw);
const int c_i_segment = ruby_whisper_full_check_segment_index(rw, i_segment);
const char * text = whisper_full_get_segment_text(rw->context, c_i_segment);
return rb_str_new2(text);
}
/*
* call-seq:
* full_get_segment_no_speech_prob(segment_index) -> Float
*/
static VALUE
ruby_whisper_full_get_segment_no_speech_prob(VALUE self, VALUE i_segment)
{
ruby_whisper *rw;
GetContext(self, rw);
const int c_i_segment = ruby_whisper_full_check_segment_index(rw, i_segment);
const float no_speech_prob = whisper_full_get_segment_no_speech_prob(rw->context, c_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));
}
static VALUE
ruby_whisper_context_free(VALUE self)
{
ruby_whisper *rw;
GetContext(self, rw);
whisper_free(rw->context);
rw->context = NULL;
return Qnil;
}
// High level API
static VALUE
ruby_whisper_full_get_segment(VALUE self, VALUE i_segment)
{
return rb_whisper_segment_s_new(self, NUM2INT(i_segment));
}
/*
* Yields each Whisper::Segment:
*
* whisper.transcribe("path/to/audio.wav", params)
* whisper.each_segment do |segment|
* puts segment.text
* end
*
* Returns an Enumerator if no block given:
*
* whisper.transcribe("path/to/audio.wav", params)
* enum = whisper.each_segment
* enum.to_a # => [#<Whisper::Segment>, ...]
*
* call-seq:
* each_segment {|segment| ... }
* each_segment -> Enumerator
*/
static VALUE
ruby_whisper_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 *rw;
GetContext(self, rw);
const int n_segments = whisper_full_n_segments(rw->context);
for (int i = 0; i < n_segments; ++i) {
rb_yield(rb_whisper_segment_s_new(self, i));
}
return self;
}
/*
* call-seq:
* model -> Whisper::Model
*/
static VALUE
ruby_whisper_get_model(VALUE self)
{
return rb_whisper_model_s_new(self);
}
VALUE
init_ruby_whisper_context(VALUE *mWhisper)
{
cContext = rb_define_class_under(*mWhisper, "Context", rb_cObject);
transcribe_option_names[0] = id_n_processors;
rb_define_alloc_func(cContext, ruby_whisper_allocate);
rb_define_method(cContext, "initialize", ruby_whisper_initialize, -1);
rb_define_method(cContext, "transcribe", ruby_whisper_transcribe, -1);
rb_define_method(cContext, "model_n_vocab", ruby_whisper_model_n_vocab, 0);
rb_define_method(cContext, "model_n_audio_ctx", ruby_whisper_model_n_audio_ctx, 0);
rb_define_method(cContext, "model_n_audio_state", ruby_whisper_model_n_audio_state, 0);
rb_define_method(cContext, "model_n_audio_head", ruby_whisper_model_n_audio_head, 0);
rb_define_method(cContext, "model_n_audio_layer", ruby_whisper_model_n_audio_layer, 0);
rb_define_method(cContext, "model_n_text_ctx", ruby_whisper_model_n_text_ctx, 0);
rb_define_method(cContext, "model_n_text_state", ruby_whisper_model_n_text_state, 0);
rb_define_method(cContext, "model_n_text_head", ruby_whisper_model_n_text_head, 0);
rb_define_method(cContext, "model_n_text_layer", ruby_whisper_model_n_text_layer, 0);
rb_define_method(cContext, "model_n_mels", ruby_whisper_model_n_mels, 0);
rb_define_method(cContext, "model_ftype", ruby_whisper_model_ftype, 0);
rb_define_method(cContext, "model_type", ruby_whisper_model_type, 0);
rb_define_method(cContext, "full_n_segments", ruby_whisper_full_n_segments, 0);
rb_define_method(cContext, "full_lang_id", ruby_whisper_full_lang_id, 0);
rb_define_method(cContext, "full_get_segment_t0", ruby_whisper_full_get_segment_t0, 1);
rb_define_method(cContext, "full_get_segment_t1", ruby_whisper_full_get_segment_t1, 1);
rb_define_method(cContext, "full_get_segment_speaker_turn_next", ruby_whisper_full_get_segment_speaker_turn_next, 1);
rb_define_method(cContext, "full_get_segment_text", ruby_whisper_full_get_segment_text, 1);
rb_define_method(cContext, "full_get_segment_no_speech_prob", ruby_whisper_full_get_segment_no_speech_prob, 1);
rb_define_method(cContext, "full_n_vad_segments", ruby_whisper_full_n_vad_segments, 0);
rb_define_method(cContext, "full_get_vad_segment_t0", ruby_whisper_full_get_vad_segment_t0, 1);
rb_define_method(cContext, "full_get_vad_segment_t1", ruby_whisper_full_get_vad_segment_t1, 1);
rb_define_method(cContext, "full", ruby_whisper_full, -1);
rb_define_method(cContext, "full_parallel", ruby_whisper_full_parallel, -1);
rb_define_method(cContext, "free", ruby_whisper_context_free, 0);
// High level
rb_define_method(cContext, "full_get_segment", ruby_whisper_full_get_segment, 1);
rb_define_method(cContext, "each_segment", ruby_whisper_each_segment, 0);
rb_define_method(cContext, "model", ruby_whisper_get_model, 0);
return cContext;
}

View File

@@ -0,0 +1,163 @@
#include "ruby_whisper.h"
#define NUM_PARAMS 6
#define DEF_BOOLEAN_ATTR_METHOD(name) \
static VALUE \
ruby_whisper_context_params_get_ ## name(VALUE self) { \
ruby_whisper_context_params *rwcp; \
GetContextParams(self, rwcp); \
return rwcp->params.name ? Qtrue : Qfalse; \
} \
static VALUE \
ruby_whisper_context_params_set_ ## name(VALUE self, VALUE value) { \
ruby_whisper_context_params *rwcp; \
GetContextParams(self, rwcp); \
rwcp->params.name = RTEST(value); \
return value; \
}
#define DEF_INT_ATTR_METHOD(name) \
static VALUE \
ruby_whisper_context_params_get_ ## name(VALUE self) { \
ruby_whisper_context_params *rwcp; \
GetContextParams(self, rwcp); \
return INT2NUM(rwcp->params.name); \
} \
static VALUE \
ruby_whisper_context_params_set_ ## name(VALUE self, VALUE value) { \
ruby_whisper_context_params *rwcp; \
GetContextParams(self, rwcp); \
rwcp->params.name = NUM2INT(value); \
return value; \
}
#define DEFINE_PARAM(param_name, nth) \
id_ ## param_name = rb_intern(#param_name); \
param_names[nth] = id_ ## param_name; \
rb_define_method(cContextParams, #param_name, ruby_whisper_context_params_get_ ## param_name, 0); \
rb_define_method(cContextParams, #param_name "=", ruby_whisper_context_params_set_ ## param_name, 1);
VALUE cContextParams;
static ID param_names[NUM_PARAMS];
static ID id_use_gpu;
static ID id_flash_attn;
static ID id_gpu_device;
static ID id_dtw_token_timestamps;
static ID id_dtw_aheads_preset;
static ID id_dtw_n_top;
static size_t
ruby_whisper_context_params_memsize(const void *p)
{
const ruby_whisper_context_params *rwcp = (ruby_whisper_context_params *)p;
if (!rwcp) {
return 0;
}
return sizeof(ruby_whisper_context_params);
}
const rb_data_type_t ruby_whisper_context_params_type = {
"ruby_whisper_context_params",
{0, RUBY_DEFAULT_FREE, ruby_whisper_context_params_memsize,},
0, 0,
0
};
static VALUE
ruby_whisper_context_params_s_allocate(VALUE klass)
{
ruby_whisper_context_params *rwcp;
return TypedData_Make_Struct(klass, ruby_whisper_context_params, &ruby_whisper_context_params_type, rwcp);
}
DEF_BOOLEAN_ATTR_METHOD(use_gpu);
DEF_BOOLEAN_ATTR_METHOD(flash_attn);
DEF_INT_ATTR_METHOD(gpu_device);
DEF_BOOLEAN_ATTR_METHOD(dtw_token_timestamps);
DEF_INT_ATTR_METHOD(dtw_aheads_preset);
static VALUE
ruby_whisper_context_params_get_dtw_n_top(VALUE self) {
ruby_whisper_context_params *rwcp;
GetContextParams(self, rwcp);
int dtw_n_top = rwcp->params.dtw_n_top;
return dtw_n_top == -1 ? Qnil : INT2NUM(dtw_n_top);
}
static VALUE
ruby_whisper_context_params_set_dtw_n_top(VALUE self, VALUE value) {
ruby_whisper_context_params *rwcp;
GetContextParams(self, rwcp);
rwcp->params.dtw_n_top = NIL_P(value) ? -1 : NUM2INT(value);
return value;
}
#define SET_PARAM_IF_SAME(param_name) \
if (id == id_ ## param_name) { \
ruby_whisper_context_params_set_ ## param_name(self, value); \
continue; \
}
static VALUE
ruby_whisper_context_params_initialize(int argc, VALUE *argv, VALUE self)
{
ruby_whisper_context_params *rwcp;
TypedData_Get_Struct(self, ruby_whisper_context_params, &ruby_whisper_context_params_type, rwcp);
rwcp->params = whisper_context_default_params();
VALUE kw_hash;
rb_scan_args_kw(RB_SCAN_ARGS_KEYWORDS, argc, argv, ":", &kw_hash);
if (NIL_P(kw_hash)) {
return Qnil;
}
VALUE values[NUM_PARAMS] = {Qundef};
rb_get_kwargs(kw_hash, param_names, 0, NUM_PARAMS, values);
ID id;
VALUE value;
for (int i = 0; i < NUM_PARAMS; i++) {
id = param_names[i];
value = values[i];
if (value == Qundef) {
continue;
}
SET_PARAM_IF_SAME(use_gpu)
SET_PARAM_IF_SAME(flash_attn)
SET_PARAM_IF_SAME(gpu_device)
SET_PARAM_IF_SAME(dtw_token_timestamps)
SET_PARAM_IF_SAME(dtw_aheads_preset)
SET_PARAM_IF_SAME(dtw_n_top)
}
return Qnil;
}
#undef SET_PARAM_IF_SAME
void
init_ruby_whisper_context_params(VALUE *cContext)
{
cContextParams = rb_define_class_under(*cContext, "Params", rb_cObject);
rb_define_alloc_func(cContextParams, ruby_whisper_context_params_s_allocate);
rb_define_method(cContextParams, "initialize", ruby_whisper_context_params_initialize, -1);
DEFINE_PARAM(use_gpu, 0)
DEFINE_PARAM(flash_attn, 1)
DEFINE_PARAM(gpu_device, 2)
DEFINE_PARAM(dtw_token_timestamps, 3)
DEFINE_PARAM(dtw_aheads_preset, 4)
DEFINE_PARAM(dtw_n_top, 5)
}
#undef DEFINE_PARAM
#undef DEF_INT_ATTR_METHOD
#undef DEF_BOOLEAN_ATTR_METHOD
#undef NUM_PARAMS

View File

@@ -0,0 +1,52 @@
#include <ruby.h>
extern VALUE eError;
VALUE ruby_whisper_error_initialize(VALUE self, VALUE code)
{
const int c_code = NUM2INT(code);
const char *raw_message;
switch (c_code) {
case -2:
raw_message = "failed to compute log mel spectrogram";
break;
case -3:
raw_message = "failed to auto-detect language";
break;
case -4:
raw_message = "too many decoders requested";
break;
case -5:
raw_message = "audio_ctx is larger than the maximum allowed";
break;
case -6:
raw_message = "failed to encode";
break;
case -7:
raw_message = "whisper_kv_cache_init() failed for self-attention cache";
break;
case -8:
raw_message = "failed to decode";
break;
case -9:
raw_message = "failed to decode";
break;
default:
raw_message = "unknown error";
break;
}
const VALUE message = rb_str_new2(raw_message);
rb_call_super(1, &message);
rb_iv_set(self, "@code", code);
return self;
}
void
init_ruby_whisper_error(VALUE *mWhisper)
{
eError = rb_define_class_under(*mWhisper, "Error", rb_eStandardError);
rb_define_attr(eError, "code", true, false);
rb_define_method(eError, "initialize", ruby_whisper_error_initialize, 1);
}

View File

@@ -0,0 +1,180 @@
#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)
{
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_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 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");
}
ruby_whisper_log log = {
0,
slot,
0,
LOG_QUEUE_CAPACITY,
};
log_queue->logs[i] = log;
}
}
void
ruby_whisper_log_queue_open(ruby_whisper_log_queue *log_queue)
{
rb_nativethread_lock_lock(&log_queue->lock);
log_queue->is_open = true;
rb_native_cond_signal(&log_queue->cond);
rb_nativethread_lock_unlock(&log_queue->lock);
}
void
ruby_whisper_log_queue_close(ruby_whisper_log_queue *log_queue)
{
rb_nativethread_lock_lock(&log_queue->lock);
log_queue->is_open = false;
rb_native_cond_broadcast(&log_queue->cond);
rb_nativethread_lock_unlock(&log_queue->lock);
}
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);
if (!log_queue->is_open) {
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) {
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);
rb_nativethread_lock_unlock(&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_native_cond_broadcast(&log_queue->cond);
}
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_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) {
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;
}

View File

@@ -0,0 +1,46 @@
#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);
#endif

View File

@@ -0,0 +1,231 @@
#include "ruby_whisper.h"
extern const rb_data_type_t ruby_whisper_type;
extern VALUE cModel;
static void rb_whisper_model_mark(void *p) {
ruby_whisper_model *rwm = (ruby_whisper_model *)p;
if (rwm->context) {
rb_gc_mark(rwm->context);
}
}
static size_t
ruby_whisper_model_memsize(const void *p)
{
const ruby_whisper_model *rwm = (const ruby_whisper_model *)p;
size_t size = sizeof(rwm);
if (!rwm) {
return 0;
}
return size;
}
static const rb_data_type_t rb_whisper_model_type = {
"ruby_whisper_model",
{rb_whisper_model_mark, RUBY_DEFAULT_FREE, ruby_whisper_model_memsize,},
0, 0,
0
};
static VALUE ruby_whisper_model_allocate(VALUE klass) {
ruby_whisper_model *rwm;
return TypedData_Make_Struct(klass, ruby_whisper_model, &rb_whisper_model_type, rwm);
}
VALUE rb_whisper_model_s_new(VALUE context) {
ruby_whisper_model *rwm;
const VALUE model = ruby_whisper_model_allocate(cModel);
TypedData_Get_Struct(model, ruby_whisper_model, &rb_whisper_model_type, rwm);
rwm->context = context;
return model;
};
/*
* call-seq:
* n_vocab -> Integer
*/
static VALUE
ruby_whisper_model_n_vocab(VALUE self)
{
ruby_whisper_model *rwm;
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
ruby_whisper *rw;
GetContext(rwm->context, rw);
return INT2NUM(whisper_model_n_vocab(rw->context));
}
/*
* call-seq:
* n_audio_ctx -> Integer
*/
static VALUE
ruby_whisper_model_n_audio_ctx(VALUE self)
{
ruby_whisper_model *rwm;
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
ruby_whisper *rw;
GetContext(rwm->context, rw);
return INT2NUM(whisper_model_n_audio_ctx(rw->context));
}
/*
* call-seq:
* n_audio_state -> Integer
*/
static VALUE
ruby_whisper_model_n_audio_state(VALUE self)
{
ruby_whisper_model *rwm;
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
ruby_whisper *rw;
GetContext(rwm->context, rw);
return INT2NUM(whisper_model_n_audio_state(rw->context));
}
/*
* call-seq:
* n_audio_head -> Integer
*/
static VALUE
ruby_whisper_model_n_audio_head(VALUE self)
{
ruby_whisper_model *rwm;
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
ruby_whisper *rw;
GetContext(rwm->context, rw);
return INT2NUM(whisper_model_n_audio_head(rw->context));
}
/*
* call-seq:
* n_audio_layer -> Integer
*/
static VALUE
ruby_whisper_model_n_audio_layer(VALUE self)
{
ruby_whisper_model *rwm;
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
ruby_whisper *rw;
GetContext(rwm->context, rw);
return INT2NUM(whisper_model_n_audio_layer(rw->context));
}
/*
* call-seq:
* n_text_ctx -> Integer
*/
static VALUE
ruby_whisper_model_n_text_ctx(VALUE self)
{
ruby_whisper_model *rwm;
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
ruby_whisper *rw;
GetContext(rwm->context, rw);
return INT2NUM(whisper_model_n_text_ctx(rw->context));
}
/*
* call-seq:
* n_text_state -> Integer
*/
static VALUE
ruby_whisper_model_n_text_state(VALUE self)
{
ruby_whisper_model *rwm;
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
ruby_whisper *rw;
GetContext(rwm->context, rw);
return INT2NUM(whisper_model_n_text_state(rw->context));
}
/*
* call-seq:
* n_text_head -> Integer
*/
static VALUE
ruby_whisper_model_n_text_head(VALUE self)
{
ruby_whisper_model *rwm;
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
ruby_whisper *rw;
GetContext(rwm->context, rw);
return INT2NUM(whisper_model_n_text_head(rw->context));
}
/*
* call-seq:
* n_text_layer -> Integer
*/
static VALUE
ruby_whisper_model_n_text_layer(VALUE self)
{
ruby_whisper_model *rwm;
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
ruby_whisper *rw;
GetContext(rwm->context, rw);
return INT2NUM(whisper_model_n_text_layer(rw->context));
}
/*
* call-seq:
* n_mels -> Integer
*/
static VALUE
ruby_whisper_model_n_mels(VALUE self)
{
ruby_whisper_model *rwm;
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
ruby_whisper *rw;
GetContext(rwm->context, rw);
return INT2NUM(whisper_model_n_mels(rw->context));
}
/*
* call-seq:
* ftype -> Integer
*/
static VALUE
ruby_whisper_model_ftype(VALUE self)
{
ruby_whisper_model *rwm;
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
ruby_whisper *rw;
GetContext(rwm->context, rw);
return INT2NUM(whisper_model_ftype(rw->context));
}
/*
* call-seq:
* type -> String
*/
static VALUE
ruby_whisper_model_type(VALUE self)
{
ruby_whisper_model *rwm;
TypedData_Get_Struct(self, ruby_whisper_model, &rb_whisper_model_type, rwm);
ruby_whisper *rw;
GetContext(rwm->context, rw);
return rb_str_new2(whisper_model_type_readable(rw->context));
}
void
init_ruby_whisper_model(VALUE *mWhisper)
{
cModel = rb_define_class_under(*mWhisper, "Model", rb_cObject);
rb_define_alloc_func(cModel, ruby_whisper_model_allocate);
rb_define_method(cModel, "n_vocab", ruby_whisper_model_n_vocab, 0);
rb_define_method(cModel, "n_audio_ctx", ruby_whisper_model_n_audio_ctx, 0);
rb_define_method(cModel, "n_audio_state", ruby_whisper_model_n_audio_state, 0);
rb_define_method(cModel, "n_audio_head", ruby_whisper_model_n_audio_head, 0);
rb_define_method(cModel, "n_audio_layer", ruby_whisper_model_n_audio_layer, 0);
rb_define_method(cModel, "n_text_ctx", ruby_whisper_model_n_text_ctx, 0);
rb_define_method(cModel, "n_text_state", ruby_whisper_model_n_text_state, 0);
rb_define_method(cModel, "n_text_head", ruby_whisper_model_n_text_head, 0);
rb_define_method(cModel, "n_text_layer", ruby_whisper_model_n_text_layer, 0);
rb_define_method(cModel, "n_mels", ruby_whisper_model_n_mels, 0);
rb_define_method(cModel, "ftype", ruby_whisper_model_ftype, 0);
rb_define_method(cModel, "type", ruby_whisper_model_type, 0);
}

View File

@@ -0,0 +1,49 @@
#include "ruby_whisper.h"
#include <stdio.h>
#include <unistd.h>
extern VALUE mParakeet;
extern VALUE mLogSettable;
extern VALUE cParakeetContext;
extern VALUE cParakeetSegment;
extern VALUE mOutputContext;
extern VALUE mOutputSegment;
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 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);
static ruby_whisper_log_queue parakeet_log_queue;
LOG_SETTABLE_SETUP(parakeet_log_queue, mParakeet, parakeet_log_set)
static VALUE
ruby_whisper_parakeet_s_system_info_str(VALUE self)
{
return rb_str_new2(parakeet_print_system_info());
}
void
init_ruby_whisper_parakeet(VALUE *mWhisper)
{
mParakeet = rb_define_module_under(*mWhisper, "Parakeet");
rb_define_const(mParakeet, "VERSION", rb_str_new2(parakeet_version()));
LOG_SETTABLE_INIT(parakeet_log_queue, mParakeet)
rb_define_singleton_method(mParakeet, "system_info_str", ruby_whisper_parakeet_s_system_info_str, 0);
init_ruby_whisper_parakeet_params(&mParakeet);
init_ruby_whisper_parakeet_token(&mParakeet);
init_ruby_whisper_parakeet_segment(&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);
rb_include_module(cParakeetSegment, mOutputSegment);
}

View File

@@ -0,0 +1,316 @@
#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;
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_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);
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;
}
xfree(rwpc);
}
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;
}
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;
}
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, init_args->params);
return NULL;
}
static VALUE
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, "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) {
rb_raise(rb_eRuntimeError, "Failed to load model");
}
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)
{
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;
}
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_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);
}
VALUE
ruby_whisper_parakeet_context_full_body(VALUE 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;
GetParakeetParams(*args->params, rwpp);
ruby_whisper_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);
ruby_whisper_full_args args = {
&self,
&argv[0],
parsed.samples,
parsed.n_samples,
};
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, rb_result));
}
}
static VALUE
ruby_whisper_parakeet_free(VALUE self)
{
ruby_whisper_parakeet_context *rwpc;
GetParakeetContext(self, rwpc);
parakeet_free(rwpc->context);
rwpc->context = NULL;
return Qnil;
}
static VALUE
ruby_whisper_parakeet_context_get_model(VALUE self)
{
return ruby_whisper_parakeet_model_s_new(self);
}
VALUE
init_ruby_whisper_parakeet_context(VALUE *mParakeet)
{
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, "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);
rb_define_method(cParakeetContext, "free", ruby_whisper_parakeet_free, 0);
#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)
return cParakeetContext;
}

View File

@@ -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)
}

View File

@@ -0,0 +1,84 @@
#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 (!NIL_P(rwpm->context)) {
rb_gc_mark(rwpm->context);
}
}
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_memsize},
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; \
ruby_whisper_parakeet_context *rwpc; \
GetParakeetModel(self, rwpm); \
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)
}

View File

@@ -0,0 +1,548 @@
#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)
#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) \
ITERATE_NORMAL_CALLBACK_PARAMS(ITERATOR) \
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_CALLBACK_PARAMS(DEF_IDX_USER_DATA)
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)
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, const 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);
}
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)
{
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
ruby_whisper_parakeet_abort_callback(void *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) {
return true;
}
if (!(data->callback_container) || !ruby_whisper_callback_container_is_present(data->callback_container)) {
return false;
}
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
void
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
#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;
}
static void
ruby_whisper_parakeet_params_mark(void *p)
{
ruby_whisper_parakeet_params *rwpp = (ruby_whisper_parakeet_params *)p;
#define MARK_CONTAINER(name) \
if (rwpp->name##_container) { \
ruby_whisper_callback_container_mark(rwpp->name##_container); \
}
ITERATE_CALLBACK_PARAMS(MARK_CONTAINER)
}
static void
ruby_whisper_parakeet_params_free(void *p)
{
ruby_whisper_parakeet_params *rwpp = (ruby_whisper_parakeet_params *)p;
#define FREE_CONTAINER(name) \
if (rwpp->name##_container) { \
xfree(rwpp->name##_container); \
}
ITERATE_CALLBACK_PARAMS(FREE_CONTAINER)
xfree(rwpp);
}
static size_t
ruby_whisper_parakeet_params_memsize(const void *p)
{
const struct ruby_whisper_parakeet_params *params = p;
if (!params) {
return 0;
}
return sizeof(ruby_whisper_parakeet_params);
}
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 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 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 = WRITER(type)(val); \
return val; \
}
#define DEF_CALLBACK_PARAM_ATTR(name) \
static VALUE \
ruby_whisper_parakeet_params_get_##name(VALUE self) \
{ \
ruby_whisper_parakeet_params *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; \
GetParakeetParams(self, rwpp); \
rwpp->CALLBACK_CONTAINER_NAME(name)->callback = (val); \
return val; \
}
#define DEF_USER_DATA_PARAM_ATTR(name) \
static VALUE \
ruby_whisper_parakeet_params_get_##name##_user_data(VALUE self) \
{ \
ruby_whisper_parakeet_params *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; \
GetParakeetParams(self, rwpp); \
rwpp->CALLBACK_CONTAINER_NAME(name)->user_data = val; \
return val; \
}
#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 (NIL_P(rwpp->name##_callback_container->callbacks)) { \
rwpp->name##_callback_container->callbacks = rb_ary_new(); \
} \
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_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;
}
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 value;
ruby_whisper_parakeet_params *rwpp;
int i;
TypedData_Get_Struct(self, ruby_whisper_parakeet_params, &ruby_whisper_parakeet_params_type, rwpp);
#define INIT_CONTAINER(name) rwpp->name##_container = ruby_whisper_callback_container_allocate();
ITERATE_CALLBACK_PARAMS(INIT_CONTAINER)
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);
for (i = 0; i < RUBY_WHISPER_PARAKEET_NUM_PARAMS; i++) {
value = values[i];
if (value == Qundef) {
continue;
}
param_writers[i](self, value);
}
return Qnil;
}
void
init_ruby_whisper_parakeet_params(VALUE *mParakeet)
{
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(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_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)
#define REGISTER_HOOK(name, data) \
rb_define_method(cParakeetParams, "on_" #name, ruby_whisper_parakeet_params_on_##name, 0);
ITERATE_NORMAL_CALLBACK_NAMES(REGISTER_HOOK, _)
rb_define_method(cParakeetParams, "abort_on", ruby_whisper_parakeet_params_abort_on, 0);
}

View File

@@ -0,0 +1,157 @@
#include "ruby_whisper.h"
#define ITERATE_ATTRS(ITERATOR) \
ITERATOR(start_time, t0, TIME) \
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,
};
#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 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_from_index(struct parakeet_context *context, int i_segment, int i_token);
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;
}
return sizeof(*rwps);
}
static const rb_data_type_t ruby_whisper_parakeet_segment_type = {
"ruby_whisper_parakeet_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;
}
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_from_index(rwpc->context, rwps->index, i));
}
return self;
}
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)
}
return hash;
}
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)
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);
}

View File

@@ -0,0 +1,188 @@
#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 = NULL;
rwpt->text = Qnil;
return token;
}
VALUE
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 = ALLOC(parakeet_token_data);
*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
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);
}

View File

@@ -0,0 +1,58 @@
#include "ruby_whisper.h"
#include "common-whisper.h"
#include <string>
#include <vector>
#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 VALUE ruby_whisper_parakeet_context_full_body(VALUE rb_args);
extern ID id_to_path;
extern ID id_new;
extern VALUE eError;
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<float> pcmf32;
std::vector<std::vector<float>> 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);
ruby_whisper_full_args args = {
&self,
&params,
pcmf32.data(),
(int)pcmf32.size(),
};
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, rb_result));
}
}
#ifdef __cplusplus
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,284 @@
#include "ruby_whisper.h"
#define N_KEY_NAMES 6
extern ID id___method__;
extern ID id_to_enum;
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;
extern VALUE cSegment;
extern VALUE ruby_whisper_token_s_init(struct whisper_context *context, int i_segment, int index);
static void
rb_whisper_segment_mark(void *p)
{
ruby_whisper_segment *rws = (ruby_whisper_segment *)p;
rb_gc_mark(rws->context);
}
static size_t
ruby_whisper_segment_memsize(const void *p)
{
const ruby_whisper_segment *rws = (const ruby_whisper_segment *)p;
size_t size = sizeof(rws);
if (!rws) {
return 0;
}
if (rws->index) {
size += sizeof(rws->index);
}
return size;
}
static const rb_data_type_t ruby_whisper_segment_type = {
"ruby_whisper_segment",
{rb_whisper_segment_mark, RUBY_DEFAULT_FREE, ruby_whisper_segment_memsize,},
0, 0,
0
};
VALUE
ruby_whisper_segment_allocate(VALUE klass)
{
ruby_whisper_segment *rws;
return TypedData_Make_Struct(klass, ruby_whisper_segment, &ruby_whisper_segment_type, rws);
}
VALUE
rb_whisper_segment_s_new(VALUE context, int index)
{
ruby_whisper_segment *rws;
const VALUE segment = ruby_whisper_segment_allocate(cSegment);
TypedData_Get_Struct(segment, ruby_whisper_segment, &ruby_whisper_segment_type, rws);
rws->context = context;
rws->index = index;
return segment;
};
/*
* Start time in milliseconds.
*
* call-seq:
* start_time -> Integer
*/
static VALUE
ruby_whisper_segment_get_start_time(VALUE self)
{
ruby_whisper_segment *rws;
TypedData_Get_Struct(self, ruby_whisper_segment, &ruby_whisper_segment_type, rws);
ruby_whisper *rw;
GetContext(rws->context, rw);
const int64_t t0 = whisper_full_get_segment_t0(rw->context, rws->index);
// able to multiply 10 without overflow because to_timestamp() in whisper.cpp does it
return LONG2NUM(t0 * 10);
}
/*
* End time in milliseconds.
*
* call-seq:
* end_time -> Integer
*/
static VALUE
ruby_whisper_segment_get_end_time(VALUE self)
{
ruby_whisper_segment *rws;
TypedData_Get_Struct(self, ruby_whisper_segment, &ruby_whisper_segment_type, rws);
ruby_whisper *rw;
GetContext(rws->context, rw);
const int64_t t1 = whisper_full_get_segment_t1(rw->context, rws->index);
// able to multiply 10 without overflow because to_timestamp() in whisper.cpp does it
return LONG2NUM(t1 * 10);
}
/*
* Whether the next segment is predicted as a speaker turn.
*
* call-seq:
* speaker_turn_next? -> bool
*/
static VALUE
ruby_whisper_segment_get_speaker_turn_next(VALUE self)
{
ruby_whisper_segment *rws;
TypedData_Get_Struct(self, ruby_whisper_segment, &ruby_whisper_segment_type, rws);
ruby_whisper *rw;
GetContext(rws->context, rw);
return whisper_full_get_segment_speaker_turn_next(rw->context, rws->index) ? Qtrue : Qfalse;
}
/*
* call-seq:
* text -> String
*/
static VALUE
ruby_whisper_segment_get_text(VALUE self)
{
ruby_whisper_segment *rws;
TypedData_Get_Struct(self, ruby_whisper_segment, &ruby_whisper_segment_type, rws);
ruby_whisper *rw;
GetContext(rws->context, rw);
const char * text = whisper_full_get_segment_text(rw->context, rws->index);
return rb_str_new2(text);
}
/*
* call-seq:
* no_speech_prob -> Float
*/
static VALUE
ruby_whisper_segment_get_no_speech_prob(VALUE self)
{
ruby_whisper_segment *rws;
TypedData_Get_Struct(self, ruby_whisper_segment, &ruby_whisper_segment_type, rws);
ruby_whisper *rw;
GetContext(rws->context, rw);
return DBL2NUM(whisper_full_get_segment_no_speech_prob(rw->context, rws->index));
}
/*
* Get number of tokens in the segment
*
* call-seq:
* n_tokens -> Integer
*/
static VALUE
ruby_whisper_segment_get_n_tokens(VALUE self)
{
ruby_whisper_segment *rws;
TypedData_Get_Struct(self, ruby_whisper_segment, &ruby_whisper_segment_type, rws);
ruby_whisper *rw;
GetContext(rws->context, rw);
return INT2NUM(whisper_full_n_tokens(rw->context, rws->index));
}
/*
* 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 # => [#<Whisper::Token>, ...]
*
* call-seq:
* each_token {|token| ... }
* each_token -> Enumerator
*/
static VALUE
ruby_whisper_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_segment *rws;
TypedData_Get_Struct(self, ruby_whisper_segment, &ruby_whisper_segment_type, rws);
ruby_whisper *rw;
GetContext(rws->context, rw);
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));
}
return self;
}
/*
* call-seq:
* deconstruct_keys(keys) -> hash
*
* Possible keys: :start_time, :end_time, :text, :no_speech_prob, :speaker_turn_next, :n_tokens
*
* 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
*/
static VALUE
ruby_whisper_segment_deconstruct_keys(VALUE self, VALUE keys)
{
ruby_whisper_segment *rws;
TypedData_Get_Struct(self, ruby_whisper_segment, &ruby_whisper_segment_type, rws);
ruby_whisper *rw;
GetContext(rws->context, rw);
VALUE hash = rb_hash_new();
long n_keys;
if (NIL_P(keys)) {
keys = rb_ary_new3(
N_KEY_NAMES,
sym_start_time,
sym_end_time,
sym_text,
sym_no_speech_prob,
sym_speaker_turn_next
);
n_keys = N_KEY_NAMES;
} else {
n_keys = RARRAY_LEN(keys);
if (n_keys > N_KEY_NAMES) {
return hash;
}
}
for (int i = 0; i < n_keys; i++) {
VALUE key = rb_ary_entry(keys, i);
if (key == sym_start_time) {
rb_hash_aset(hash, key, ruby_whisper_segment_get_start_time(self));
}
if (key == sym_end_time) {
rb_hash_aset(hash, key, ruby_whisper_segment_get_end_time(self));
}
if (key == sym_text) {
rb_hash_aset(hash, key, ruby_whisper_segment_get_text(self));
}
if (key == sym_no_speech_prob) {
rb_hash_aset(hash, key, ruby_whisper_segment_get_no_speech_prob(self));
}
if (key == sym_speaker_turn_next) {
rb_hash_aset(hash, key, ruby_whisper_segment_get_speaker_turn_next(self));
}
if (key == sym_n_tokens) {
rb_hash_aset(hash, key, ruby_whisper_segment_get_n_tokens(self));
}
}
return hash;
}
void
init_ruby_whisper_segment(VALUE *mWhisper)
{
cSegment = rb_define_class_under(*mWhisper, "Segment", rb_cObject);
sym_start_time = ID2SYM(rb_intern("start_time"));
sym_end_time = ID2SYM(rb_intern("end_time"));
sym_text = ID2SYM(rb_intern("text"));
sym_no_speech_prob = ID2SYM(rb_intern("no_speech_prob"));
sym_speaker_turn_next = ID2SYM(rb_intern("speaker_turn_next"));
sym_n_tokens = ID2SYM(rb_intern("n_tokens"));
rb_define_alloc_func(cSegment, ruby_whisper_segment_allocate);
rb_define_method(cSegment, "start_time", ruby_whisper_segment_get_start_time, 0);
rb_define_method(cSegment, "end_time", ruby_whisper_segment_get_end_time, 0);
rb_define_method(cSegment, "speaker_turn_next?", ruby_whisper_segment_get_speaker_turn_next, 0);
rb_define_method(cSegment, "text", ruby_whisper_segment_get_text, 0);
rb_define_method(cSegment, "no_speech_prob", ruby_whisper_segment_get_no_speech_prob, 0);
rb_define_method(cSegment, "n_tokens", ruby_whisper_segment_get_n_tokens, 0);
rb_define_method(cSegment, "each_token", ruby_whisper_segment_each_token, 0);
rb_define_method(cSegment, "deconstruct_keys", ruby_whisper_segment_deconstruct_keys, 1);
}
#undef N_KEY_NAMES

View File

@@ -0,0 +1,371 @@
#include "ruby_whisper.h"
#define N_KEY_NAMES 11
extern VALUE cToken;
extern const rb_data_type_t ruby_whisper_type;
static VALUE sym_id;
static VALUE sym_tid;
static VALUE sym_probability;
static VALUE sym_log_probability;
static VALUE sym_pt;
static VALUE sym_ptsum;
static VALUE sym_t_dtw;
static VALUE sym_voice_length;
static VALUE sym_start_time;
static VALUE sym_end_time;
static VALUE sym_text;
static size_t
ruby_whisper_token_memsize(const void *p)
{
const ruby_whisper_token *rwt = (const ruby_whisper_token *)p;
if (!rwt) {
return 0;
}
size_t size = sizeof(*rwt);
if (rwt->token_data) {
size += sizeof(*rwt->token_data);
}
return size;
}
static void
ruby_whisper_token_mark(void *p)
{
ruby_whisper_token *rwt = (ruby_whisper_token *)p;
rb_gc_mark(rwt->text);
}
static void
ruby_whisper_token_free(void *p)
{
ruby_whisper_token *rwt = (ruby_whisper_token *)p;
if (rwt->token_data) {
xfree(rwt->token_data);
rwt->token_data = NULL;
}
xfree(rwt);
}
static const rb_data_type_t ruby_whisper_token_type = {
"ruby_whisper_token",
{ruby_whisper_token_mark, ruby_whisper_token_free, ruby_whisper_token_memsize,},
0, 0,
0
};
static VALUE
ruby_whisper_token_allocate(VALUE klass)
{
ruby_whisper_token *rwt;
VALUE token = TypedData_Make_Struct(klass, ruby_whisper_token, &ruby_whisper_token_type, rwt);
rwt->token_data = NULL;
rwt->text = Qnil;
return token;
}
VALUE
ruby_whisper_token_s_init(struct whisper_context *context, int i_segment, int i_token)
{
const VALUE token = ruby_whisper_token_allocate(cToken);
ruby_whisper_token *rwt;
TypedData_Get_Struct(token, ruby_whisper_token, &ruby_whisper_token_type, rwt);
rwt->token_data = ALLOC(whisper_token_data);
*(rwt->token_data) = whisper_full_get_token_data(context, i_segment, i_token);
rwt->text = rb_str_new2(whisper_full_get_token_text(context, i_segment, i_token));
return token;
}
/*
* Token ID.
*
* call-seq:
* id -> Integer
*/
static VALUE
ruby_whisper_token_get_id(VALUE self)
{
ruby_whisper_token *rwt;
GetToken(self, rwt);
return INT2NUM(rwt->token_data->id);
}
/*
* Forced timestamp token ID.
*
* call-seq:
* tid -> Integer
*/
static VALUE
ruby_whisper_token_get_tid(VALUE self)
{
ruby_whisper_token *rwt;
GetToken(self, rwt);
return INT2NUM(rwt->token_data->tid);
}
/*
* Probability of the token.
*
* call-seq:
* probability -> Float
*/
static VALUE
ruby_whisper_token_get_p(VALUE self)
{
ruby_whisper_token *rwt;
GetToken(self, rwt);
return DBL2NUM(rwt->token_data->p);
}
/*
* Log probability of the token.
*
* call-seq:
* log_probability -> Float
*/
static VALUE
ruby_whisper_token_get_plog(VALUE self)
{
ruby_whisper_token *rwt;
GetToken(self, rwt);
return DBL2NUM(rwt->token_data->plog);
}
/*
* Probability of the timestamp token.
*
* call-seq:
* pt -> Float
*/
static VALUE
ruby_whisper_token_get_pt(VALUE self)
{
ruby_whisper_token *rwt;
GetToken(self, rwt);
return DBL2NUM(rwt->token_data->pt);
}
/*
* Sum of probability of all timestamp tokens.
*
* call-seq:
* ptsum -> Float
*/
static VALUE
ruby_whisper_token_get_ptsum(VALUE self)
{
ruby_whisper_token *rwt;
GetToken(self, rwt);
return DBL2NUM(rwt->token_data->ptsum);
}
/*
* [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.
*
* call-seq:
* t_dtw -> Integer
*/
static VALUE
ruby_whisper_token_get_t_dtw(VALUE self)
{
ruby_whisper_token *rwt;
GetToken(self, rwt);
return LONG2NUM(rwt->token_data->t_dtw);
}
/*
* Voice length of the token.
*
* call-seq:
* voice_length -> Float
*/
static VALUE
ruby_whisper_token_get_vlen(VALUE self)
{
ruby_whisper_token *rwt;
GetToken(self, rwt);
return DBL2NUM(rwt->token_data->vlen);
}
/*
* Get the token text of the token.
*
* call-seq:
* text -> String
*/
static VALUE
ruby_whisper_token_get_text(VALUE self)
{
ruby_whisper_token *rwt;
GetToken(self, rwt);
return rwt->text;
}
/*
* Start time of the token.
*
* Token-level timestamp data.
* Do not use if you haven't computed token-level timestamps.
*
* call-seq:
* start_time -> Integer
*/
static VALUE
ruby_whisper_token_get_start_time(VALUE self)
{
ruby_whisper_token *rwt;
GetToken(self, rwt);
return LONG2NUM(rwt->token_data->t0 * 10);
}
/*
* End time of the token.
*
* Token-level timestamp data.
* Do not use if you haven't computed token-level timestamps.
*
* call-seq:
* end_time -> Integer
*/
static VALUE
ruby_whisper_token_get_end_time(VALUE self)
{
ruby_whisper_token *rwt;
GetToken(self, rwt);
return LONG2NUM(rwt->token_data->t1 * 10);
}
/*
* call-seq:
* deconstruct_keys(keys) -> hash
*
* Possible keys: :id, :tid, :probability, :log_probability, :pt, :ptsum,
* :t_dtw, :voice_length, :start_time, :end_time, :text
* segment.each_token do |token|
* token => {text:, probability:}
puts "#{text} (#{probability})"
* end
*/
static VALUE ruby_whisper_token_deconstruct_keys(VALUE self, VALUE keys)
{
ruby_whisper_token *rwt;
GetToken(self, rwt);
VALUE hash = rb_hash_new();
long n_keys = 0;
if (NIL_P(keys)) {
keys = rb_ary_new3(
N_KEY_NAMES,
sym_id,
sym_tid,
sym_probability,
sym_log_probability,
sym_pt,
sym_ptsum,
sym_t_dtw,
sym_voice_length,
sym_start_time,
sym_end_time,
sym_text
);
n_keys = N_KEY_NAMES;
} else {
n_keys = RARRAY_LEN(keys);
if (n_keys > N_KEY_NAMES) {
return hash;
}
}
for (int i = 0; i < n_keys; i++) {
VALUE key = rb_ary_entry(keys, i);
if (key == sym_start_time) {
rb_hash_aset(hash, key, ruby_whisper_token_get_start_time(self));
continue;
}
if (key == sym_end_time) {
rb_hash_aset(hash, key, ruby_whisper_token_get_end_time(self));
continue;
}
if (key == sym_text) {
rb_hash_aset(hash, key, ruby_whisper_token_get_text(self));
continue;
}
if (key == sym_probability) {
rb_hash_aset(hash, key, ruby_whisper_token_get_p(self));
continue;
}
if (key == sym_id) {
rb_hash_aset(hash, key, ruby_whisper_token_get_id(self));
continue;
}
if (key == sym_tid) {
rb_hash_aset(hash, key, ruby_whisper_token_get_tid(self));
continue;
}
if (key == sym_log_probability) {
rb_hash_aset(hash, key, ruby_whisper_token_get_plog(self));
continue;
}
if (key == sym_pt) {
rb_hash_aset(hash, key, ruby_whisper_token_get_pt(self));
continue;
}
if (key == sym_ptsum) {
rb_hash_aset(hash, key, ruby_whisper_token_get_ptsum(self));
continue;
}
if (key == sym_t_dtw) {
rb_hash_aset(hash, key, ruby_whisper_token_get_t_dtw(self));
continue;
}
if (key == sym_voice_length) {
rb_hash_aset(hash, key, ruby_whisper_token_get_vlen(self));
continue;
}
}
return hash;
}
void
init_ruby_whisper_token(VALUE *mWhisper)
{
cToken = rb_define_class_under(*mWhisper, "Token", rb_cObject);
rb_define_alloc_func(cToken, ruby_whisper_token_allocate);
sym_id = ID2SYM(rb_intern("id"));
sym_tid = ID2SYM(rb_intern("tid"));
sym_probability = ID2SYM(rb_intern("probability"));
sym_log_probability = ID2SYM(rb_intern("log_probability"));
sym_pt = ID2SYM(rb_intern("pt"));
sym_ptsum = ID2SYM(rb_intern("ptsum"));
sym_t_dtw = ID2SYM(rb_intern("t_dtw"));
sym_voice_length = ID2SYM(rb_intern("voice_length"));
sym_start_time = ID2SYM(rb_intern("start_time"));
sym_end_time = ID2SYM(rb_intern("end_time"));
sym_text = ID2SYM(rb_intern("text"));
rb_define_method(cToken, "id", ruby_whisper_token_get_id, 0);
rb_define_method(cToken, "tid", ruby_whisper_token_get_tid, 0);
rb_define_method(cToken, "probability", ruby_whisper_token_get_p, 0);
rb_define_method(cToken, "log_probability", ruby_whisper_token_get_plog, 0);
rb_define_method(cToken, "pt", ruby_whisper_token_get_pt, 0);
rb_define_method(cToken, "ptsum", ruby_whisper_token_get_ptsum, 0);
rb_define_method(cToken, "t_dtw", ruby_whisper_token_get_t_dtw, 0);
rb_define_method(cToken, "voice_length", ruby_whisper_token_get_vlen, 0);
rb_define_method(cToken, "start_time", ruby_whisper_token_get_start_time, 0);
rb_define_method(cToken, "end_time", ruby_whisper_token_get_end_time, 0);
rb_define_method(cToken, "text", ruby_whisper_token_get_text, 0);
rb_define_method(cToken, "deconstruct_keys", ruby_whisper_token_deconstruct_keys, 1);
}
#undef N_KEY_NAMES

View File

@@ -0,0 +1,104 @@
#include "ruby_whisper.h"
#include "common-whisper.h"
#include <string>
#include <vector>
#ifdef __cplusplus
extern "C" {
#endif
extern const rb_data_type_t ruby_whisper_type;
extern const rb_data_type_t ruby_whisper_params_type;
extern ID id_to_s;
extern ID id_call;
extern ID id_to_path;
extern ID transcribe_option_names[1];
extern VALUE full_body(VALUE rb_args);
extern VALUE full_parallel_body(VALUE rb_args);
/*
* transcribe a single file
* can emit to a block results
*
* params = Whisper::Params.new
* params.duration = 60_000
* whisper.transcribe "path/to/audio.wav", params do |text|
* puts text
* end
*
* call-seq:
* transcribe(path_to_audio, params) {|text| ...}
**/
VALUE
ruby_whisper_transcribe(int argc, VALUE *argv, VALUE self) {
ruby_whisper *rw;
ruby_whisper_params *rwp;
VALUE wave_file_path, blk, params, kws;
VALUE opts[1];
rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS, argc, argv, "2:&", &wave_file_path, &params, &kws, &blk);
rb_get_kwargs(kws, transcribe_option_names, 0, 1, opts);
int n_processors = opts[0] == Qundef ? 1 : NUM2INT(opts[0]);
GetContext(self, rw);
TypedData_Get_Struct(params, ruby_whisper_params, &ruby_whisper_params_type, rwp);
if (!rb_respond_to(wave_file_path, id_to_s)) {
rb_raise(rb_eRuntimeError, "Expected file path to wave file");
}
if (rb_respond_to(wave_file_path, id_to_path)) {
wave_file_path = rb_funcall(wave_file_path, id_to_path, 0);
}
std::string fname_inp = StringValueCStr(wave_file_path);
std::vector<float> pcmf32; // mono-channel F32 PCM
std::vector<std::vector<float>> pcmf32s; // stereo-channel F32 PCM
if (!read_audio_data(fname_inp, pcmf32, pcmf32s, rwp->diarize)) {
fprintf(stderr, "error: failed to open '%s' as WAV file\n", fname_inp.c_str());
return self;
}
VALUE rb_result;
if (n_processors == 1) {
ruby_whisper_full_args args = {
&self,
&params,
pcmf32.data(),
(int)pcmf32.size(),
};
rb_result = full_body((VALUE)&args);
} else {
ruby_whisper_full_parallel_args parallel_args = {
&self,
&params,
pcmf32.data(),
(int)pcmf32.size(),
n_processors,
};
rb_result = full_parallel_body((VALUE)&parallel_args);
}
const int result = NUM2INT(rb_result);
if (result != 0) {
fprintf(stderr, "failed to process audio\n");
return self;
}
if (NIL_P(blk)) {
return self;
}
const int n_segments = whisper_full_n_segments(rw->context);
VALUE output = rb_str_new2("");
for (int i = 0; i < n_segments; ++i) {
const char * text = whisper_full_get_segment_text(rw->context, i);
output = rb_str_concat(output, rb_str_new2(text));
}
rb_funcall(blk, id_call, 1, output);
return self;
}
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,154 @@
#include "ruby_whisper.h"
extern ID id_to_s;
extern VALUE cVADContext;
extern const rb_data_type_t ruby_whisper_vad_params_type;
extern VALUE ruby_whisper_vad_detect(VALUE self, VALUE file_path, VALUE params);
extern VALUE ruby_whisper_normalize_model_path(VALUE model_path);
extern parsed_samples_t parse_samples(VALUE *samples, VALUE *n_samples);
extern VALUE release_samples(VALUE parsed);
extern VALUE ruby_whisper_vad_segments_s_init(struct whisper_vad_segments *segments);
static size_t
ruby_whisper_vad_context_memsize(const void *p)
{
const ruby_whisper_vad_context *rwvc = p;
size_t size = sizeof(rwvc);
if (!rwvc) {
return 0;
}
if (rwvc->context) {
size += sizeof(rwvc->context);
}
return size;
}
static void
ruby_whisper_vad_context_free(void *p)
{
ruby_whisper_vad_context *rwvc = (ruby_whisper_vad_context *)p;
if (rwvc->context) {
whisper_vad_free(rwvc->context);
rwvc->context = NULL;
}
xfree(rwvc);
}
const rb_data_type_t ruby_whisper_vad_context_type = {
"ruby_whisper_vad_context",
{0, ruby_whisper_vad_context_free, ruby_whisper_vad_context_memsize,},
0, 0,
0
};
static VALUE
ruby_whisper_vad_context_s_allocate(VALUE klass)
{
ruby_whisper_vad_context *rwvc;
VALUE obj = TypedData_Make_Struct(klass, ruby_whisper_vad_context, &ruby_whisper_vad_context_type, rwvc);
rwvc->context = NULL;
return obj;
}
static VALUE
ruby_whisper_vad_context_initialize(VALUE self, VALUE model_path)
{
ruby_whisper_vad_context *rwvc;
struct whisper_vad_context *context;
model_path = ruby_whisper_normalize_model_path(model_path);
context = whisper_vad_init_from_file_with_params(StringValueCStr(model_path), whisper_vad_default_context_params());
if (context == NULL) {
rb_raise(rb_eRuntimeError, "Failed to initialize whisper VAD context");
}
TypedData_Get_Struct(self, ruby_whisper_vad_context, &ruby_whisper_vad_context_type, rwvc);
rwvc->context = context;
return Qnil;
}
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;
ruby_whisper_vad_context *rwvc;
ruby_whisper_vad_params *rwvp;
GetVADContext(*args->context, rwvc);
GetVADParams(*args->params, rwvp);
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_from_samples_without_gvl_args.result);
}
static VALUE
ruby_whisper_vad_segments_from_samples(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);
segments_from_samples_args args = {
&self,
&argv[0],
parsed.samples,
parsed.n_samples,
};
VALUE segments = rb_ensure(segments_from_samples_body, (VALUE)&args, release_samples, (VALUE)&parsed);
return segments;
}
static VALUE
ruby_whisper_vad_free(VALUE self)
{
ruby_whisper_vad_context *rwvc;
GetVADContext(self, rwvc);
whisper_vad_free(rwvc->context);
rwvc->context = NULL;
return Qnil;
}
void init_ruby_whisper_vad_context(VALUE *mVAD)
{
cVADContext = rb_define_class_under(*mVAD, "Context", rb_cObject);
rb_define_alloc_func(cVADContext, ruby_whisper_vad_context_s_allocate);
rb_define_method(cVADContext, "initialize", ruby_whisper_vad_context_initialize, 1);
rb_define_method(cVADContext, "segments_from_samples", ruby_whisper_vad_segments_from_samples, -1);
rb_define_method(cVADContext, "detect", ruby_whisper_vad_detect, 2);
rb_define_method(cVADContext, "free", ruby_whisper_vad_free, 0);
}

View File

@@ -0,0 +1,50 @@
#include "ruby_whisper.h"
#include "common-whisper.h"
#include <string>
#include <vector>
#ifdef __cplusplus
extern "C" {
#endif
extern ID id_to_path;
extern VALUE cVADSegments;
extern const rb_data_type_t ruby_whisper_vad_context_type;
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;
std::string cpp_file_path;
std::vector<float> pcmf32;
std::vector<std::vector<float>> pcmf32s;
GetVADContext(self, rwvc);
if (rb_respond_to(file_path, id_to_path)) {
file_path = rb_funcall(file_path, id_to_path, 0);
}
cpp_file_path = StringValueCStr(file_path);
if (!read_audio_data(cpp_file_path, pcmf32, pcmf32s, false)) {
rb_raise(rb_eRuntimeError, "Failed to open '%s' as WAV file\n", cpp_file_path.c_str());
}
segments_from_samples_args args = {
&self,
&params,
pcmf32.data(),
(int)pcmf32.size(),
};
return segments_from_samples_body((VALUE)&args);
}
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,287 @@
#include "ruby_whisper.h"
#define DEFINE_PARAM(param_name, nth) \
id_ ## param_name = rb_intern(#param_name); \
param_names[nth] = id_ ## param_name; \
rb_define_method(cVADParams, #param_name, ruby_whisper_vad_params_get_ ## param_name, 0); \
rb_define_method(cVADParams, #param_name "=", ruby_whisper_vad_params_set_ ## param_name, 1);
#define NUM_PARAMS 6
extern VALUE cVADParams;
static size_t
ruby_whisper_vad_params_memsize(const void *p)
{
const struct ruby_whisper_vad_params *params = p;
size_t size = sizeof(params);
if (!params) {
return 0;
}
return size;
}
static ID param_names[NUM_PARAMS];
static ID id_threshold;
static ID id_min_speech_duration_ms;
static ID id_min_silence_duration_ms;
static ID id_max_speech_duration_s;
static ID id_speech_pad_ms;
static ID id_samples_overlap;
const rb_data_type_t ruby_whisper_vad_params_type = {
"ruby_whisper_vad_params",
{0, RUBY_DEFAULT_FREE, ruby_whisper_vad_params_memsize,},
0, 0,
0
};
static VALUE
ruby_whisper_vad_params_s_allocate(VALUE klass)
{
ruby_whisper_vad_params *rwvp;
VALUE obj = TypedData_Make_Struct(klass, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
rwvp->params = whisper_vad_default_params();
return obj;
}
/*
* Probability threshold to consider as speech.
*
* call-seq:
* threshold = th -> th
*/
static VALUE
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 = NUM2DBL(value);
return value;
}
static VALUE
ruby_whisper_vad_params_get_threshold(VALUE self)
{
ruby_whisper_vad_params *rwvp;
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
return DBL2NUM(rwvp->params.threshold);
}
/*
* Min duration for a valid speech segment.
*
* call-seq:
* min_speech_duration_ms = duration_ms -> duration_ms
*/
static VALUE
ruby_whisper_vad_params_set_min_speech_duration_ms(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.min_speech_duration_ms = NUM2INT(value);
return value;
}
static VALUE
ruby_whisper_vad_params_get_min_speech_duration_ms(VALUE self)
{
ruby_whisper_vad_params *rwvp;
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
return INT2NUM(rwvp->params.min_speech_duration_ms);
}
/*
* Min silence duration to consider speech as ended.
*
* call-seq:
* min_silence_duration_ms = duration_ms -> duration_ms
*/
static VALUE
ruby_whisper_vad_params_set_min_silence_duration_ms(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.min_silence_duration_ms = NUM2INT(value);
return value;
}
static VALUE
ruby_whisper_vad_params_get_min_silence_duration_ms(VALUE self)
{
ruby_whisper_vad_params *rwvp;
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
return INT2NUM(rwvp->params.min_silence_duration_ms);
}
/*
* Max duration of a speech segment before forcing a new segment.
*
* call-seq:
* max_speech_duration_s = duration_s -> duration_s
*/
static VALUE
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 = NUM2DBL(value);
return value;
}
static VALUE
ruby_whisper_vad_params_get_max_speech_duration_s(VALUE self)
{
ruby_whisper_vad_params *rwvp;
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
return DBL2NUM(rwvp->params.max_speech_duration_s);
}
/*
* Padding added before and after speech segments.
*
* call-seq:
* speech_pad_ms = pad_ms -> pad_ms
*/
static VALUE
ruby_whisper_vad_params_set_speech_pad_ms(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.speech_pad_ms = NUM2INT(value);
return value;
}
static VALUE
ruby_whisper_vad_params_get_speech_pad_ms(VALUE self)
{
ruby_whisper_vad_params *rwvp;
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
return INT2NUM(rwvp->params.speech_pad_ms);
}
/*
* Overlap in seconds when copying audio samples from speech segment.
*
* call-seq:
* samples_overlap = overlap -> overlap
*/
static VALUE
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 = NUM2DBL(value);
return value;
}
static VALUE
ruby_whisper_vad_params_get_samples_overlap(VALUE self)
{
ruby_whisper_vad_params *rwvp;
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
return DBL2NUM(rwvp->params.samples_overlap);
}
static VALUE
ruby_whisper_vad_params_equal(VALUE self, VALUE other)
{
ruby_whisper_vad_params *rwvp1;
ruby_whisper_vad_params *rwvp2;
if (self == other) {
return Qtrue;
}
if (!rb_obj_is_kind_of(other, cVADParams)) {
return Qfalse;
}
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp1);
TypedData_Get_Struct(other, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp2);
if (rwvp1->params.threshold != rwvp2->params.threshold) {
return Qfalse;
}
if (rwvp1->params.min_speech_duration_ms != rwvp2->params.min_speech_duration_ms) {
return Qfalse;
}
if (rwvp1->params.min_silence_duration_ms != rwvp2->params.min_silence_duration_ms) {
return Qfalse;
}
if (rwvp1->params.max_speech_duration_s != rwvp2->params.max_speech_duration_s) {
return Qfalse;
}
if (rwvp1->params.speech_pad_ms != rwvp2->params.speech_pad_ms) {
return Qfalse;
}
if (rwvp1->params.samples_overlap != rwvp2->params.samples_overlap) {
return Qfalse;
}
return Qtrue;
}
#define SET_PARAM_IF_SAME(param_name) \
if (id == id_ ## param_name) { \
ruby_whisper_vad_params_set_ ## param_name(self, value); \
continue; \
}
VALUE
ruby_whisper_vad_params_initialize(int argc, VALUE *argv, VALUE self)
{
VALUE kw_hash;
VALUE values[NUM_PARAMS] = {Qundef};
VALUE value;
ruby_whisper_vad_params *rwvp;
ID id;
int i;
TypedData_Get_Struct(self, ruby_whisper_vad_params, &ruby_whisper_vad_params_type, rwvp);
rb_scan_args_kw(RB_SCAN_ARGS_KEYWORDS, argc, argv, ":", &kw_hash);
if (NIL_P(kw_hash)) {
return self;
}
rb_get_kwargs(kw_hash, param_names, 0, NUM_PARAMS, values);
for (i = 0; i < NUM_PARAMS; i++) {
id = param_names[i];
value = values[i];
if (value == Qundef) {
continue;
}
SET_PARAM_IF_SAME(threshold)
SET_PARAM_IF_SAME(min_speech_duration_ms)
SET_PARAM_IF_SAME(min_silence_duration_ms)
SET_PARAM_IF_SAME(max_speech_duration_s)
SET_PARAM_IF_SAME(speech_pad_ms)
SET_PARAM_IF_SAME(samples_overlap)
}
return self;
}
#undef SET_PARAM_IF_SAME
void
init_ruby_whisper_vad_params(VALUE *mVAD)
{
cVADParams = rb_define_class_under(*mVAD, "Params", rb_cObject);
rb_define_alloc_func(cVADParams, ruby_whisper_vad_params_s_allocate);
rb_define_method(cVADParams, "initialize", ruby_whisper_vad_params_initialize, -1);
DEFINE_PARAM(threshold, 0)
DEFINE_PARAM(min_speech_duration_ms, 1)
DEFINE_PARAM(min_silence_duration_ms, 2)
DEFINE_PARAM(max_speech_duration_s, 3)
DEFINE_PARAM(speech_pad_ms, 4)
DEFINE_PARAM(samples_overlap, 5)
rb_define_method(cVADParams, "==", ruby_whisper_vad_params_equal, 1);
}
#undef DEFINE_PARAM
#undef NUM_PARAMS

View File

@@ -0,0 +1,138 @@
#include "ruby_whisper.h"
#define N_KEY_NAMES 2
extern VALUE cVADSegment;
extern const rb_data_type_t ruby_whisper_vad_segments_type;
static VALUE sym_start_time;
static VALUE sym_end_time;
static void
rb_whisper_vad_segment_mark(void *p)
{
ruby_whisper_vad_segment *rwvs = (ruby_whisper_vad_segment *)p;
rb_gc_mark(rwvs->segments);
}
static size_t
ruby_whisper_vad_segment_memsize(const void *p)
{
const ruby_whisper_vad_segment *rwvs = p;
size_t size = sizeof(rwvs);
if (!rwvs) {
return 0;
}
if (rwvs->index) {
size += sizeof(rwvs->index);
}
return size;
}
static const rb_data_type_t ruby_whisper_vad_segment_type = {
"ruby_whisper_vad_segment",
{rb_whisper_vad_segment_mark, RUBY_DEFAULT_FREE, ruby_whisper_vad_segment_memsize,},
0, 0,
0
};
static VALUE
ruby_whisper_vad_segment_s_allocate(VALUE klass)
{
ruby_whisper_vad_segment *rwvs;
VALUE obj = TypedData_Make_Struct(klass, ruby_whisper_vad_segment, &ruby_whisper_vad_segment_type, rwvs);
rwvs->segments = Qnil;
rwvs->index = -1;
return obj;
}
VALUE
rb_whisper_vad_segment_s_new(VALUE segments, int index)
{
ruby_whisper_vad_segment *rwvs;
const VALUE segment = ruby_whisper_vad_segment_s_allocate(cVADSegment);
TypedData_Get_Struct(segment, ruby_whisper_vad_segment, &ruby_whisper_vad_segment_type, rwvs);
rwvs->segments = segments;
rwvs->index = index;
return segment;
}
static VALUE
ruby_whisper_vad_segment_get_start_time(VALUE self)
{
ruby_whisper_vad_segment *rwvs;
ruby_whisper_vad_segments *rwvss;
float t0;
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 LONG2NUM(t0 * 10);
}
static VALUE
ruby_whisper_vad_segment_get_end_time(VALUE self)
{
ruby_whisper_vad_segment *rwvs;
ruby_whisper_vad_segments *rwvss;
float t1;
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 LONG2NUM(t1 * 10);
}
static VALUE
ruby_whisper_vad_segment_deconstruct_keys(VALUE self, VALUE keys)
{
ruby_whisper_vad_segment *rwvs;
ruby_whisper_vad_segments *rwvss;
VALUE hash, key;
long n_keys;
int i;
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);
hash = rb_hash_new();
if (NIL_P(keys)) {
keys = rb_ary_new3(
N_KEY_NAMES,
sym_start_time,
sym_end_time
);
n_keys = N_KEY_NAMES;
} else {
n_keys = RARRAY_LEN(keys);
if (n_keys > N_KEY_NAMES) {
return hash;
}
}
for (i = 0; i < n_keys; i++) {
key = rb_ary_entry(keys, i);
if (key == sym_start_time) {
rb_hash_aset(hash, key, ruby_whisper_vad_segment_get_start_time(self));
}
if (key == sym_end_time) {
rb_hash_aset(hash, key, ruby_whisper_vad_segment_get_end_time(self));
}
}
return hash;
}
void
init_ruby_whisper_vad_segment(VALUE *mVAD)
{
cVADSegment = rb_define_class_under(*mVAD, "Segment", rb_cObject);
sym_start_time = ID2SYM(rb_intern("start_time"));
sym_end_time = ID2SYM(rb_intern("end_time"));
rb_define_alloc_func(cVADSegment, ruby_whisper_vad_segment_s_allocate);
rb_define_method(cVADSegment, "start_time", ruby_whisper_vad_segment_get_start_time, 0);
rb_define_method(cVADSegment, "end_time", ruby_whisper_vad_segment_get_end_time, 0);
rb_define_method(cVADSegment, "deconstruct_keys", ruby_whisper_vad_segment_deconstruct_keys, 1);
}

View File

@@ -0,0 +1,105 @@
#include "ruby_whisper.h"
extern ID id___method__;
extern ID id_to_enum;
extern VALUE cVADSegments;
extern VALUE rb_whisper_vad_segment_s_new(VALUE segments, int index);
static size_t
ruby_whisper_vad_segments_memsize(const void *p)
{
const ruby_whisper_vad_segments *rwvss = p;
size_t size = sizeof(rwvss);
if (!rwvss) {
return 0;
}
if (rwvss->segments) {
size += sizeof(rwvss->segments);
}
return size;
}
static void
ruby_whisper_vad_segments_free(void *p)
{
ruby_whisper_vad_segments *rwvss = (ruby_whisper_vad_segments *)p;
if (rwvss->segments) {
whisper_vad_free_segments(rwvss->segments);
rwvss->segments = NULL;
}
xfree(rwvss);
}
const rb_data_type_t ruby_whisper_vad_segments_type = {
"ruby_whisper_vad_segments",
{0, ruby_whisper_vad_segments_free, ruby_whisper_vad_segments_memsize,},
0, 0,
0
};
static VALUE
ruby_whisper_vad_segments_s_allocate(VALUE klass)
{
ruby_whisper_vad_segments *rwvss;
VALUE obj = TypedData_Make_Struct(klass, ruby_whisper_vad_segments, &ruby_whisper_vad_segments_type, rwvss);
rwvss->segments = NULL;
return obj;
}
VALUE
ruby_whisper_vad_segments_s_init(struct whisper_vad_segments *segments)
{
VALUE rb_segments;
ruby_whisper_vad_segments *rwvss;
rb_segments = ruby_whisper_vad_segments_s_allocate(cVADSegments);
TypedData_Get_Struct(rb_segments, ruby_whisper_vad_segments, &ruby_whisper_vad_segments_type, rwvss);
rwvss->segments = segments;
return rb_segments;
}
static VALUE
ruby_whisper_vad_segments_each(VALUE self)
{
ruby_whisper_vad_segments *rwvss;
VALUE method_name;
int n_segments, i;
if (!rb_block_given_p()) {
method_name = rb_funcall(self, id___method__, 0);
return rb_funcall(self, id_to_enum, 1, method_name);
}
GetVADSegments(self, rwvss);
n_segments = whisper_vad_segments_n_segments(rwvss->segments);
for (i = 0; i < n_segments; ++i) {
rb_yield(rb_whisper_vad_segment_s_new(self, i));
}
return self;
}
static VALUE
ruby_whisper_vad_segments_get_length(VALUE self)
{
ruby_whisper_vad_segments *rwvss;
int n_segments;
GetVADSegments(self, rwvss);
n_segments = whisper_vad_segments_n_segments(rwvss->segments);
return INT2NUM(n_segments);
}
void
init_ruby_whisper_vad_segments(VALUE *mVAD)
{
cVADSegments = rb_define_class_under(*mVAD, "Segments", rb_cObject);
rb_define_alloc_func(cVADSegments, ruby_whisper_vad_segments_s_allocate);
rb_define_method(cVADSegments, "each", ruby_whisper_vad_segments_each, 0);
rb_define_method(cVADSegments, "length", ruby_whisper_vad_segments_get_length, 0);
rb_include_module(cVADSegments, rb_path2class("Enumerable"));
}

View File

@@ -0,0 +1,8 @@
set(GRAPHVIZ_EXECUTABLES FALSE)
set(GRAPHVIZ_STATIC_LIBS TRUE)
set(GRAPHVIZ_SHARED_LIBS FALSE)
set(GRAPHVIZ_MODULE_LIBS FALSE)
set(GRAPHVIZ_INTERFACE_LIBS FALSE)
set(GRAPHVIZ_OBJECT_LIBS FALSE)
set(GRAPHVIZ_UNKNOWN_LIBS FALSE)
set(GRAPHVIZ_GENERATE_DEPENDERS FALSE)