Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1cfd7fc98 | ||
|
|
e83da30048 | ||
|
|
4dbd14f423 | ||
|
|
652d775c8c | ||
|
|
6c91e0650a | ||
|
|
5889c1f1e1 | ||
|
|
402a7757cb | ||
|
|
48fddfe1c6 |
@@ -17,43 +17,23 @@ LIBS += -lwhisper -lggml
|
||||
endif
|
||||
|
||||
SRC := \
|
||||
./src/main.c \
|
||||
./src/gui_main.c \
|
||||
./src/config.c \
|
||||
./src/hotkey.c \
|
||||
./src/audio.c \
|
||||
./src/transcribe.c \
|
||||
./src/typer.c
|
||||
|
||||
OBJ := $(SRC:.c=.o)
|
||||
TARGET := ./voice_linux
|
||||
GUI_TARGET := ./voice_linux_gui
|
||||
|
||||
.PHONY: all clean run run-gui cpu gpu
|
||||
.PHONY: all clean run
|
||||
|
||||
all: $(TARGET) $(GUI_TARGET)
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJ)
|
||||
$(CC) $(OBJ) -o $@ $(LDFLAGS) $(LIBS)
|
||||
|
||||
./src/%.o: ./src/%.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(GUI_TARGET): ./src/gui_main.c ./src/config.c ./src/audio.c ./src/transcribe.c ./src/typer.c
|
||||
$(TARGET): $(SRC)
|
||||
$(CC) $(CFLAGS) $(GTK_CFLAGS) $^ -o $@ $(LDFLAGS) $(LIBS) $(GTK_LIBS)
|
||||
|
||||
cpu:
|
||||
$(MAKE) clean
|
||||
$(MAKE) WITH_WHISPER=1
|
||||
|
||||
gpu:
|
||||
$(MAKE) clean
|
||||
$(MAKE) WITH_WHISPER=1
|
||||
|
||||
run: $(TARGET)
|
||||
./voice_linux
|
||||
|
||||
run-gui: $(GUI_TARGET)
|
||||
./voice_linux_gui
|
||||
|
||||
clean:
|
||||
rm -f $(OBJ) $(TARGET) $(GUI_TARGET)
|
||||
rm -f $(TARGET)
|
||||
|
||||
@@ -9,58 +9,56 @@ Local hotkey dictation for Linux/X11 using:
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install -y build-essential cmake git libpulse-dev libx11-dev libxtst-dev
|
||||
```
|
||||
|
||||
## Build whisper.cpp
|
||||
|
||||
```bash
|
||||
mkdir -p ./vendor
|
||||
cd ./vendor
|
||||
git clone https://github.com/ggerganov/whisper.cpp.git
|
||||
cd ./whisper.cpp
|
||||
cmake -B build -DWHISPER_CUDA=ON
|
||||
cmake --build build -j"$(nproc)"
|
||||
```
|
||||
|
||||
If CUDA is not ready yet, you can still build whisper.cpp CPU-only by omitting `-DWHISPER_CUDA=ON`.
|
||||
|
||||
## Download a model
|
||||
|
||||
```bash
|
||||
mkdir -p ./models
|
||||
wget -O ./models/ggml-base.en.bin \
|
||||
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin
|
||||
sudo apt install -y build-essential cmake git wget libpulse-dev libx11-dev libxtst-dev libgtk-3-dev pkg-config
|
||||
```
|
||||
|
||||
## Build app
|
||||
|
||||
Use [`build.sh`](build.sh):
|
||||
|
||||
```bash
|
||||
# builds CLI + GUI by default
|
||||
# CPU whisper build
|
||||
bash ./build.sh
|
||||
|
||||
# optional switches:
|
||||
WHISPER_CUDA=1 bash ./build.sh # enable CUDA whisper.cpp build
|
||||
BUILD_GUI=0 bash ./build.sh # build CLI only
|
||||
WITH_WHISPER=0 bash ./build.sh # build without whisper integration
|
||||
# CUDA whisper build
|
||||
WHISPER_CUDA=1 bash ./build.sh
|
||||
|
||||
# build without whisper integration
|
||||
WITH_WHISPER=0 bash ./build.sh
|
||||
```
|
||||
|
||||
`./voice_linux` is the single app binary and always launches the GTK window.
|
||||
|
||||
## Download whisper models
|
||||
|
||||
Download all supported test models:
|
||||
|
||||
```bash
|
||||
bash ./scripts/download_models.sh
|
||||
```
|
||||
|
||||
This fetches:
|
||||
- `ggml-tiny.en.bin`
|
||||
- `ggml-base.en.bin`
|
||||
- `ggml-small.en.bin`
|
||||
- `ggml-medium.en.bin`
|
||||
- `ggml-large-v3-turbo.bin`
|
||||
- `ggml-large-v3.bin`
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
# CLI hotkey mode (uses ./config.ini by default)
|
||||
./voice_linux
|
||||
|
||||
# GTK3 window mode (uses ./config.ini by default)
|
||||
./voice_linux_gui
|
||||
```
|
||||
|
||||
On startup, the app opens a dialog where you choose:
|
||||
- CPU or GPU mode
|
||||
- Whisper model from `./models/`
|
||||
|
||||
Default hotkey: `Ctrl+Alt+V`
|
||||
- Press once to **start** recording
|
||||
- Press again to **stop** recording, transcribe, and type into the focused window
|
||||
|
||||
If you see `samples=0`, that was an immediate start/stop event.
|
||||
|
||||
If you see `[stt] [BLANK_AUDIO]`, recording worked but audio energy was near silent.
|
||||
Check your input source with:
|
||||
|
||||
@@ -68,7 +66,7 @@ Check your input source with:
|
||||
pactl list short sources
|
||||
```
|
||||
|
||||
Then set `audio_device=` in `./config.ini` to the source you want.
|
||||
Then set `audio_device=` in [`config.ini`](config.ini).
|
||||
|
||||
## Versioning workflow
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ cd "$ROOT_DIR"
|
||||
|
||||
WITH_WHISPER="${WITH_WHISPER:-1}"
|
||||
WHISPER_CUDA="${WHISPER_CUDA:-0}"
|
||||
BUILD_GUI="${BUILD_GUI:-1}"
|
||||
JOBS="${JOBS:-$(nproc)}"
|
||||
|
||||
WHISPER_DIR="./vendor/whisper.cpp"
|
||||
@@ -33,9 +32,24 @@ LDFLAGS_COMMON=(
|
||||
)
|
||||
|
||||
echo "[build] root: $ROOT_DIR"
|
||||
echo "[build] WITH_WHISPER=$WITH_WHISPER WHISPER_CUDA=$WHISPER_CUDA BUILD_GUI=$BUILD_GUI JOBS=$JOBS"
|
||||
echo "[build] WITH_WHISPER=$WITH_WHISPER WHISPER_CUDA=$WHISPER_CUDA JOBS=$JOBS"
|
||||
|
||||
need_cmd gcc
|
||||
need_cmd pkg-config
|
||||
if ! pkg-config --exists gtk+-3.0; then
|
||||
echo "[build] error: gtk+-3.0 dev files not found" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
GTK_CFLAGS_STR="$(pkg-config --cflags gtk+-3.0 2>/dev/null || true)"
|
||||
if [[ -z "$GTK_CFLAGS_STR" ]]; then
|
||||
echo "[build] error: gtk+-3.0 cflags are unavailable" >&2
|
||||
exit 2
|
||||
fi
|
||||
if ! printf '#include <gtk/gtk.h>\n' | gcc -x c -fsyntax-only $GTK_CFLAGS_STR - >/dev/null 2>&1; then
|
||||
echo "[build] error: gtk headers are not usable (gtk/gtk.h check failed)" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ "$WITH_WHISPER" == "1" ]]; then
|
||||
if ! have_cmd cmake; then
|
||||
@@ -58,28 +72,7 @@ if [[ "$WITH_WHISPER" == "1" ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$BUILD_GUI" == "1" ]]; then
|
||||
if ! have_cmd pkg-config; then
|
||||
echo "[build] warning: pkg-config not found; forcing BUILD_GUI=0" >&2
|
||||
BUILD_GUI=0
|
||||
elif ! pkg-config --exists gtk+-3.0; then
|
||||
echo "[build] warning: gtk+-3.0 dev files not found; forcing BUILD_GUI=0" >&2
|
||||
BUILD_GUI=0
|
||||
else
|
||||
GTK_CFLAGS_STR="$(pkg-config --cflags gtk+-3.0 2>/dev/null || true)"
|
||||
if [[ -z "$GTK_CFLAGS_STR" ]]; then
|
||||
echo "[build] warning: gtk+-3.0 cflags are unavailable; forcing BUILD_GUI=0" >&2
|
||||
BUILD_GUI=0
|
||||
else
|
||||
if ! printf '#include <gtk/gtk.h>\n' | gcc -x c -fsyntax-only $GTK_CFLAGS_STR - >/dev/null 2>&1; then
|
||||
echo "[build] warning: gtk headers are not usable (gtk/gtk.h check failed); forcing BUILD_GUI=0" >&2
|
||||
BUILD_GUI=0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "[build] effective: WITH_WHISPER=$WITH_WHISPER BUILD_GUI=$BUILD_GUI"
|
||||
echo "[build] effective: WITH_WHISPER=$WITH_WHISPER"
|
||||
|
||||
if [[ "$WITH_WHISPER" == "1" ]]; then
|
||||
if [[ ! -d "$WHISPER_DIR" ]]; then
|
||||
@@ -90,7 +83,7 @@ if [[ "$WITH_WHISPER" == "1" ]]; then
|
||||
|
||||
echo "[build] configuring whisper.cpp..."
|
||||
if [[ "$WHISPER_CUDA" == "1" ]]; then
|
||||
cmake -S "$WHISPER_DIR" -B "$WHISPER_DIR/build" -DWHISPER_CUDA=ON
|
||||
cmake -S "$WHISPER_DIR" -B "$WHISPER_DIR/build" -DGGML_CUDA=ON
|
||||
else
|
||||
cmake -S "$WHISPER_DIR" -B "$WHISPER_DIR/build"
|
||||
fi
|
||||
@@ -99,26 +92,31 @@ if [[ "$WITH_WHISPER" == "1" ]]; then
|
||||
cmake --build "$WHISPER_DIR/build" -j"$JOBS"
|
||||
|
||||
mkdir -p ./models
|
||||
if [[ ! -f "$MODEL_PATH" ]]; then
|
||||
if [[ -x ./scripts/download_models.sh ]]; then
|
||||
echo "[build] ensuring whisper models are downloaded..."
|
||||
bash ./scripts/download_models.sh
|
||||
elif [[ ! -f "$MODEL_PATH" ]]; then
|
||||
echo "[build] downloading model $MODEL_PATH..."
|
||||
wget -O "$MODEL_PATH" \
|
||||
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p ./src
|
||||
echo "[build] building GTK3 app binary..."
|
||||
|
||||
echo "[build] building CLI binary..."
|
||||
CLI_CFLAGS=("${CFLAGS_COMMON[@]}")
|
||||
CLI_LDFLAGS=("${LDFLAGS_COMMON[@]}")
|
||||
read -r -a GTK_CFLAGS <<< "$(pkg-config --cflags gtk+-3.0)"
|
||||
read -r -a GTK_LIBS <<< "$(pkg-config --libs gtk+-3.0)"
|
||||
|
||||
APP_CFLAGS=("${CFLAGS_COMMON[@]}" "${GTK_CFLAGS[@]}")
|
||||
APP_LDFLAGS=("${LDFLAGS_COMMON[@]}" "${GTK_LIBS[@]}")
|
||||
|
||||
if [[ "$WITH_WHISPER" == "1" ]]; then
|
||||
CLI_CFLAGS+=(
|
||||
APP_CFLAGS+=(
|
||||
-DWITH_WHISPER=1
|
||||
-isystem "$WHISPER_DIR/include"
|
||||
-isystem "$WHISPER_DIR/ggml/include"
|
||||
)
|
||||
CLI_LDFLAGS+=(
|
||||
APP_LDFLAGS+=(
|
||||
-L"$WHISPER_DIR/build/src"
|
||||
-L"$WHISPER_DIR/build/ggml/src"
|
||||
-Wl,-rpath,"$WHISPER_DIR/build/src"
|
||||
@@ -127,41 +125,10 @@ if [[ "$WITH_WHISPER" == "1" ]]; then
|
||||
)
|
||||
fi
|
||||
|
||||
gcc "${CLI_CFLAGS[@]}" \
|
||||
./src/main.c ./src/config.c ./src/hotkey.c ./src/audio.c ./src/transcribe.c ./src/typer.c \
|
||||
gcc "${APP_CFLAGS[@]}" \
|
||||
./src/gui_main.c ./src/config.c ./src/audio.c ./src/transcribe.c ./src/typer.c \
|
||||
-o ./voice_linux \
|
||||
"${CLI_LDFLAGS[@]}"
|
||||
"${APP_LDFLAGS[@]}"
|
||||
|
||||
if [[ "$BUILD_GUI" == "1" ]]; then
|
||||
echo "[build] building GTK3 GUI binary..."
|
||||
|
||||
read -r -a GTK_CFLAGS <<< "$(pkg-config --cflags gtk+-3.0)"
|
||||
read -r -a GTK_LIBS <<< "$(pkg-config --libs gtk+-3.0)"
|
||||
|
||||
GUI_CFLAGS=("${CFLAGS_COMMON[@]}" "${GTK_CFLAGS[@]}")
|
||||
GUI_LDFLAGS=("${LDFLAGS_COMMON[@]}" "${GTK_LIBS[@]}")
|
||||
|
||||
if [[ "$WITH_WHISPER" == "1" ]]; then
|
||||
GUI_CFLAGS+=(
|
||||
-DWITH_WHISPER=1
|
||||
-isystem "$WHISPER_DIR/include"
|
||||
-isystem "$WHISPER_DIR/ggml/include"
|
||||
)
|
||||
GUI_LDFLAGS+=(
|
||||
-L"$WHISPER_DIR/build/src"
|
||||
-L"$WHISPER_DIR/build/ggml/src"
|
||||
-Wl,-rpath,"$WHISPER_DIR/build/src"
|
||||
-Wl,-rpath,"$WHISPER_DIR/build/ggml/src"
|
||||
-lwhisper -lggml
|
||||
)
|
||||
fi
|
||||
|
||||
gcc "${GUI_CFLAGS[@]}" \
|
||||
./src/gui_main.c ./src/config.c ./src/audio.c ./src/transcribe.c ./src/typer.c \
|
||||
-o ./voice_linux_gui \
|
||||
"${GUI_LDFLAGS[@]}"
|
||||
fi
|
||||
|
||||
echo "[build] done: ./voice_linux (and ./voice_linux_gui if BUILD_GUI=1)"
|
||||
echo "[build] run CLI: ./voice_linux"
|
||||
echo "[build] run GUI: ./voice_linux_gui"
|
||||
echo "[build] done: ./voice_linux"
|
||||
echo "[build] run: ./voice_linux"
|
||||
|
||||
+4
-2
@@ -2,7 +2,8 @@
|
||||
# Hotkey format supports Ctrl, Alt, Shift, Super + final key symbol
|
||||
# Example: Ctrl+Alt+V
|
||||
|
||||
model_path=./models/ggml-base.en.bin
|
||||
model_path=./models/ggml-medium.en.bin
|
||||
use_gpu=1
|
||||
audio_device=alsa_input.usb-Shure_Inc_Shure_MV6_MV6_5-ed2d37fa14d3e65d8d477cacf910d93c-01.mono-fallback
|
||||
sample_rate=16000
|
||||
language=en
|
||||
@@ -15,7 +16,7 @@ always_on=1
|
||||
always_on_window_ms=1400
|
||||
|
||||
# live level threshold (0.0 - 1.0)
|
||||
vad_peak_threshold=0.006
|
||||
vad_peak_threshold=0.0060
|
||||
# level must stay above threshold this long before speech starts
|
||||
vad_trigger_ms=100
|
||||
# level must stay below threshold this long before speech ends
|
||||
@@ -28,3 +29,4 @@ vad_min_speech_ms=250
|
||||
vad_max_speech_ms=15000
|
||||
|
||||
autotype_enabled=1
|
||||
filter_bracketed_tags=1
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
# GPU Enablement Plan — voice_linux on NVIDIA GTX 1080 Ti
|
||||
|
||||
## Summary
|
||||
|
||||
Enable CUDA-accelerated whisper.cpp inference for the voice_linux project by passing the GTX 1080 Ti through to the Qubes `ai` **StandaloneVM**, installing NVIDIA drivers and CUDA toolkit, and rebuilding the application with `WHISPER_CUDA=1`.
|
||||
|
||||
## Current State
|
||||
|
||||
The codebase is **already GPU-ready**:
|
||||
- [`build.sh`](../build.sh) accepts `WHISPER_CUDA=1` to build whisper.cpp with CUDA
|
||||
- [`src/transcribe.c:22`](../src/transcribe.c:22) sets `cparams.use_gpu` from the transcribe params
|
||||
- [`src/main.c:77`](../src/main.c:77) supports `--gpu` / `--cpu` CLI flags
|
||||
- [`src/transcribe.h:9`](../src/transcribe.h:9) has `use_gpu` in the params struct
|
||||
|
||||
What's missing is the **hardware and driver stack** underneath.
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[dom0 - Xen Hypervisor] -->|PCI passthrough| B[ai StandaloneVM]
|
||||
B --> C[NVIDIA Driver 550.x]
|
||||
C --> D[CUDA Toolkit 12.6]
|
||||
D --> E[whisper.cpp built with WHISPER_CUDA=ON]
|
||||
E --> F[voice_linux --gpu]
|
||||
|
||||
style A fill:#f9f,stroke:#333
|
||||
style F fill:#9f9,stroke:#333
|
||||
```
|
||||
|
||||
## Phase 0: GPU Passthrough in dom0
|
||||
|
||||
> **All commands in this phase run in a dom0 terminal.**
|
||||
|
||||
### Step 0.1 — Hide the GTX 1080 Ti from dom0
|
||||
|
||||
Edit `/etc/default/grub` in dom0 and add `rd.qubes.hide_pci=65:00.0,65:00.1` to the end of the **first** `GRUB_CMDLINE_LINUX` line (inside the quotes):
|
||||
|
||||
```bash
|
||||
sudo nano /etc/default/grub
|
||||
```
|
||||
|
||||
The line should look like:
|
||||
```
|
||||
GRUB_CMDLINE_LINUX="...existing options... rd.qubes.hide_pci=65:00.0,65:00.1"
|
||||
```
|
||||
|
||||
The BDF addresses `65:00.0` (GPU) and `65:00.1` (HDMI audio) come from the `lspci` output documented in [`plans/architecture.md:97`](../plans/architecture.md:97).
|
||||
|
||||
### Step 0.2 — Regenerate GRUB and reboot
|
||||
|
||||
```bash
|
||||
sudo grub2-mkconfig -o /boot/efi/EFI/qubes/grub.cfg
|
||||
# If that path doesn't exist: sudo grub2-mkconfig -o /boot/grub2/grub.cfg
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
### Step 0.3 — Verify GPU is assignable
|
||||
|
||||
After reboot, in dom0:
|
||||
|
||||
```bash
|
||||
xl pci-assignable-list
|
||||
```
|
||||
|
||||
Expected output should include:
|
||||
```
|
||||
0000:65:00.0
|
||||
0000:65:00.1
|
||||
```
|
||||
|
||||
If these don't appear, check IOMMU grouping with `xl pci-assignable-list -l` and verify the GRUB change took effect with `cat /proc/cmdline`.
|
||||
|
||||
### Step 0.4 — Attach GPU to the ai StandaloneVM
|
||||
|
||||
```bash
|
||||
qvm-pci attach ai dom0:65_00.0 --persistent -o permissive=true
|
||||
qvm-pci attach ai dom0:65_00.1 --persistent -o permissive=true
|
||||
```
|
||||
|
||||
The `--persistent` flag ensures the GPU is attached on every boot. The `permissive=true` option is needed for NVIDIA GPUs in Qubes.
|
||||
|
||||
### Step 0.5 — Persistence model (already satisfied)
|
||||
|
||||
Your `ai` VM is already a **StandaloneVM**, so driver and CUDA installs persist normally across reboots.
|
||||
|
||||
No template/bind-dirs workaround is required.
|
||||
|
||||
## Phase 1: NVIDIA Driver Installation
|
||||
|
||||
> **All commands from here run inside the ai StandaloneVM.**
|
||||
|
||||
### Step 1.1 — Install build prerequisites
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install -y build-essential linux-headers-$(uname -r)
|
||||
```
|
||||
|
||||
### Step 1.2 — Install NVIDIA driver
|
||||
|
||||
For the GTX 1080 Ti (Pascal/GP102, compute capability 6.1), use the 550.x branch:
|
||||
|
||||
```bash
|
||||
wget https://us.download.nvidia.com/XFree86/Linux-x86_64/550.127.05/NVIDIA-Linux-x86_64-550.127.05.run
|
||||
chmod +x NVIDIA-Linux-x86_64-550.127.05.run
|
||||
sudo ./NVIDIA-Linux-x86_64-550.127.05.run --no-opengl-files --dkms
|
||||
```
|
||||
|
||||
> **Critical**: `--no-opengl-files` prevents replacing the VM's display GL stack. We only need CUDA compute, not display rendering.
|
||||
|
||||
### Step 1.3 — Verify driver
|
||||
|
||||
```bash
|
||||
nvidia-smi
|
||||
```
|
||||
|
||||
Expected: Shows GTX 1080 Ti with 11GB VRAM and driver version 550.127.05.
|
||||
|
||||
## Phase 2: CUDA Toolkit Installation
|
||||
|
||||
### Step 2.1 — Install CUDA toolkit
|
||||
|
||||
```bash
|
||||
wget https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64/cuda-keyring_1.1-1_all.deb
|
||||
sudo dpkg -i cuda-keyring_1.1-1_all.deb
|
||||
sudo apt update
|
||||
sudo apt install -y cuda-toolkit-12-6
|
||||
```
|
||||
|
||||
### Step 2.2 — Configure environment
|
||||
|
||||
Add to `~/.bashrc`:
|
||||
|
||||
```bash
|
||||
echo 'export PATH=/usr/local/cuda/bin:$PATH' >> ~/.bashrc
|
||||
echo 'export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
```
|
||||
|
||||
### Step 2.3 — Verify CUDA
|
||||
|
||||
```bash
|
||||
nvcc --version
|
||||
```
|
||||
|
||||
Expected: CUDA 12.6.
|
||||
|
||||
## Phase 3: Rebuild with CUDA
|
||||
|
||||
### Step 3.1 — Clean and rebuild
|
||||
|
||||
From the project directory, run:
|
||||
|
||||
```bash
|
||||
WHISPER_CUDA=1 bash ./build.sh
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Clone whisper.cpp into `vendor/whisper.cpp` (if not already present)
|
||||
2. Run `cmake -DWHISPER_CUDA=ON` to configure whisper.cpp with CUDA support
|
||||
3. Build whisper.cpp with CUDA kernels
|
||||
4. Download the `ggml-base.en.bin` model (if not present)
|
||||
5. Compile voice_linux linking against the CUDA-enabled whisper library
|
||||
|
||||
### Step 3.2 — Verify CUDA linkage
|
||||
|
||||
```bash
|
||||
ldd ./voice_linux | grep -i cuda
|
||||
```
|
||||
|
||||
Should show linkage to CUDA libraries (indirectly through libwhisper).
|
||||
|
||||
Also check:
|
||||
```bash
|
||||
ldd ./vendor/whisper.cpp/build/src/libwhisper.so | grep -i cuda
|
||||
```
|
||||
|
||||
Should show `libcudart.so`, `libcublas.so`, etc.
|
||||
|
||||
## Phase 4: Run with GPU
|
||||
|
||||
### Step 4.1 — Launch with GPU flag
|
||||
|
||||
```bash
|
||||
./voice_linux --gpu
|
||||
```
|
||||
|
||||
The startup log at [`src/main.c:119`](../src/main.c:119) will print `gpu: on` confirming GPU mode. Watch for whisper.cpp log lines mentioning CUDA device initialization.
|
||||
|
||||
### Step 4.2 — Verify GPU utilization
|
||||
|
||||
In a separate terminal while transcribing:
|
||||
|
||||
```bash
|
||||
nvidia-smi
|
||||
```
|
||||
|
||||
Should show `voice_linux` process using GPU memory.
|
||||
|
||||
### Step 4.3 — Optional: Try a larger model
|
||||
|
||||
With 11GB VRAM on the 1080 Ti, you can comfortably run `medium.en` for much better accuracy:
|
||||
|
||||
```bash
|
||||
cd models/
|
||||
wget https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium.en.bin
|
||||
```
|
||||
|
||||
Then update [`config.ini`](../config.ini) line 5:
|
||||
```ini
|
||||
model_path=./models/ggml-medium.en.bin
|
||||
```
|
||||
|
||||
| Model | VRAM Usage | Speed (10s audio) | Accuracy |
|
||||
|-------|-----------|-------------------|----------|
|
||||
| base.en | ~1GB | <1s | Good |
|
||||
| medium.en | ~5GB | ~2-3s | Excellent |
|
||||
| large-v3 | ~10GB | ~4-6s | Best |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Problem | Solution |
|
||||
|---------|----------|
|
||||
| `xl pci-assignable-list` empty | Check GRUB cmdline with `cat /proc/cmdline`, verify `rd.qubes.hide_pci` is present |
|
||||
| `nvidia-smi` not found | Driver install failed; check `dmesg` for NVIDIA errors |
|
||||
| `nvcc` not found | CUDA PATH not set; run `source ~/.bashrc` or check install |
|
||||
| whisper.cpp cmake fails with CUDA | Ensure `nvcc` is in PATH before running `build.sh` |
|
||||
| `voice_linux` crashes on `--gpu` | Run without `--gpu` first to verify CPU mode works, then check CUDA libs with `ldd` |
|
||||
| GPU passthrough causes VM crash | Try without `permissive=true` first, or check IOMMU groups in dom0 |
|
||||
|
||||
## No Code Changes Required
|
||||
|
||||
The existing codebase handles everything:
|
||||
- [`build.sh:8`](../build.sh:8) — `WHISPER_CUDA` env var controls CUDA build
|
||||
- [`build.sh:92-93`](../build.sh:92) — passes `-DWHISPER_CUDA=ON` to cmake
|
||||
- [`src/transcribe.c:21-22`](../src/transcribe.c:21) — `whisper_context_default_params()` + `use_gpu` flag
|
||||
- [`src/main.c:77-78`](../src/main.c:77) — `--gpu` / `--cpu` CLI argument parsing
|
||||
|
||||
The only change is the build command: `WHISPER_CUDA=1 bash ./build.sh` instead of `bash ./build.sh`.
|
||||
@@ -0,0 +1,142 @@
|
||||
# Unified Binary + Startup Dialog Plan
|
||||
|
||||
## Summary
|
||||
|
||||
Merge the CLI and GUI into a single `voice_linux` binary that always opens the GTK3 window. On launch, a **startup dialog** appears letting the user choose CPU vs GPU and select which whisper model to use. All available ggml models are downloaded to `./models/` so the user can test them.
|
||||
|
||||
## Current State
|
||||
|
||||
| Item | Status |
|
||||
|------|--------|
|
||||
| Two binaries: `voice_linux` (CLI) and `voice_linux_gui` (GTK3) | Active |
|
||||
| GPU passthrough + NVIDIA driver + CUDA toolkit | Working |
|
||||
| whisper.cpp built with `GGML_CUDA=ON` | Working |
|
||||
| `--gpu` / `--cpu` flags parsed in both binaries | Working |
|
||||
| Model path hardcoded in `config.ini` as `model_path=` | Working |
|
||||
| `transcribe_init()` called once at startup before GTK loop | Working |
|
||||
|
||||
## Architecture Changes
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[./voice_linux launched] --> B[Load config.ini]
|
||||
B --> C[audio_init]
|
||||
C --> D{Show Startup Dialog}
|
||||
D --> |User picks CPU/GPU + model| E[transcribe_init with selections]
|
||||
E --> F[typer_init]
|
||||
F --> G[Main GTK window + event loop]
|
||||
G --> H[On window close: cleanup]
|
||||
```
|
||||
|
||||
Key change: **`transcribe_init()` is deferred** until after the startup dialog returns the user's selections. Audio init happens first so the mic meter can work, but model loading waits for the dialog.
|
||||
|
||||
## Whisper Models to Download
|
||||
|
||||
All English-optimized ggml models from the whisper.cpp HuggingFace repo. The GTX 1080 Ti has 11 GB VRAM, so all models up to `large-v3` fit comfortably.
|
||||
|
||||
| Model | File | Size | VRAM est. |
|
||||
|-------|------|------|-----------|
|
||||
| tiny.en | `ggml-tiny.en.bin` | ~75 MB | ~200 MB |
|
||||
| base.en | `ggml-base.en.bin` | ~142 MB | ~350 MB |
|
||||
| small.en | `ggml-small.en.bin` | ~466 MB | ~1 GB |
|
||||
| medium.en | `ggml-medium.en.bin` | ~1.5 GB | ~3 GB |
|
||||
| large-v3-turbo | `ggml-large-v3-turbo.bin` | ~1.6 GB | ~3.5 GB |
|
||||
| large-v3 | `ggml-large-v3.bin` | ~3.1 GB | ~6 GB |
|
||||
|
||||
Base URL: `https://huggingface.co/ggerganov/whisper.cpp/resolve/main/`
|
||||
|
||||
## Detailed Steps
|
||||
|
||||
### Step 1: Download all whisper models
|
||||
|
||||
Create a `scripts/download_models.sh` that downloads each model to `./models/` if not already present. Also callable from `build.sh`.
|
||||
|
||||
### Step 2: Add startup dialog to gui_main.c
|
||||
|
||||
Insert a modal `GtkDialog` that runs **before** `transcribe_init()`:
|
||||
|
||||
- **GPU toggle**: `GtkComboBoxText` with entries `CPU` and `GPU`. Default to `GPU` if CUDA device is detected at runtime, otherwise `CPU`.
|
||||
- **Model picker**: `GtkComboBoxText` populated by scanning `./models/ggml-*.bin` files. Display friendly names derived from filenames. Pre-select the model from `config.ini`.
|
||||
- **OK button**: closes dialog, returns selections to `main()`.
|
||||
|
||||
The dialog is a transient child of a hidden parent window so it appears centered on screen.
|
||||
|
||||
GPU detection at dialog time: check if `nvidia-smi` exits 0, or check if the whisper.cpp CUDA backend reports a device. Simplest approach: try `access /dev/nvidia0` — if it exists, default to GPU.
|
||||
|
||||
### Step 3: Defer transcribe_init
|
||||
|
||||
Current flow in `gui_main.c` [`main()`](src/gui_main.c:588):
|
||||
|
||||
```
|
||||
config_load -> use_gpu from argv -> audio_init -> transcribe_init -> typer_init -> gtk loop
|
||||
```
|
||||
|
||||
New flow:
|
||||
|
||||
```
|
||||
config_load -> audio_init -> show_startup_dialog -> transcribe_init(dialog selections) -> typer_init -> gtk loop
|
||||
```
|
||||
|
||||
The startup dialog returns:
|
||||
- `int use_gpu` — 0 or 1
|
||||
- `char model_path[512]` — full path to selected model file
|
||||
|
||||
These override whatever was in config.ini / argv.
|
||||
|
||||
### Step 4: Remove CLI binary from default build
|
||||
|
||||
- Keep `src/main.c` in the repo for reference or future headless use, but do not build it by default.
|
||||
- In [`build.sh`](build.sh): remove the separate CLI gcc invocation. The GUI gcc line outputs `./voice_linux` instead of `./voice_linux_gui`.
|
||||
- In [`Makefile`](Makefile): change `TARGET` to build from `gui_main.c` sources. Remove `voice_linux_gui` target. Optionally add a `cli` target for headless builds.
|
||||
- The `BUILD_GUI` env var is no longer needed; remove it.
|
||||
|
||||
### Step 5: Add use_gpu to config.ini and config.c
|
||||
|
||||
Add a `use_gpu=1` line to [`config.ini`](config.ini). Parse it in [`config_load_file()`](src/config.c:43). This becomes the default for the startup dialog, overridable by the dialog selection. The `--gpu` / `--cpu` CLI flags still work as overrides.
|
||||
|
||||
Add `int use_gpu;` to [`voice_config_t`](src/config.h:6).
|
||||
|
||||
### Step 6: Update README.md
|
||||
|
||||
Simplify run instructions to just `./voice_linux`. Remove `voice_linux_gui` references. Document the startup dialog behavior.
|
||||
|
||||
### Step 7: Build and test
|
||||
|
||||
```bash
|
||||
bash ./build.sh
|
||||
./voice_linux
|
||||
```
|
||||
|
||||
Verify:
|
||||
- Startup dialog appears with CPU/GPU toggle and model list
|
||||
- Selecting GPU + medium.en loads correctly with CUDA backend
|
||||
- Main window works as before after dialog closes
|
||||
|
||||
## Files Modified
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `src/gui_main.c` | Add startup dialog function; defer `transcribe_init`; update `main()` flow |
|
||||
| `src/config.h` | Add `use_gpu` field to `voice_config_t` |
|
||||
| `src/config.c` | Parse `use_gpu` from config file |
|
||||
| `config.ini` | Add `use_gpu=1` |
|
||||
| `build.sh` | Single binary output `./voice_linux`; remove `BUILD_GUI` toggle; remove CLI build |
|
||||
| `Makefile` | Single target `./voice_linux` from gui sources |
|
||||
| `README.md` | Update run instructions |
|
||||
| `scripts/download_models.sh` | New script to download all models |
|
||||
|
||||
## Files NOT Modified
|
||||
|
||||
| File | Reason |
|
||||
|------|--------|
|
||||
| `src/main.c` | Kept for optional headless builds; not built by default |
|
||||
| `src/transcribe.c` | API unchanged — `transcribe_init` / `transcribe_cleanup` already support the needed flow |
|
||||
| `src/transcribe.h` | No changes needed |
|
||||
| `src/audio.c` / `src/audio.h` | No changes needed |
|
||||
| `src/hotkey.c` / `src/hotkey.h` | Only used by CLI; not linked into GUI binary |
|
||||
|
||||
## Risk Notes
|
||||
|
||||
- The startup dialog adds a brief pause before the main window. Model loading (especially large-v3 at 3 GB) takes several seconds — a progress indicator or status label in the main window info area will show loading state.
|
||||
- If no models are found in `./models/`, the dialog should show an error message and offer to run the download script.
|
||||
- The `transcribe_init` call blocks the GTK main thread during model load. For large models this could be 5-10 seconds. An improvement would be async loading with a progress bar, but for v1 a simple "Loading model..." label update before the blocking call is sufficient.
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
mkdir -p ./models
|
||||
|
||||
BASE_URL="https://huggingface.co/ggerganov/whisper.cpp/resolve/main"
|
||||
MODELS=(
|
||||
"ggml-tiny.en.bin"
|
||||
"ggml-base.en.bin"
|
||||
"ggml-small.en.bin"
|
||||
"ggml-medium.en.bin"
|
||||
"ggml-large-v3-turbo.bin"
|
||||
"ggml-large-v3.bin"
|
||||
)
|
||||
|
||||
need_cmd() {
|
||||
if ! command -v "$1" >/dev/null 2>&1; then
|
||||
echo "[models] error: required command '$1' not found" >&2
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
need_cmd wget
|
||||
|
||||
echo "[models] target dir: $ROOT_DIR/models"
|
||||
|
||||
for model in "${MODELS[@]}"; do
|
||||
out="./models/$model"
|
||||
url="$BASE_URL/$model"
|
||||
|
||||
if [[ -f "$out" ]]; then
|
||||
size="$(stat -c%s "$out" 2>/dev/null || echo 0)"
|
||||
echo "[models] found local copy for $model (${size} bytes), resuming/verifying with wget -c"
|
||||
else
|
||||
echo "[models] downloading: $model"
|
||||
fi
|
||||
|
||||
wget -c -O "$out" "$url"
|
||||
echo "[models] ready: $model"
|
||||
done
|
||||
|
||||
echo "[models] complete"
|
||||
+175
@@ -25,6 +25,7 @@ void config_set_defaults(voice_config_t *cfg) {
|
||||
snprintf(cfg->audio_device, sizeof(cfg->audio_device), "qubes-source");
|
||||
snprintf(cfg->language, sizeof(cfg->language), "en");
|
||||
snprintf(cfg->hotkey, sizeof(cfg->hotkey), "Ctrl+Alt+V");
|
||||
cfg->use_gpu = 1;
|
||||
cfg->sample_rate = 16000;
|
||||
cfg->type_delay_us = 8000;
|
||||
cfg->max_record_seconds = 30;
|
||||
@@ -37,6 +38,44 @@ void config_set_defaults(voice_config_t *cfg) {
|
||||
cfg->vad_min_speech_ms = 250;
|
||||
cfg->vad_max_speech_ms = 15000;
|
||||
cfg->autotype_enabled = 1;
|
||||
cfg->filter_bracketed_tags = 1;
|
||||
|
||||
cfg->whisper_sampling_strategy = 0;
|
||||
cfg->whisper_n_max_text_ctx = 16384;
|
||||
cfg->whisper_offset_ms = 0;
|
||||
cfg->whisper_duration_ms = 0;
|
||||
|
||||
cfg->whisper_translate = 0;
|
||||
cfg->whisper_detect_language = 0;
|
||||
cfg->whisper_no_context = 0;
|
||||
cfg->whisper_no_timestamps = 1;
|
||||
cfg->whisper_single_segment = 0;
|
||||
|
||||
cfg->whisper_token_timestamps = 0;
|
||||
cfg->whisper_thold_pt = 0.01f;
|
||||
cfg->whisper_thold_ptsum = 0.01f;
|
||||
cfg->whisper_max_len = 0;
|
||||
cfg->whisper_split_on_word = 0;
|
||||
cfg->whisper_max_tokens = 0;
|
||||
|
||||
cfg->whisper_audio_ctx = 0;
|
||||
cfg->whisper_tdrz_enable = 0;
|
||||
|
||||
cfg->whisper_suppress_blank = 1;
|
||||
cfg->whisper_suppress_nst = 0;
|
||||
|
||||
cfg->whisper_temperature = 0.0f;
|
||||
cfg->whisper_max_initial_ts = 1.0f;
|
||||
cfg->whisper_length_penalty = -1.0f;
|
||||
|
||||
cfg->whisper_temperature_inc = 0.2f;
|
||||
cfg->whisper_entropy_thold = 2.4f;
|
||||
cfg->whisper_logprob_thold = -1.0f;
|
||||
cfg->whisper_no_speech_thold = 0.6f;
|
||||
|
||||
cfg->whisper_greedy_best_of = 1;
|
||||
cfg->whisper_beam_size = 5;
|
||||
cfg->whisper_beam_patience = -1.0f;
|
||||
}
|
||||
|
||||
int config_load_file(const char *path, voice_config_t *cfg) {
|
||||
@@ -67,6 +106,8 @@ int config_load_file(const char *path, voice_config_t *cfg) {
|
||||
snprintf(cfg->language, sizeof(cfg->language), "%s", value);
|
||||
} else if (strcmp(key, "hotkey") == 0) {
|
||||
snprintf(cfg->hotkey, sizeof(cfg->hotkey), "%s", value);
|
||||
} else if (strcmp(key, "use_gpu") == 0) {
|
||||
cfg->use_gpu = atoi(value);
|
||||
} else if (strcmp(key, "sample_rate") == 0) {
|
||||
cfg->sample_rate = atoi(value);
|
||||
} else if (strcmp(key, "type_delay_us") == 0) {
|
||||
@@ -91,9 +132,143 @@ int config_load_file(const char *path, voice_config_t *cfg) {
|
||||
cfg->vad_max_speech_ms = atoi(value);
|
||||
} else if (strcmp(key, "autotype_enabled") == 0) {
|
||||
cfg->autotype_enabled = atoi(value);
|
||||
} else if (strcmp(key, "filter_bracketed_tags") == 0) {
|
||||
cfg->filter_bracketed_tags = atoi(value);
|
||||
} else if (strcmp(key, "whisper_sampling_strategy") == 0) {
|
||||
cfg->whisper_sampling_strategy = atoi(value);
|
||||
} else if (strcmp(key, "whisper_n_max_text_ctx") == 0) {
|
||||
cfg->whisper_n_max_text_ctx = atoi(value);
|
||||
} else if (strcmp(key, "whisper_offset_ms") == 0) {
|
||||
cfg->whisper_offset_ms = atoi(value);
|
||||
} else if (strcmp(key, "whisper_duration_ms") == 0) {
|
||||
cfg->whisper_duration_ms = atoi(value);
|
||||
} else if (strcmp(key, "whisper_translate") == 0) {
|
||||
cfg->whisper_translate = atoi(value);
|
||||
} else if (strcmp(key, "whisper_detect_language") == 0) {
|
||||
cfg->whisper_detect_language = atoi(value);
|
||||
} else if (strcmp(key, "whisper_no_context") == 0) {
|
||||
cfg->whisper_no_context = atoi(value);
|
||||
} else if (strcmp(key, "whisper_no_timestamps") == 0) {
|
||||
cfg->whisper_no_timestamps = atoi(value);
|
||||
} else if (strcmp(key, "whisper_single_segment") == 0) {
|
||||
cfg->whisper_single_segment = atoi(value);
|
||||
} else if (strcmp(key, "whisper_token_timestamps") == 0) {
|
||||
cfg->whisper_token_timestamps = atoi(value);
|
||||
} else if (strcmp(key, "whisper_thold_pt") == 0) {
|
||||
cfg->whisper_thold_pt = (float)atof(value);
|
||||
} else if (strcmp(key, "whisper_thold_ptsum") == 0) {
|
||||
cfg->whisper_thold_ptsum = (float)atof(value);
|
||||
} else if (strcmp(key, "whisper_max_len") == 0) {
|
||||
cfg->whisper_max_len = atoi(value);
|
||||
} else if (strcmp(key, "whisper_split_on_word") == 0) {
|
||||
cfg->whisper_split_on_word = atoi(value);
|
||||
} else if (strcmp(key, "whisper_max_tokens") == 0) {
|
||||
cfg->whisper_max_tokens = atoi(value);
|
||||
} else if (strcmp(key, "whisper_audio_ctx") == 0) {
|
||||
cfg->whisper_audio_ctx = atoi(value);
|
||||
} else if (strcmp(key, "whisper_tdrz_enable") == 0) {
|
||||
cfg->whisper_tdrz_enable = atoi(value);
|
||||
} else if (strcmp(key, "whisper_suppress_blank") == 0) {
|
||||
cfg->whisper_suppress_blank = atoi(value);
|
||||
} else if (strcmp(key, "whisper_suppress_nst") == 0) {
|
||||
cfg->whisper_suppress_nst = atoi(value);
|
||||
} else if (strcmp(key, "whisper_temperature") == 0) {
|
||||
cfg->whisper_temperature = (float)atof(value);
|
||||
} else if (strcmp(key, "whisper_max_initial_ts") == 0) {
|
||||
cfg->whisper_max_initial_ts = (float)atof(value);
|
||||
} else if (strcmp(key, "whisper_length_penalty") == 0) {
|
||||
cfg->whisper_length_penalty = (float)atof(value);
|
||||
} else if (strcmp(key, "whisper_temperature_inc") == 0) {
|
||||
cfg->whisper_temperature_inc = (float)atof(value);
|
||||
} else if (strcmp(key, "whisper_entropy_thold") == 0) {
|
||||
cfg->whisper_entropy_thold = (float)atof(value);
|
||||
} else if (strcmp(key, "whisper_logprob_thold") == 0) {
|
||||
cfg->whisper_logprob_thold = (float)atof(value);
|
||||
} else if (strcmp(key, "whisper_no_speech_thold") == 0) {
|
||||
cfg->whisper_no_speech_thold = (float)atof(value);
|
||||
} else if (strcmp(key, "whisper_greedy_best_of") == 0) {
|
||||
cfg->whisper_greedy_best_of = atoi(value);
|
||||
} else if (strcmp(key, "whisper_beam_size") == 0) {
|
||||
cfg->whisper_beam_size = atoi(value);
|
||||
} else if (strcmp(key, "whisper_beam_patience") == 0) {
|
||||
cfg->whisper_beam_patience = (float)atof(value);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_save_file(const char *path, const voice_config_t *cfg) {
|
||||
if (!path || !cfg) return -1;
|
||||
|
||||
FILE *f = fopen(path, "w");
|
||||
if (!f) return -2;
|
||||
|
||||
fprintf(f, "# voice_linux runtime configuration\n");
|
||||
fprintf(f, "# Hotkey format supports Ctrl, Alt, Shift, Super + final key symbol\n");
|
||||
fprintf(f, "# Example: Ctrl+Alt+V\n\n");
|
||||
|
||||
fprintf(f, "model_path=%s\n", cfg->model_path);
|
||||
fprintf(f, "use_gpu=%d\n", cfg->use_gpu ? 1 : 0);
|
||||
fprintf(f, "audio_device=%s\n", cfg->audio_device);
|
||||
fprintf(f, "sample_rate=%d\n", cfg->sample_rate);
|
||||
fprintf(f, "language=%s\n", cfg->language);
|
||||
fprintf(f, "hotkey=%s\n", cfg->hotkey);
|
||||
fprintf(f, "type_delay_us=%d\n", cfg->type_delay_us);
|
||||
fprintf(f, "max_record_seconds=%d\n", cfg->max_record_seconds);
|
||||
fprintf(f, "# always-on VAD controls\n");
|
||||
fprintf(f, "always_on=%d\n", cfg->always_on ? 1 : 0);
|
||||
fprintf(f, "# legacy window size (unused by new VAD pipeline, kept for compatibility)\n");
|
||||
fprintf(f, "always_on_window_ms=%d\n\n", cfg->always_on_window_ms);
|
||||
|
||||
fprintf(f, "# live level threshold (0.0 - 1.0)\n");
|
||||
fprintf(f, "vad_peak_threshold=%.4f\n", cfg->vad_peak_threshold);
|
||||
fprintf(f, "# level must stay above threshold this long before speech starts\n");
|
||||
fprintf(f, "vad_trigger_ms=%d\n", cfg->vad_trigger_ms);
|
||||
fprintf(f, "# level must stay below threshold this long before speech ends\n");
|
||||
fprintf(f, "vad_release_ms=%d\n", cfg->vad_release_ms);
|
||||
fprintf(f, "# include this much pre-trigger audio in transcript segment\n");
|
||||
fprintf(f, "vad_preroll_ms=%d\n", cfg->vad_preroll_ms);
|
||||
fprintf(f, "# discard segments shorter than this\n");
|
||||
fprintf(f, "vad_min_speech_ms=%d\n", cfg->vad_min_speech_ms);
|
||||
fprintf(f, "# hard cap for one speech segment\n");
|
||||
fprintf(f, "vad_max_speech_ms=%d\n\n", cfg->vad_max_speech_ms);
|
||||
|
||||
fprintf(f, "autotype_enabled=%d\n", cfg->autotype_enabled ? 1 : 0);
|
||||
fprintf(f, "filter_bracketed_tags=%d\n\n", cfg->filter_bracketed_tags ? 1 : 0);
|
||||
|
||||
fprintf(f, "# whisper decode controls\n");
|
||||
fprintf(f, "whisper_sampling_strategy=%d\n", cfg->whisper_sampling_strategy);
|
||||
fprintf(f, "whisper_n_max_text_ctx=%d\n", cfg->whisper_n_max_text_ctx);
|
||||
fprintf(f, "whisper_offset_ms=%d\n", cfg->whisper_offset_ms);
|
||||
fprintf(f, "whisper_duration_ms=%d\n", cfg->whisper_duration_ms);
|
||||
fprintf(f, "whisper_translate=%d\n", cfg->whisper_translate ? 1 : 0);
|
||||
fprintf(f, "whisper_detect_language=%d\n", cfg->whisper_detect_language ? 1 : 0);
|
||||
fprintf(f, "whisper_no_context=%d\n", cfg->whisper_no_context ? 1 : 0);
|
||||
fprintf(f, "whisper_no_timestamps=%d\n", cfg->whisper_no_timestamps ? 1 : 0);
|
||||
fprintf(f, "whisper_single_segment=%d\n", cfg->whisper_single_segment ? 1 : 0);
|
||||
fprintf(f, "whisper_token_timestamps=%d\n", cfg->whisper_token_timestamps ? 1 : 0);
|
||||
fprintf(f, "whisper_thold_pt=%.4f\n", cfg->whisper_thold_pt);
|
||||
fprintf(f, "whisper_thold_ptsum=%.4f\n", cfg->whisper_thold_ptsum);
|
||||
fprintf(f, "whisper_max_len=%d\n", cfg->whisper_max_len);
|
||||
fprintf(f, "whisper_split_on_word=%d\n", cfg->whisper_split_on_word ? 1 : 0);
|
||||
fprintf(f, "whisper_max_tokens=%d\n", cfg->whisper_max_tokens);
|
||||
fprintf(f, "whisper_audio_ctx=%d\n", cfg->whisper_audio_ctx);
|
||||
fprintf(f, "whisper_tdrz_enable=%d\n", cfg->whisper_tdrz_enable ? 1 : 0);
|
||||
fprintf(f, "whisper_suppress_blank=%d\n", cfg->whisper_suppress_blank ? 1 : 0);
|
||||
fprintf(f, "whisper_suppress_nst=%d\n", cfg->whisper_suppress_nst ? 1 : 0);
|
||||
fprintf(f, "whisper_temperature=%.4f\n", cfg->whisper_temperature);
|
||||
fprintf(f, "whisper_max_initial_ts=%.4f\n", cfg->whisper_max_initial_ts);
|
||||
fprintf(f, "whisper_length_penalty=%.4f\n", cfg->whisper_length_penalty);
|
||||
fprintf(f, "whisper_temperature_inc=%.4f\n", cfg->whisper_temperature_inc);
|
||||
fprintf(f, "whisper_entropy_thold=%.4f\n", cfg->whisper_entropy_thold);
|
||||
fprintf(f, "whisper_logprob_thold=%.4f\n", cfg->whisper_logprob_thold);
|
||||
fprintf(f, "whisper_no_speech_thold=%.4f\n", cfg->whisper_no_speech_thold);
|
||||
fprintf(f, "whisper_greedy_best_of=%d\n", cfg->whisper_greedy_best_of);
|
||||
fprintf(f, "whisper_beam_size=%d\n", cfg->whisper_beam_size);
|
||||
fprintf(f, "whisper_beam_patience=%.4f\n", cfg->whisper_beam_patience);
|
||||
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ typedef struct {
|
||||
char audio_device[256];
|
||||
char language[32];
|
||||
char hotkey[64];
|
||||
int use_gpu;
|
||||
int sample_rate;
|
||||
int type_delay_us;
|
||||
int max_record_seconds;
|
||||
@@ -20,9 +21,48 @@ typedef struct {
|
||||
int vad_min_speech_ms;
|
||||
int vad_max_speech_ms;
|
||||
int autotype_enabled;
|
||||
int filter_bracketed_tags;
|
||||
|
||||
int whisper_sampling_strategy; // 0=greedy, 1=beam search
|
||||
int whisper_n_max_text_ctx;
|
||||
int whisper_offset_ms;
|
||||
int whisper_duration_ms;
|
||||
|
||||
int whisper_translate;
|
||||
int whisper_detect_language;
|
||||
int whisper_no_context;
|
||||
int whisper_no_timestamps;
|
||||
int whisper_single_segment;
|
||||
|
||||
int whisper_token_timestamps;
|
||||
float whisper_thold_pt;
|
||||
float whisper_thold_ptsum;
|
||||
int whisper_max_len;
|
||||
int whisper_split_on_word;
|
||||
int whisper_max_tokens;
|
||||
|
||||
int whisper_audio_ctx;
|
||||
int whisper_tdrz_enable;
|
||||
|
||||
int whisper_suppress_blank;
|
||||
int whisper_suppress_nst;
|
||||
|
||||
float whisper_temperature;
|
||||
float whisper_max_initial_ts;
|
||||
float whisper_length_penalty;
|
||||
|
||||
float whisper_temperature_inc;
|
||||
float whisper_entropy_thold;
|
||||
float whisper_logprob_thold;
|
||||
float whisper_no_speech_thold;
|
||||
|
||||
int whisper_greedy_best_of;
|
||||
int whisper_beam_size;
|
||||
float whisper_beam_patience;
|
||||
} voice_config_t;
|
||||
|
||||
void config_set_defaults(voice_config_t *cfg);
|
||||
int config_load_file(const char *path, voice_config_t *cfg);
|
||||
int config_save_file(const char *path, const voice_config_t *cfg);
|
||||
|
||||
#endif
|
||||
|
||||
+690
-46
@@ -6,10 +6,13 @@
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
typedef struct {
|
||||
GtkApplication *app;
|
||||
@@ -18,7 +21,9 @@ typedef struct {
|
||||
GtkWidget *record_btn;
|
||||
GtkWidget *always_on_btn;
|
||||
GtkWidget *type_check;
|
||||
GtkWidget *filter_tags_check;
|
||||
GtkWidget *transcript_view;
|
||||
GtkWidget *gpu_light;
|
||||
GtkWidget *record_light;
|
||||
GtkWidget *transcribe_light;
|
||||
|
||||
@@ -31,6 +36,38 @@ typedef struct {
|
||||
GtkWidget *max_speech_scale;
|
||||
GtkWidget *debug_label;
|
||||
|
||||
GtkWidget *sampling_combo;
|
||||
GtkWidget *translate_check;
|
||||
GtkWidget *detect_language_check;
|
||||
GtkWidget *no_context_check;
|
||||
GtkWidget *no_timestamps_check;
|
||||
GtkWidget *single_segment_check;
|
||||
GtkWidget *token_timestamps_check;
|
||||
GtkWidget *split_on_word_check;
|
||||
GtkWidget *suppress_blank_check;
|
||||
GtkWidget *suppress_nst_check;
|
||||
GtkWidget *tdrz_enable_check;
|
||||
|
||||
GtkWidget *n_max_text_ctx_spin;
|
||||
GtkWidget *offset_ms_spin;
|
||||
GtkWidget *duration_ms_spin;
|
||||
GtkWidget *max_len_spin;
|
||||
GtkWidget *max_tokens_spin;
|
||||
GtkWidget *audio_ctx_spin;
|
||||
GtkWidget *greedy_best_of_spin;
|
||||
GtkWidget *beam_size_spin;
|
||||
|
||||
GtkWidget *thold_pt_spin;
|
||||
GtkWidget *thold_ptsum_spin;
|
||||
GtkWidget *temperature_spin;
|
||||
GtkWidget *max_initial_ts_spin;
|
||||
GtkWidget *length_penalty_spin;
|
||||
GtkWidget *temperature_inc_spin;
|
||||
GtkWidget *entropy_thold_spin;
|
||||
GtkWidget *logprob_thold_spin;
|
||||
GtkWidget *no_speech_thold_spin;
|
||||
GtkWidget *beam_patience_spin;
|
||||
|
||||
voice_config_t cfg;
|
||||
int use_gpu;
|
||||
int recording;
|
||||
@@ -39,6 +76,10 @@ typedef struct {
|
||||
|
||||
unsigned int meter_timer_id;
|
||||
float live_peak;
|
||||
int runtime_ready;
|
||||
char last_transcript[4096];
|
||||
double last_transcript_time;
|
||||
char cfg_path[512];
|
||||
} gui_state_t;
|
||||
|
||||
static float clampf(float v, float lo, float hi) {
|
||||
@@ -65,8 +106,74 @@ static void append_transcript(gui_state_t *s, const char *text) {
|
||||
gtk_text_buffer_insert(buf, &end, text, -1);
|
||||
}
|
||||
|
||||
static void save_config_best_effort(gui_state_t *s) {
|
||||
if (!s || s->cfg_path[0] == '\0') return;
|
||||
if (config_save_file(s->cfg_path, &s->cfg) != 0) {
|
||||
fprintf(stderr, "gui: warning: failed to save config to %s\n", s->cfg_path);
|
||||
}
|
||||
}
|
||||
|
||||
static void fill_transcribe_params_from_cfg(const gui_state_t *s, transcribe_params_t *tp) {
|
||||
if (!s || !tp) return;
|
||||
|
||||
memset(tp, 0, sizeof(*tp));
|
||||
snprintf(tp->model_path, sizeof(tp->model_path), "%s", s->cfg.model_path);
|
||||
snprintf(tp->language, sizeof(tp->language), "%s", s->cfg.language);
|
||||
tp->use_gpu = s->use_gpu;
|
||||
tp->n_threads = (int)sysconf(_SC_NPROCESSORS_ONLN);
|
||||
tp->filter_bracketed_tags = s->cfg.filter_bracketed_tags;
|
||||
|
||||
tp->sampling_strategy = s->cfg.whisper_sampling_strategy;
|
||||
tp->n_max_text_ctx = s->cfg.whisper_n_max_text_ctx;
|
||||
tp->offset_ms = s->cfg.whisper_offset_ms;
|
||||
tp->duration_ms = s->cfg.whisper_duration_ms;
|
||||
|
||||
tp->translate = s->cfg.whisper_translate;
|
||||
tp->detect_language = s->cfg.whisper_detect_language;
|
||||
tp->no_context = s->cfg.whisper_no_context;
|
||||
tp->no_timestamps = s->cfg.whisper_no_timestamps;
|
||||
tp->single_segment = s->cfg.whisper_single_segment;
|
||||
|
||||
tp->token_timestamps = s->cfg.whisper_token_timestamps;
|
||||
tp->thold_pt = s->cfg.whisper_thold_pt;
|
||||
tp->thold_ptsum = s->cfg.whisper_thold_ptsum;
|
||||
tp->max_len = s->cfg.whisper_max_len;
|
||||
tp->split_on_word = s->cfg.whisper_split_on_word;
|
||||
tp->max_tokens = s->cfg.whisper_max_tokens;
|
||||
|
||||
tp->audio_ctx = s->cfg.whisper_audio_ctx;
|
||||
tp->tdrz_enable = s->cfg.whisper_tdrz_enable;
|
||||
|
||||
tp->suppress_blank = s->cfg.whisper_suppress_blank;
|
||||
tp->suppress_nst = s->cfg.whisper_suppress_nst;
|
||||
|
||||
tp->temperature = s->cfg.whisper_temperature;
|
||||
tp->max_initial_ts = s->cfg.whisper_max_initial_ts;
|
||||
tp->length_penalty = s->cfg.whisper_length_penalty;
|
||||
|
||||
tp->temperature_inc = s->cfg.whisper_temperature_inc;
|
||||
tp->entropy_thold = s->cfg.whisper_entropy_thold;
|
||||
tp->logprob_thold = s->cfg.whisper_logprob_thold;
|
||||
tp->no_speech_thold = s->cfg.whisper_no_speech_thold;
|
||||
|
||||
tp->greedy_best_of = s->cfg.whisper_greedy_best_of;
|
||||
tp->beam_size = s->cfg.whisper_beam_size;
|
||||
tp->beam_patience = s->cfg.whisper_beam_patience;
|
||||
}
|
||||
|
||||
static void push_transcribe_runtime_params(gui_state_t *s) {
|
||||
if (!s || !s->runtime_ready) return;
|
||||
transcribe_params_t tp;
|
||||
fill_transcribe_params_from_cfg(s, &tp);
|
||||
transcribe_update_params(&tp);
|
||||
}
|
||||
|
||||
static void update_activity_lights(gui_state_t *s) {
|
||||
if (!s->record_light || !s->transcribe_light) return;
|
||||
if (!s || !s->gpu_light || !s->record_light || !s->transcribe_light) return;
|
||||
|
||||
const char *gpu_markup = s->use_gpu
|
||||
? "<span foreground='#1976d2'><b>● GPU</b></span>"
|
||||
: "<span foreground='#777777'>● CPU</span>";
|
||||
|
||||
const int rec_on = (s->recording || s->always_on_running) ? 1 : 0;
|
||||
const char *rec_markup = rec_on
|
||||
@@ -77,6 +184,7 @@ static void update_activity_lights(gui_state_t *s) {
|
||||
? "<span foreground='#ef6c00'><b>● Transcribing</b></span>"
|
||||
: "<span foreground='#777777'>● Transcribing</span>";
|
||||
|
||||
gtk_label_set_markup(GTK_LABEL(s->gpu_light), gpu_markup);
|
||||
gtk_label_set_markup(GTK_LABEL(s->record_light), rec_markup);
|
||||
gtk_label_set_markup(GTK_LABEL(s->transcribe_light), tr_markup);
|
||||
}
|
||||
@@ -120,6 +228,12 @@ static void update_always_on_button(gui_state_t *s) {
|
||||
gtk_button_set_label(GTK_BUTTON(s->always_on_btn), s->always_on_running ? "Stop Always-On" : "Start Always-On");
|
||||
}
|
||||
|
||||
static void on_autotype_toggled(GtkToggleButton *btn, gpointer user_data) {
|
||||
gui_state_t *s = (gui_state_t *)user_data;
|
||||
s->cfg.autotype_enabled = gtk_toggle_button_get_active(btn) ? 1 : 0;
|
||||
save_config_best_effort(s);
|
||||
}
|
||||
|
||||
static void log_audio_stats(const float *samples, size_t count, int sample_rate, const char *device_name, char *out, size_t out_sz) {
|
||||
if (!samples || count == 0 || sample_rate <= 0) {
|
||||
snprintf(out, out_sz, "Empty capture");
|
||||
@@ -160,6 +274,28 @@ static int peak_is_speech(const float *samples, size_t count, float threshold) {
|
||||
return peak_abs >= threshold;
|
||||
}
|
||||
|
||||
static double monotonic_seconds(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (double)ts.tv_sec + (double)ts.tv_nsec / 1000000000.0;
|
||||
}
|
||||
|
||||
static int is_duplicate_transcript(gui_state_t *s, const char *text) {
|
||||
if (!s || !text || text[0] == '\0') return 0;
|
||||
if (s->last_transcript[0] == '\0') return 0;
|
||||
|
||||
const double dt = monotonic_seconds() - s->last_transcript_time;
|
||||
if (dt < 0.0 || dt > 1.0) return 0;
|
||||
|
||||
return strcmp(s->last_transcript, text) == 0;
|
||||
}
|
||||
|
||||
static void remember_transcript(gui_state_t *s, const char *text) {
|
||||
if (!s || !text || text[0] == '\0') return;
|
||||
snprintf(s->last_transcript, sizeof(s->last_transcript), "%s", text);
|
||||
s->last_transcript_time = monotonic_seconds();
|
||||
}
|
||||
|
||||
static void process_segment(gui_state_t *s, float *samples, size_t count) {
|
||||
if (!samples || count == 0) {
|
||||
free(samples);
|
||||
@@ -175,7 +311,13 @@ static void process_segment(gui_state_t *s, float *samples, size_t count) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->type_check))) {
|
||||
int should_autotype = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->type_check)) ? 1 : 0;
|
||||
if (should_autotype && gtk_window_is_active(GTK_WINDOW(s->window))) {
|
||||
should_autotype = 0;
|
||||
fprintf(stderr, "gui: autotype skipped because voice_linux window is focused\n");
|
||||
}
|
||||
|
||||
if (should_autotype) {
|
||||
if (typer_capture_focus() != 0) {
|
||||
fprintf(stderr, "gui: failed to capture focus target for autotype\n");
|
||||
}
|
||||
@@ -197,9 +339,16 @@ static void process_segment(gui_state_t *s, float *samples, size_t count) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_duplicate_transcript(s, text)) {
|
||||
fprintf(stderr, "gui: suppressed duplicate transcript within 1s window\n");
|
||||
set_status(s, "Duplicate segment suppressed");
|
||||
free(text);
|
||||
return;
|
||||
}
|
||||
|
||||
append_transcript(s, text);
|
||||
|
||||
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->type_check))) {
|
||||
if (should_autotype) {
|
||||
if (typer_type_text(text) != 0) {
|
||||
fprintf(stderr, "gui: autotype injection failed\n");
|
||||
set_status(s, "Autotype failed (see terminal logs)");
|
||||
@@ -208,6 +357,7 @@ static void process_segment(gui_state_t *s, float *samples, size_t count) {
|
||||
}
|
||||
}
|
||||
|
||||
remember_transcript(s, text);
|
||||
set_status(s, audio_msg);
|
||||
free(text);
|
||||
}
|
||||
@@ -249,6 +399,7 @@ static void on_vad_value_changed(GtkRange *range, gpointer user_data) {
|
||||
(void)range;
|
||||
gui_state_t *s = (gui_state_t *)user_data;
|
||||
apply_vad_params(s);
|
||||
save_config_best_effort(s);
|
||||
}
|
||||
|
||||
static gboolean on_level_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data) {
|
||||
@@ -386,6 +537,8 @@ static void on_always_on_clicked(GtkButton *btn, gpointer user_data) {
|
||||
update_always_on_button(s);
|
||||
update_record_button(s);
|
||||
set_status(s, "Always-on stopped");
|
||||
s->cfg.always_on = 0;
|
||||
save_config_best_effort(s);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -395,9 +548,12 @@ static void on_always_on_clicked(GtkButton *btn, gpointer user_data) {
|
||||
update_always_on_button(s);
|
||||
update_record_button(s);
|
||||
set_status(s, "Always-on active (continuous monitor)");
|
||||
s->cfg.always_on = 1;
|
||||
save_config_best_effort(s);
|
||||
}
|
||||
|
||||
static GtkWidget *add_scale_row(GtkWidget *grid,
|
||||
int col,
|
||||
int row,
|
||||
const char *label_text,
|
||||
double min,
|
||||
@@ -409,14 +565,14 @@ static GtkWidget *add_scale_row(GtkWidget *grid,
|
||||
GtkWidget **out_scale) {
|
||||
GtkWidget *label = gtk_label_new(label_text);
|
||||
gtk_label_set_xalign(GTK_LABEL(label), 0.0f);
|
||||
gtk_grid_attach(GTK_GRID(grid), label, 0, row, 1, 1);
|
||||
gtk_grid_attach(GTK_GRID(grid), label, col, row, 1, 1);
|
||||
|
||||
GtkWidget *scale = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, min, max, step);
|
||||
gtk_scale_set_digits(GTK_SCALE(scale), digits);
|
||||
gtk_scale_set_draw_value(GTK_SCALE(scale), TRUE);
|
||||
gtk_range_set_value(GTK_RANGE(scale), value);
|
||||
gtk_widget_set_hexpand(scale, TRUE);
|
||||
gtk_grid_attach(GTK_GRID(grid), scale, 1, row, 1, 1);
|
||||
gtk_grid_attach(GTK_GRID(grid), scale, col + 1, row, 1, 1);
|
||||
|
||||
g_signal_connect(scale, "value-changed", G_CALLBACK(on_vad_value_changed), s);
|
||||
*out_scale = scale;
|
||||
@@ -424,36 +580,522 @@ static GtkWidget *add_scale_row(GtkWidget *grid,
|
||||
return scale;
|
||||
}
|
||||
|
||||
static void on_filter_tags_toggled(GtkToggleButton *btn, gpointer user_data) {
|
||||
gui_state_t *s = (gui_state_t *)user_data;
|
||||
int enabled = gtk_toggle_button_get_active(btn) ? 1 : 0;
|
||||
s->cfg.filter_bracketed_tags = enabled;
|
||||
transcribe_set_filter_bracketed_tags(enabled);
|
||||
push_transcribe_runtime_params(s);
|
||||
save_config_best_effort(s);
|
||||
}
|
||||
|
||||
static int read_spin_int(GtkWidget *spin) {
|
||||
return gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
|
||||
}
|
||||
|
||||
static float read_spin_float(GtkWidget *spin) {
|
||||
return (float)gtk_spin_button_get_value(GTK_SPIN_BUTTON(spin));
|
||||
}
|
||||
|
||||
static void apply_whisper_settings_from_widgets(gui_state_t *s) {
|
||||
if (!s || !s->sampling_combo) return;
|
||||
|
||||
s->cfg.whisper_sampling_strategy = gtk_combo_box_get_active(GTK_COMBO_BOX(s->sampling_combo)) == 1 ? 1 : 0;
|
||||
|
||||
s->cfg.whisper_translate = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->translate_check)) ? 1 : 0;
|
||||
s->cfg.whisper_detect_language = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->detect_language_check)) ? 1 : 0;
|
||||
s->cfg.whisper_no_context = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->no_context_check)) ? 1 : 0;
|
||||
s->cfg.whisper_no_timestamps = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->no_timestamps_check)) ? 1 : 0;
|
||||
s->cfg.whisper_single_segment = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->single_segment_check)) ? 1 : 0;
|
||||
|
||||
s->cfg.whisper_token_timestamps = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->token_timestamps_check)) ? 1 : 0;
|
||||
s->cfg.whisper_split_on_word = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->split_on_word_check)) ? 1 : 0;
|
||||
s->cfg.whisper_suppress_blank = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->suppress_blank_check)) ? 1 : 0;
|
||||
s->cfg.whisper_suppress_nst = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->suppress_nst_check)) ? 1 : 0;
|
||||
s->cfg.whisper_tdrz_enable = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->tdrz_enable_check)) ? 1 : 0;
|
||||
|
||||
s->cfg.whisper_n_max_text_ctx = read_spin_int(s->n_max_text_ctx_spin);
|
||||
s->cfg.whisper_offset_ms = read_spin_int(s->offset_ms_spin);
|
||||
s->cfg.whisper_duration_ms = read_spin_int(s->duration_ms_spin);
|
||||
s->cfg.whisper_max_len = read_spin_int(s->max_len_spin);
|
||||
s->cfg.whisper_max_tokens = read_spin_int(s->max_tokens_spin);
|
||||
s->cfg.whisper_audio_ctx = read_spin_int(s->audio_ctx_spin);
|
||||
s->cfg.whisper_greedy_best_of = read_spin_int(s->greedy_best_of_spin);
|
||||
s->cfg.whisper_beam_size = read_spin_int(s->beam_size_spin);
|
||||
|
||||
s->cfg.whisper_thold_pt = read_spin_float(s->thold_pt_spin);
|
||||
s->cfg.whisper_thold_ptsum = read_spin_float(s->thold_ptsum_spin);
|
||||
s->cfg.whisper_temperature = read_spin_float(s->temperature_spin);
|
||||
s->cfg.whisper_max_initial_ts = read_spin_float(s->max_initial_ts_spin);
|
||||
s->cfg.whisper_length_penalty = read_spin_float(s->length_penalty_spin);
|
||||
s->cfg.whisper_temperature_inc = read_spin_float(s->temperature_inc_spin);
|
||||
s->cfg.whisper_entropy_thold = read_spin_float(s->entropy_thold_spin);
|
||||
s->cfg.whisper_logprob_thold = read_spin_float(s->logprob_thold_spin);
|
||||
s->cfg.whisper_no_speech_thold = read_spin_float(s->no_speech_thold_spin);
|
||||
s->cfg.whisper_beam_patience = read_spin_float(s->beam_patience_spin);
|
||||
|
||||
push_transcribe_runtime_params(s);
|
||||
save_config_best_effort(s);
|
||||
}
|
||||
|
||||
static void on_whisper_toggle_changed(GtkToggleButton *btn, gpointer user_data) {
|
||||
(void)btn;
|
||||
gui_state_t *s = (gui_state_t *)user_data;
|
||||
apply_whisper_settings_from_widgets(s);
|
||||
}
|
||||
|
||||
static void on_whisper_spin_changed(GtkSpinButton *btn, gpointer user_data) {
|
||||
(void)btn;
|
||||
gui_state_t *s = (gui_state_t *)user_data;
|
||||
apply_whisper_settings_from_widgets(s);
|
||||
}
|
||||
|
||||
static void on_whisper_combo_changed(GtkComboBox *box, gpointer user_data) {
|
||||
(void)box;
|
||||
gui_state_t *s = (gui_state_t *)user_data;
|
||||
apply_whisper_settings_from_widgets(s);
|
||||
}
|
||||
|
||||
static GtkWidget *add_spin_row(GtkWidget *grid,
|
||||
int col,
|
||||
int row,
|
||||
const char *label_text,
|
||||
double min,
|
||||
double max,
|
||||
double step,
|
||||
double value,
|
||||
int digits,
|
||||
gui_state_t *s,
|
||||
GtkWidget **out_spin) {
|
||||
GtkWidget *label = gtk_label_new(label_text);
|
||||
gtk_label_set_xalign(GTK_LABEL(label), 0.0f);
|
||||
gtk_grid_attach(GTK_GRID(grid), label, col, row, 1, 1);
|
||||
|
||||
GtkWidget *spin = gtk_spin_button_new_with_range(min, max, step);
|
||||
gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), digits);
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), value);
|
||||
gtk_widget_set_hexpand(spin, TRUE);
|
||||
gtk_grid_attach(GTK_GRID(grid), spin, col + 1, row, 1, 1);
|
||||
|
||||
g_signal_connect(spin, "value-changed", G_CALLBACK(on_whisper_spin_changed), s);
|
||||
*out_spin = spin;
|
||||
return spin;
|
||||
}
|
||||
|
||||
static GtkWidget *build_whisper_settings_tab(gui_state_t *s) {
|
||||
GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
||||
|
||||
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
|
||||
gtk_container_set_border_width(GTK_CONTAINER(box), 10);
|
||||
gtk_container_add(GTK_CONTAINER(scroll), box);
|
||||
|
||||
GtkWidget *hint = gtk_label_new(
|
||||
"Whisper decode settings apply globally across model sizes.\n"
|
||||
"Changes are saved immediately and applied to new transcriptions.");
|
||||
gtk_label_set_xalign(GTK_LABEL(hint), 0.0f);
|
||||
gtk_box_pack_start(GTK_BOX(box), hint, FALSE, FALSE, 0);
|
||||
|
||||
GtkWidget *checks = gtk_grid_new();
|
||||
gtk_grid_set_row_spacing(GTK_GRID(checks), 6);
|
||||
gtk_grid_set_column_spacing(GTK_GRID(checks), 12);
|
||||
gtk_box_pack_start(GTK_BOX(box), checks, FALSE, FALSE, 0);
|
||||
|
||||
GtkWidget *sampling_label = gtk_label_new("Sampling strategy:");
|
||||
gtk_label_set_xalign(GTK_LABEL(sampling_label), 0.0f);
|
||||
gtk_grid_attach(GTK_GRID(checks), sampling_label, 0, 0, 1, 1);
|
||||
|
||||
s->sampling_combo = gtk_combo_box_text_new();
|
||||
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(s->sampling_combo), "Greedy");
|
||||
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(s->sampling_combo), "Beam Search");
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(s->sampling_combo), s->cfg.whisper_sampling_strategy ? 1 : 0);
|
||||
g_signal_connect(s->sampling_combo, "changed", G_CALLBACK(on_whisper_combo_changed), s);
|
||||
gtk_grid_attach(GTK_GRID(checks), s->sampling_combo, 1, 0, 1, 1);
|
||||
|
||||
s->translate_check = gtk_check_button_new_with_label("Translate to English");
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->translate_check), s->cfg.whisper_translate ? TRUE : FALSE);
|
||||
g_signal_connect(s->translate_check, "toggled", G_CALLBACK(on_whisper_toggle_changed), s);
|
||||
gtk_grid_attach(GTK_GRID(checks), s->translate_check, 0, 1, 1, 1);
|
||||
|
||||
s->detect_language_check = gtk_check_button_new_with_label("Auto detect language");
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->detect_language_check), s->cfg.whisper_detect_language ? TRUE : FALSE);
|
||||
g_signal_connect(s->detect_language_check, "toggled", G_CALLBACK(on_whisper_toggle_changed), s);
|
||||
gtk_grid_attach(GTK_GRID(checks), s->detect_language_check, 1, 1, 1, 1);
|
||||
|
||||
s->no_context_check = gtk_check_button_new_with_label("No context");
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->no_context_check), s->cfg.whisper_no_context ? TRUE : FALSE);
|
||||
g_signal_connect(s->no_context_check, "toggled", G_CALLBACK(on_whisper_toggle_changed), s);
|
||||
gtk_grid_attach(GTK_GRID(checks), s->no_context_check, 0, 2, 1, 1);
|
||||
|
||||
s->no_timestamps_check = gtk_check_button_new_with_label("No timestamps");
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->no_timestamps_check), s->cfg.whisper_no_timestamps ? TRUE : FALSE);
|
||||
g_signal_connect(s->no_timestamps_check, "toggled", G_CALLBACK(on_whisper_toggle_changed), s);
|
||||
gtk_grid_attach(GTK_GRID(checks), s->no_timestamps_check, 1, 2, 1, 1);
|
||||
|
||||
s->single_segment_check = gtk_check_button_new_with_label("Single segment");
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->single_segment_check), s->cfg.whisper_single_segment ? TRUE : FALSE);
|
||||
g_signal_connect(s->single_segment_check, "toggled", G_CALLBACK(on_whisper_toggle_changed), s);
|
||||
gtk_grid_attach(GTK_GRID(checks), s->single_segment_check, 0, 3, 1, 1);
|
||||
|
||||
s->token_timestamps_check = gtk_check_button_new_with_label("Token timestamps");
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->token_timestamps_check), s->cfg.whisper_token_timestamps ? TRUE : FALSE);
|
||||
g_signal_connect(s->token_timestamps_check, "toggled", G_CALLBACK(on_whisper_toggle_changed), s);
|
||||
gtk_grid_attach(GTK_GRID(checks), s->token_timestamps_check, 1, 3, 1, 1);
|
||||
|
||||
s->split_on_word_check = gtk_check_button_new_with_label("Split on word");
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->split_on_word_check), s->cfg.whisper_split_on_word ? TRUE : FALSE);
|
||||
g_signal_connect(s->split_on_word_check, "toggled", G_CALLBACK(on_whisper_toggle_changed), s);
|
||||
gtk_grid_attach(GTK_GRID(checks), s->split_on_word_check, 0, 4, 1, 1);
|
||||
|
||||
s->suppress_blank_check = gtk_check_button_new_with_label("Suppress blank tokens");
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->suppress_blank_check), s->cfg.whisper_suppress_blank ? TRUE : FALSE);
|
||||
g_signal_connect(s->suppress_blank_check, "toggled", G_CALLBACK(on_whisper_toggle_changed), s);
|
||||
gtk_grid_attach(GTK_GRID(checks), s->suppress_blank_check, 1, 4, 1, 1);
|
||||
|
||||
s->suppress_nst_check = gtk_check_button_new_with_label("Suppress non-speech tokens");
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->suppress_nst_check), s->cfg.whisper_suppress_nst ? TRUE : FALSE);
|
||||
g_signal_connect(s->suppress_nst_check, "toggled", G_CALLBACK(on_whisper_toggle_changed), s);
|
||||
gtk_grid_attach(GTK_GRID(checks), s->suppress_nst_check, 0, 5, 1, 1);
|
||||
|
||||
s->tdrz_enable_check = gtk_check_button_new_with_label("Enable tinydiarize (TDRZ)");
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->tdrz_enable_check), s->cfg.whisper_tdrz_enable ? TRUE : FALSE);
|
||||
g_signal_connect(s->tdrz_enable_check, "toggled", G_CALLBACK(on_whisper_toggle_changed), s);
|
||||
gtk_grid_attach(GTK_GRID(checks), s->tdrz_enable_check, 1, 5, 1, 1);
|
||||
|
||||
GtkWidget *grid = gtk_grid_new();
|
||||
gtk_grid_set_row_spacing(GTK_GRID(grid), 6);
|
||||
gtk_grid_set_column_spacing(GTK_GRID(grid), 10);
|
||||
gtk_box_pack_start(GTK_BOX(box), grid, FALSE, FALSE, 0);
|
||||
|
||||
add_spin_row(grid, 0, 0, "n_max_text_ctx", 0.0, 32768.0, 1.0,
|
||||
(double)s->cfg.whisper_n_max_text_ctx, 0, s, &s->n_max_text_ctx_spin);
|
||||
add_spin_row(grid, 2, 0, "offset_ms", 0.0, 60000.0, 10.0,
|
||||
(double)s->cfg.whisper_offset_ms, 0, s, &s->offset_ms_spin);
|
||||
add_spin_row(grid, 0, 1, "duration_ms (0=all)", 0.0, 600000.0, 100.0,
|
||||
(double)s->cfg.whisper_duration_ms, 0, s, &s->duration_ms_spin);
|
||||
add_spin_row(grid, 2, 1, "max_len", 0.0, 1024.0, 1.0,
|
||||
(double)s->cfg.whisper_max_len, 0, s, &s->max_len_spin);
|
||||
add_spin_row(grid, 0, 2, "max_tokens", 0.0, 1024.0, 1.0,
|
||||
(double)s->cfg.whisper_max_tokens, 0, s, &s->max_tokens_spin);
|
||||
add_spin_row(grid, 2, 2, "audio_ctx", 0.0, 4096.0, 1.0,
|
||||
(double)s->cfg.whisper_audio_ctx, 0, s, &s->audio_ctx_spin);
|
||||
|
||||
add_spin_row(grid, 0, 3, "thold_pt", 0.0, 1.0, 0.001,
|
||||
(double)s->cfg.whisper_thold_pt, 3, s, &s->thold_pt_spin);
|
||||
add_spin_row(grid, 2, 3, "thold_ptsum", 0.0, 1.0, 0.001,
|
||||
(double)s->cfg.whisper_thold_ptsum, 3, s, &s->thold_ptsum_spin);
|
||||
add_spin_row(grid, 0, 4, "temperature", 0.0, 2.0, 0.01,
|
||||
(double)s->cfg.whisper_temperature, 2, s, &s->temperature_spin);
|
||||
add_spin_row(grid, 2, 4, "max_initial_ts", 0.0, 5.0, 0.01,
|
||||
(double)s->cfg.whisper_max_initial_ts, 2, s, &s->max_initial_ts_spin);
|
||||
add_spin_row(grid, 0, 5, "length_penalty", -2.0, 2.0, 0.01,
|
||||
(double)s->cfg.whisper_length_penalty, 2, s, &s->length_penalty_spin);
|
||||
add_spin_row(grid, 2, 5, "temperature_inc", 0.0, 1.0, 0.01,
|
||||
(double)s->cfg.whisper_temperature_inc, 2, s, &s->temperature_inc_spin);
|
||||
add_spin_row(grid, 0, 6, "entropy_thold", -5.0, 10.0, 0.01,
|
||||
(double)s->cfg.whisper_entropy_thold, 2, s, &s->entropy_thold_spin);
|
||||
add_spin_row(grid, 2, 6, "logprob_thold", -5.0, 1.0, 0.01,
|
||||
(double)s->cfg.whisper_logprob_thold, 2, s, &s->logprob_thold_spin);
|
||||
add_spin_row(grid, 0, 7, "no_speech_thold", 0.0, 1.0, 0.01,
|
||||
(double)s->cfg.whisper_no_speech_thold, 2, s, &s->no_speech_thold_spin);
|
||||
add_spin_row(grid, 2, 7, "greedy_best_of", 1.0, 10.0, 1.0,
|
||||
(double)s->cfg.whisper_greedy_best_of, 0, s, &s->greedy_best_of_spin);
|
||||
add_spin_row(grid, 0, 8, "beam_size", 1.0, 20.0, 1.0,
|
||||
(double)s->cfg.whisper_beam_size, 0, s, &s->beam_size_spin);
|
||||
add_spin_row(grid, 2, 8, "beam_patience", -1.0, 5.0, 0.01,
|
||||
(double)s->cfg.whisper_beam_patience, 2, s, &s->beam_patience_spin);
|
||||
|
||||
return scroll;
|
||||
}
|
||||
|
||||
static int cmp_str_ptr(const void *a, const void *b) {
|
||||
const char *sa = *(const char * const *)a;
|
||||
const char *sb = *(const char * const *)b;
|
||||
return strcmp(sa, sb);
|
||||
}
|
||||
|
||||
static int scan_model_paths(char out[][512], int max_out) {
|
||||
DIR *d = opendir("./models");
|
||||
if (!d) return 0;
|
||||
|
||||
int n = 0;
|
||||
struct dirent *ent;
|
||||
while ((ent = readdir(d)) != NULL && n < max_out) {
|
||||
const char *name = ent->d_name;
|
||||
if (strncmp(name, "ggml-", 5) != 0) continue;
|
||||
size_t len = strlen(name);
|
||||
if (len < 5 || strcmp(name + len - 4, ".bin") != 0) continue;
|
||||
snprintf(out[n], 512, "./models/%s", name);
|
||||
n++;
|
||||
}
|
||||
closedir(d);
|
||||
|
||||
if (n > 1) {
|
||||
char *ptrs[64];
|
||||
for (int i = 0; i < n; ++i) ptrs[i] = out[i];
|
||||
qsort(ptrs, (size_t)n, sizeof(ptrs[0]), cmp_str_ptr);
|
||||
|
||||
char sorted[64][512];
|
||||
for (int i = 0; i < n; ++i) {
|
||||
snprintf(sorted[i], sizeof(sorted[i]), "%s", ptrs[i]);
|
||||
}
|
||||
for (int i = 0; i < n; ++i) {
|
||||
snprintf(out[i], 512, "%s", sorted[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static const char *base_name(const char *path) {
|
||||
const char *slash = strrchr(path, '/');
|
||||
return slash ? (slash + 1) : path;
|
||||
}
|
||||
|
||||
static long long model_size_bytes(const char *path) {
|
||||
struct stat st;
|
||||
if (!path) return -1;
|
||||
if (stat(path, &st) != 0) return -1;
|
||||
return (long long)st.st_size;
|
||||
}
|
||||
|
||||
static void format_size_human(long long bytes, char *out, size_t out_sz) {
|
||||
static const char *units[] = { "B", "KB", "MB", "GB", "TB" };
|
||||
double v = (double)(bytes < 0 ? 0 : bytes);
|
||||
int unit = 0;
|
||||
while (v >= 1024.0 && unit < 4) {
|
||||
v /= 1024.0;
|
||||
unit++;
|
||||
}
|
||||
snprintf(out, out_sz, "%.1f %s", v, units[unit]);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
transcribe_params_t tp;
|
||||
int rc;
|
||||
volatile gint done;
|
||||
} transcribe_init_worker_t;
|
||||
|
||||
static gpointer transcribe_init_worker(gpointer user_data) {
|
||||
transcribe_init_worker_t *w = (transcribe_init_worker_t *)user_data;
|
||||
w->rc = transcribe_init(&w->tp);
|
||||
g_atomic_int_set(&w->done, 1);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int run_model_init_with_progress(gui_state_t *s) {
|
||||
GtkWidget *dialog = gtk_dialog_new();
|
||||
gtk_window_set_title(GTK_WINDOW(dialog), "Loading Model");
|
||||
gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
|
||||
gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
|
||||
|
||||
GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
|
||||
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
|
||||
gtk_container_set_border_width(GTK_CONTAINER(box), 12);
|
||||
gtk_box_pack_start(GTK_BOX(content), box, TRUE, TRUE, 0);
|
||||
|
||||
char msg[640];
|
||||
snprintf(msg, sizeof(msg), "Loading %s (%s mode)...",
|
||||
base_name(s->cfg.model_path), s->use_gpu ? "GPU" : "CPU");
|
||||
GtkWidget *label = gtk_label_new(msg);
|
||||
gtk_label_set_xalign(GTK_LABEL(label), 0.0f);
|
||||
gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 0);
|
||||
|
||||
GtkWidget *bar = gtk_progress_bar_new();
|
||||
gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(bar), TRUE);
|
||||
gtk_progress_bar_set_text(GTK_PROGRESS_BAR(bar), "Initializing whisper context...");
|
||||
gtk_box_pack_start(GTK_BOX(box), bar, FALSE, FALSE, 0);
|
||||
|
||||
gtk_widget_show_all(dialog);
|
||||
|
||||
transcribe_init_worker_t worker;
|
||||
memset(&worker, 0, sizeof(worker));
|
||||
fill_transcribe_params_from_cfg(s, &worker.tp);
|
||||
worker.done = 0;
|
||||
|
||||
GThread *thread = g_thread_new("transcribe-init", transcribe_init_worker, &worker);
|
||||
|
||||
int ticks = 0;
|
||||
while (!g_atomic_int_get(&worker.done)) {
|
||||
while (gtk_events_pending()) gtk_main_iteration();
|
||||
gtk_progress_bar_pulse(GTK_PROGRESS_BAR(bar));
|
||||
|
||||
ticks++;
|
||||
if ((ticks % 20) == 0) {
|
||||
char txt[128];
|
||||
snprintf(txt, sizeof(txt), "Loading... %ds", ticks / 20);
|
||||
gtk_progress_bar_set_text(GTK_PROGRESS_BAR(bar), txt);
|
||||
}
|
||||
|
||||
g_usleep(50000);
|
||||
}
|
||||
|
||||
g_thread_join(thread);
|
||||
gtk_widget_destroy(dialog);
|
||||
return worker.rc;
|
||||
}
|
||||
|
||||
static int run_startup_dialog(gui_state_t *s, GtkWindow *parent) {
|
||||
char model_paths[64][512];
|
||||
int model_count = scan_model_paths(model_paths, 64);
|
||||
if (model_count <= 0) {
|
||||
GtkWidget *msg = gtk_message_dialog_new(
|
||||
parent,
|
||||
GTK_DIALOG_MODAL,
|
||||
GTK_MESSAGE_ERROR,
|
||||
GTK_BUTTONS_CLOSE,
|
||||
"No whisper models found in ./models\nRun scripts/download_models.sh first.");
|
||||
gtk_dialog_run(GTK_DIALOG(msg));
|
||||
gtk_widget_destroy(msg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
GtkWidget *dialog = gtk_dialog_new_with_buttons(
|
||||
"Startup Options",
|
||||
parent,
|
||||
GTK_DIALOG_MODAL,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
"_Start", GTK_RESPONSE_OK,
|
||||
NULL);
|
||||
|
||||
GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
|
||||
GtkWidget *grid = gtk_grid_new();
|
||||
gtk_grid_set_row_spacing(GTK_GRID(grid), 8);
|
||||
gtk_grid_set_column_spacing(GTK_GRID(grid), 8);
|
||||
gtk_container_set_border_width(GTK_CONTAINER(grid), 10);
|
||||
gtk_box_pack_start(GTK_BOX(content), grid, TRUE, TRUE, 0);
|
||||
|
||||
GtkWidget *gpu_label = gtk_label_new("Compute mode:");
|
||||
gtk_label_set_xalign(GTK_LABEL(gpu_label), 0.0f);
|
||||
gtk_grid_attach(GTK_GRID(grid), gpu_label, 0, 0, 1, 1);
|
||||
|
||||
GtkWidget *gpu_combo = gtk_combo_box_text_new();
|
||||
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(gpu_combo), "CPU");
|
||||
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(gpu_combo), "GPU");
|
||||
|
||||
int gpu_default = s->use_gpu;
|
||||
if (gpu_default == 0 && access("/dev/nvidia0", F_OK) == 0) {
|
||||
gpu_default = 1;
|
||||
}
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(gpu_combo), gpu_default ? 1 : 0);
|
||||
gtk_grid_attach(GTK_GRID(grid), gpu_combo, 1, 0, 1, 1);
|
||||
|
||||
GtkWidget *model_label = gtk_label_new("Whisper model:");
|
||||
gtk_label_set_xalign(GTK_LABEL(model_label), 0.0f);
|
||||
gtk_grid_attach(GTK_GRID(grid), model_label, 0, 1, 1, 1);
|
||||
|
||||
GtkWidget *model_combo = gtk_combo_box_text_new();
|
||||
int active_model_index = 0;
|
||||
for (int i = 0; i < model_count; ++i) {
|
||||
const char *path = model_paths[i];
|
||||
char entry[256];
|
||||
long long sz = model_size_bytes(path);
|
||||
if (sz >= 0) {
|
||||
char pretty[32];
|
||||
format_size_human(sz, pretty, sizeof(pretty));
|
||||
snprintf(entry, sizeof(entry), "%.220s (%s)", base_name(path), pretty);
|
||||
} else {
|
||||
snprintf(entry, sizeof(entry), "%.250s", base_name(path));
|
||||
}
|
||||
|
||||
gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(model_combo), path, entry);
|
||||
if (strcmp(path, s->cfg.model_path) == 0) {
|
||||
active_model_index = i;
|
||||
}
|
||||
}
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(model_combo), active_model_index);
|
||||
gtk_grid_attach(GTK_GRID(grid), model_combo, 1, 1, 1, 1);
|
||||
|
||||
gtk_widget_show_all(dialog);
|
||||
int rc = gtk_dialog_run(GTK_DIALOG(dialog));
|
||||
|
||||
if (rc == GTK_RESPONSE_OK) {
|
||||
const gchar *active_model = gtk_combo_box_get_active_id(GTK_COMBO_BOX(model_combo));
|
||||
int gpu_active = gtk_combo_box_get_active(GTK_COMBO_BOX(gpu_combo));
|
||||
s->use_gpu = (gpu_active == 1) ? 1 : 0;
|
||||
if (active_model && active_model[0] != '\0') {
|
||||
snprintf(s->cfg.model_path, sizeof(s->cfg.model_path), "%s", active_model);
|
||||
}
|
||||
s->cfg.use_gpu = s->use_gpu;
|
||||
save_config_best_effort(s);
|
||||
gtk_widget_destroy(dialog);
|
||||
return 0;
|
||||
}
|
||||
|
||||
gtk_widget_destroy(dialog);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void on_app_activate(GtkApplication *app, gpointer user_data) {
|
||||
gui_state_t *s = (gui_state_t *)user_data;
|
||||
|
||||
if (!s->runtime_ready) {
|
||||
if (run_startup_dialog(s, NULL) != 0) {
|
||||
g_application_quit(G_APPLICATION(app));
|
||||
return;
|
||||
}
|
||||
|
||||
if (run_model_init_with_progress(s) != 0) {
|
||||
GtkWidget *msg = gtk_message_dialog_new(
|
||||
NULL,
|
||||
GTK_DIALOG_MODAL,
|
||||
GTK_MESSAGE_ERROR,
|
||||
GTK_BUTTONS_CLOSE,
|
||||
"Failed to initialize model:\n%s",
|
||||
s->cfg.model_path);
|
||||
gtk_dialog_run(GTK_DIALOG(msg));
|
||||
gtk_widget_destroy(msg);
|
||||
g_application_quit(G_APPLICATION(app));
|
||||
return;
|
||||
}
|
||||
|
||||
if (typer_init(s->cfg.type_delay_us) != 0) {
|
||||
GtkWidget *msg = gtk_message_dialog_new(
|
||||
NULL,
|
||||
GTK_DIALOG_MODAL,
|
||||
GTK_MESSAGE_ERROR,
|
||||
GTK_BUTTONS_CLOSE,
|
||||
"Failed to initialize X11 typing backend.");
|
||||
gtk_dialog_run(GTK_DIALOG(msg));
|
||||
gtk_widget_destroy(msg);
|
||||
g_application_quit(G_APPLICATION(app));
|
||||
return;
|
||||
}
|
||||
|
||||
s->runtime_ready = 1;
|
||||
}
|
||||
|
||||
s->window = gtk_application_window_new(app);
|
||||
gtk_window_set_title(GTK_WINDOW(s->window), "voice_linux");
|
||||
gtk_window_set_default_size(GTK_WINDOW(s->window), 760, 640);
|
||||
char title[128];
|
||||
snprintf(title, sizeof(title), "voice_linux %s", VOICE_LINUX_VERSION);
|
||||
gtk_window_set_title(GTK_WINDOW(s->window), title);
|
||||
gtk_window_set_default_size(GTK_WINDOW(s->window), 760, 780);
|
||||
|
||||
GtkWidget *outer = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
|
||||
gtk_container_set_border_width(GTK_CONTAINER(outer), 12);
|
||||
gtk_container_add(GTK_CONTAINER(s->window), outer);
|
||||
|
||||
char info[1024];
|
||||
snprintf(info, sizeof(info), "Version: %s\nDevice: %s\nModel: %s\nLanguage: %s\nGPU: %s",
|
||||
VOICE_LINUX_VERSION, s->cfg.audio_device, s->cfg.model_path, s->cfg.language, s->use_gpu ? "on" : "off");
|
||||
GtkWidget *info_label = gtk_label_new(info);
|
||||
gtk_label_set_xalign(GTK_LABEL(info_label), 0.0f);
|
||||
gtk_box_pack_start(GTK_BOX(outer), info_label, FALSE, FALSE, 0);
|
||||
|
||||
s->status_label = gtk_label_new("Idle");
|
||||
gtk_label_set_xalign(GTK_LABEL(s->status_label), 0.0f);
|
||||
gtk_box_pack_start(GTK_BOX(outer), s->status_label, FALSE, FALSE, 0);
|
||||
|
||||
GtkWidget *notebook = gtk_notebook_new();
|
||||
gtk_box_pack_start(GTK_BOX(outer), notebook, TRUE, TRUE, 0);
|
||||
|
||||
GtkWidget *main_page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
|
||||
gtk_container_set_border_width(GTK_CONTAINER(main_page), 6);
|
||||
|
||||
GtkWidget *lights_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 14);
|
||||
s->gpu_light = gtk_label_new(NULL);
|
||||
s->record_light = gtk_label_new(NULL);
|
||||
s->transcribe_light = gtk_label_new(NULL);
|
||||
gtk_label_set_use_markup(GTK_LABEL(s->gpu_light), TRUE);
|
||||
gtk_label_set_use_markup(GTK_LABEL(s->record_light), TRUE);
|
||||
gtk_label_set_use_markup(GTK_LABEL(s->transcribe_light), TRUE);
|
||||
gtk_box_pack_start(GTK_BOX(lights_row), s->gpu_light, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(lights_row), s->record_light, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(lights_row), s->transcribe_light, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(outer), lights_row, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(main_page), lights_row, FALSE, FALSE, 0);
|
||||
|
||||
s->record_btn = gtk_button_new_with_label("Start Recording");
|
||||
gtk_widget_set_name(s->record_btn, "record-btn");
|
||||
@@ -478,19 +1120,31 @@ static void on_app_activate(GtkApplication *app, gpointer user_data) {
|
||||
update_record_button(s);
|
||||
|
||||
g_signal_connect(s->record_btn, "clicked", G_CALLBACK(on_record_clicked), s);
|
||||
gtk_box_pack_start(GTK_BOX(outer), s->record_btn, FALSE, FALSE, 0);
|
||||
update_activity_lights(s);
|
||||
|
||||
s->always_on_btn = gtk_button_new_with_label("Start Always-On");
|
||||
g_signal_connect(s->always_on_btn, "clicked", G_CALLBACK(on_always_on_clicked), s);
|
||||
gtk_box_pack_start(GTK_BOX(outer), s->always_on_btn, FALSE, FALSE, 0);
|
||||
|
||||
GtkWidget *btn_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||
gtk_box_pack_start(GTK_BOX(btn_row), s->record_btn, TRUE, TRUE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(btn_row), s->always_on_btn, TRUE, TRUE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(main_page), btn_row, FALSE, FALSE, 0);
|
||||
update_activity_lights(s);
|
||||
|
||||
GtkWidget *checks_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
|
||||
s->type_check = gtk_check_button_new_with_label("Type transcript into focused window");
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->type_check), s->cfg.autotype_enabled ? TRUE : FALSE);
|
||||
gtk_box_pack_start(GTK_BOX(outer), s->type_check, FALSE, FALSE, 0);
|
||||
g_signal_connect(s->type_check, "toggled", G_CALLBACK(on_autotype_toggled), s);
|
||||
gtk_box_pack_start(GTK_BOX(checks_row), s->type_check, TRUE, TRUE, 0);
|
||||
|
||||
s->filter_tags_check = gtk_check_button_new_with_label("Filter bracketed tags like [laughs]");
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->filter_tags_check), s->cfg.filter_bracketed_tags ? TRUE : FALSE);
|
||||
g_signal_connect(s->filter_tags_check, "toggled", G_CALLBACK(on_filter_tags_toggled), s);
|
||||
gtk_box_pack_start(GTK_BOX(checks_row), s->filter_tags_check, TRUE, TRUE, 0);
|
||||
|
||||
gtk_box_pack_start(GTK_BOX(main_page), checks_row, FALSE, FALSE, 0);
|
||||
|
||||
GtkWidget *meter_frame = gtk_frame_new("Live Microphone Level");
|
||||
gtk_box_pack_start(GTK_BOX(outer), meter_frame, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(main_page), meter_frame, FALSE, FALSE, 0);
|
||||
|
||||
GtkWidget *meter_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
|
||||
gtk_container_set_border_width(GTK_CONTAINER(meter_box), 8);
|
||||
@@ -506,17 +1160,17 @@ static void on_app_activate(GtkApplication *app, gpointer user_data) {
|
||||
gtk_grid_set_column_spacing(GTK_GRID(grid), 10);
|
||||
gtk_box_pack_start(GTK_BOX(meter_box), grid, FALSE, FALSE, 0);
|
||||
|
||||
add_scale_row(grid, 0, "Threshold (0.0-1.0)", 0.0, 1.0, 0.001,
|
||||
add_scale_row(grid, 0, 0, "Threshold (0.0-1.0)", 0.0, 1.0, 0.001,
|
||||
s->cfg.vad_peak_threshold, 3, s, &s->threshold_scale);
|
||||
add_scale_row(grid, 1, "Trigger ms (above threshold)", 10.0, 2000.0, 10.0,
|
||||
add_scale_row(grid, 2, 0, "Trigger ms (above threshold)", 10.0, 2000.0, 10.0,
|
||||
(double)s->cfg.vad_trigger_ms, 0, s, &s->trigger_scale);
|
||||
add_scale_row(grid, 2, "Release ms (below threshold)", 10.0, 4000.0, 10.0,
|
||||
add_scale_row(grid, 0, 1, "Release ms (below threshold)", 10.0, 4000.0, 10.0,
|
||||
(double)s->cfg.vad_release_ms, 0, s, &s->release_scale);
|
||||
add_scale_row(grid, 3, "Preroll ms (audio before trigger)", 0.0, 3000.0, 10.0,
|
||||
add_scale_row(grid, 2, 1, "Preroll ms (audio before trigger)", 0.0, 3000.0, 10.0,
|
||||
(double)s->cfg.vad_preroll_ms, 0, s, &s->preroll_scale);
|
||||
add_scale_row(grid, 4, "Min speech ms", 50.0, 5000.0, 10.0,
|
||||
add_scale_row(grid, 0, 2, "Min speech ms", 50.0, 5000.0, 10.0,
|
||||
(double)s->cfg.vad_min_speech_ms, 0, s, &s->min_speech_scale);
|
||||
add_scale_row(grid, 5, "Max speech ms", 200.0, 60000.0, 100.0,
|
||||
add_scale_row(grid, 2, 2, "Max speech ms", 200.0, 60000.0, 100.0,
|
||||
(double)s->cfg.vad_max_speech_ms, 0, s, &s->max_speech_scale);
|
||||
|
||||
GtkWidget *hint = gtk_label_new(
|
||||
@@ -530,9 +1184,10 @@ static void on_app_activate(GtkApplication *app, gpointer user_data) {
|
||||
gtk_box_pack_start(GTK_BOX(meter_box), s->debug_label, FALSE, FALSE, 0);
|
||||
|
||||
GtkWidget *frame = gtk_frame_new("Transcript Log");
|
||||
gtk_box_pack_start(GTK_BOX(outer), frame, TRUE, TRUE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(main_page), frame, TRUE, TRUE, 0);
|
||||
|
||||
GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
|
||||
gtk_widget_set_size_request(scroll, -1, 320);
|
||||
gtk_container_add(GTK_CONTAINER(frame), scroll);
|
||||
|
||||
s->transcript_view = gtk_text_view_new();
|
||||
@@ -541,6 +1196,11 @@ static void on_app_activate(GtkApplication *app, gpointer user_data) {
|
||||
|
||||
apply_vad_params(s);
|
||||
update_always_on_button(s);
|
||||
|
||||
gtk_notebook_append_page(GTK_NOTEBOOK(notebook), main_page, gtk_label_new("Main"));
|
||||
GtkWidget *whisper_page = build_whisper_settings_tab(s);
|
||||
gtk_notebook_append_page(GTK_NOTEBOOK(notebook), whisper_page, gtk_label_new("Whisper"));
|
||||
|
||||
gtk_widget_show_all(s->window);
|
||||
|
||||
s->meter_timer_id = g_timeout_add(60, meter_tick_cb, s);
|
||||
@@ -580,11 +1240,13 @@ int main(int argc, char **argv) {
|
||||
|
||||
const char *cfg_path = arg_value(argc, argv, "--config");
|
||||
if (!cfg_path) cfg_path = "./config.ini";
|
||||
snprintf(st.cfg_path, sizeof(st.cfg_path), "%s", cfg_path);
|
||||
if (config_load_file(cfg_path, &st.cfg) != 0) {
|
||||
fprintf(stderr, "warning: failed to load %s, using defaults\n", cfg_path);
|
||||
}
|
||||
|
||||
st.use_gpu = arg_flag(argc, argv, "--gpu") ? 1 : 0;
|
||||
st.use_gpu = st.cfg.use_gpu ? 1 : 0;
|
||||
if (arg_flag(argc, argv, "--gpu")) st.use_gpu = 1;
|
||||
if (arg_flag(argc, argv, "--cpu")) st.use_gpu = 0;
|
||||
|
||||
audio_params_t ap = {0};
|
||||
@@ -592,12 +1254,6 @@ int main(int argc, char **argv) {
|
||||
ap.sample_rate = st.cfg.sample_rate;
|
||||
ap.max_seconds = st.cfg.max_record_seconds;
|
||||
|
||||
transcribe_params_t tp = {0};
|
||||
snprintf(tp.model_path, sizeof(tp.model_path), "%s", st.cfg.model_path);
|
||||
snprintf(tp.language, sizeof(tp.language), "%s", st.cfg.language);
|
||||
tp.use_gpu = st.use_gpu;
|
||||
tp.n_threads = (int)sysconf(_SC_NPROCESSORS_ONLN);
|
||||
|
||||
if (audio_init(&ap) != 0) {
|
||||
fprintf(stderr, "fatal: audio init failed\n");
|
||||
return 1;
|
||||
@@ -613,18 +1269,6 @@ int main(int argc, char **argv) {
|
||||
};
|
||||
audio_set_vad_params(&vp);
|
||||
|
||||
if (transcribe_init(&tp) != 0) {
|
||||
fprintf(stderr, "fatal: transcribe init failed\n");
|
||||
audio_cleanup();
|
||||
return 1;
|
||||
}
|
||||
if (typer_init(st.cfg.type_delay_us) != 0) {
|
||||
fprintf(stderr, "fatal: typer init failed\n");
|
||||
transcribe_cleanup();
|
||||
audio_cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
st.app = gtk_application_new("local.voice_linux", G_APPLICATION_DEFAULT_FLAGS);
|
||||
g_signal_connect(st.app, "activate", G_CALLBACK(on_app_activate), &st);
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ int main(int argc, char **argv) {
|
||||
snprintf(tp.language, sizeof(tp.language), "%s", cfg.language);
|
||||
tp.use_gpu = use_gpu;
|
||||
tp.n_threads = (int)sysconf(_SC_NPROCESSORS_ONLN);
|
||||
tp.filter_bracketed_tags = cfg.filter_bracketed_tags;
|
||||
|
||||
hotkey_ctx_t hk;
|
||||
|
||||
|
||||
+135
-5
@@ -1,5 +1,6 @@
|
||||
#include "transcribe.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -8,15 +9,124 @@
|
||||
#define WITH_WHISPER 0
|
||||
#endif
|
||||
|
||||
static int g_filter_bracketed_tags = 1;
|
||||
|
||||
static char *filter_bracketed_tags_copy(const char *input) {
|
||||
if (!input) return NULL;
|
||||
|
||||
const size_t len = strlen(input);
|
||||
char *no_tags = (char *)malloc(len + 1);
|
||||
if (!no_tags) return NULL;
|
||||
|
||||
size_t w = 0;
|
||||
int bracket_depth = 0;
|
||||
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
unsigned char ch = (unsigned char)input[i];
|
||||
|
||||
if (ch == '[') {
|
||||
bracket_depth++;
|
||||
continue;
|
||||
}
|
||||
if (ch == ']' && bracket_depth > 0) {
|
||||
bracket_depth--;
|
||||
continue;
|
||||
}
|
||||
if (bracket_depth > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
no_tags[w++] = (char)ch;
|
||||
}
|
||||
no_tags[w] = '\0';
|
||||
|
||||
char *out = (char *)malloc(w + 1);
|
||||
if (!out) {
|
||||
free(no_tags);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t j = 0;
|
||||
int in_space = 1;
|
||||
for (size_t i = 0; i < w; ++i) {
|
||||
unsigned char ch = (unsigned char)no_tags[i];
|
||||
if (isspace(ch)) {
|
||||
if (!in_space) {
|
||||
out[j++] = ' ';
|
||||
in_space = 1;
|
||||
}
|
||||
} else {
|
||||
out[j++] = (char)ch;
|
||||
in_space = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (j > 0 && out[j - 1] == ' ') {
|
||||
j--;
|
||||
}
|
||||
out[j] = '\0';
|
||||
|
||||
free(no_tags);
|
||||
return out;
|
||||
}
|
||||
|
||||
void transcribe_set_filter_bracketed_tags(int enabled) {
|
||||
g_filter_bracketed_tags = enabled ? 1 : 0;
|
||||
}
|
||||
|
||||
#if WITH_WHISPER
|
||||
#include <whisper.h>
|
||||
|
||||
static struct whisper_context *g_ctx = NULL;
|
||||
static transcribe_params_t g_params;
|
||||
|
||||
static void apply_full_params_from_config(struct whisper_full_params *dst, const transcribe_params_t *src) {
|
||||
if (!dst || !src) return;
|
||||
|
||||
dst->n_threads = src->n_threads > 0 ? src->n_threads : 4;
|
||||
dst->n_max_text_ctx = src->n_max_text_ctx;
|
||||
dst->offset_ms = src->offset_ms;
|
||||
dst->duration_ms = src->duration_ms;
|
||||
|
||||
dst->translate = src->translate ? true : false;
|
||||
dst->detect_language = src->detect_language ? true : false;
|
||||
dst->no_context = src->no_context ? true : false;
|
||||
dst->no_timestamps = src->no_timestamps ? true : false;
|
||||
dst->single_segment = src->single_segment ? true : false;
|
||||
|
||||
dst->token_timestamps = src->token_timestamps ? true : false;
|
||||
dst->thold_pt = src->thold_pt;
|
||||
dst->thold_ptsum = src->thold_ptsum;
|
||||
dst->max_len = src->max_len;
|
||||
dst->split_on_word = src->split_on_word ? true : false;
|
||||
dst->max_tokens = src->max_tokens;
|
||||
|
||||
dst->audio_ctx = src->audio_ctx;
|
||||
dst->tdrz_enable = src->tdrz_enable ? true : false;
|
||||
|
||||
dst->suppress_blank = src->suppress_blank ? true : false;
|
||||
dst->suppress_nst = src->suppress_nst ? true : false;
|
||||
|
||||
dst->temperature = src->temperature;
|
||||
dst->max_initial_ts = src->max_initial_ts;
|
||||
dst->length_penalty = src->length_penalty;
|
||||
|
||||
dst->temperature_inc = src->temperature_inc;
|
||||
dst->entropy_thold = src->entropy_thold;
|
||||
dst->logprob_thold = src->logprob_thold;
|
||||
dst->no_speech_thold = src->no_speech_thold;
|
||||
|
||||
dst->greedy.best_of = src->greedy_best_of;
|
||||
dst->beam_search.beam_size = src->beam_size;
|
||||
dst->beam_search.patience = src->beam_patience;
|
||||
|
||||
dst->language = src->language[0] ? src->language : "en";
|
||||
}
|
||||
|
||||
int transcribe_init(const transcribe_params_t *params) {
|
||||
if (!params) return -1;
|
||||
g_params = *params;
|
||||
transcribe_set_filter_bracketed_tags(g_params.filter_bracketed_tags);
|
||||
|
||||
struct whisper_context_params cparams = whisper_context_default_params();
|
||||
cparams.use_gpu = g_params.use_gpu ? true : false;
|
||||
@@ -28,17 +138,22 @@ int transcribe_init(const transcribe_params_t *params) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void transcribe_update_params(const transcribe_params_t *params) {
|
||||
if (!params) return;
|
||||
g_params = *params;
|
||||
transcribe_set_filter_bracketed_tags(g_params.filter_bracketed_tags);
|
||||
}
|
||||
|
||||
char *transcribe_buffer(const float *samples, size_t count) {
|
||||
if (!g_ctx || !samples || count == 0) return NULL;
|
||||
|
||||
struct whisper_full_params params = whisper_full_default_params(WHISPER_SAMPLING_GREEDY);
|
||||
enum whisper_sampling_strategy strategy = g_params.sampling_strategy ? WHISPER_SAMPLING_BEAM_SEARCH : WHISPER_SAMPLING_GREEDY;
|
||||
struct whisper_full_params params = whisper_full_default_params(strategy);
|
||||
params.print_progress = false;
|
||||
params.print_special = false;
|
||||
params.print_realtime = false;
|
||||
params.print_timestamps = false;
|
||||
params.translate = false;
|
||||
params.n_threads = g_params.n_threads > 0 ? g_params.n_threads : 4;
|
||||
params.language = g_params.language[0] ? g_params.language : "en";
|
||||
apply_full_params_from_config(¶ms, &g_params);
|
||||
|
||||
if (whisper_full(g_ctx, params, samples, (int)count) != 0) {
|
||||
fprintf(stderr, "transcribe: whisper_full failed\n");
|
||||
@@ -63,7 +178,17 @@ char *transcribe_buffer(const float *samples, size_t count) {
|
||||
if (i + 1 < nseg) strcat(out, " ");
|
||||
}
|
||||
|
||||
return out;
|
||||
if (!g_filter_bracketed_tags) {
|
||||
return out;
|
||||
}
|
||||
|
||||
char *filtered = filter_bracketed_tags_copy(out);
|
||||
if (!filtered) {
|
||||
return out;
|
||||
}
|
||||
|
||||
free(out);
|
||||
return filtered;
|
||||
}
|
||||
|
||||
void transcribe_cleanup(void) {
|
||||
@@ -84,6 +209,11 @@ int transcribe_init(const transcribe_params_t *params) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void transcribe_update_params(const transcribe_params_t *params) {
|
||||
if (!params) return;
|
||||
g_params = *params;
|
||||
}
|
||||
|
||||
char *transcribe_buffer(const float *samples, size_t count) {
|
||||
(void)samples;
|
||||
(void)count;
|
||||
|
||||
@@ -8,9 +8,49 @@ typedef struct {
|
||||
char language[32];
|
||||
int use_gpu;
|
||||
int n_threads;
|
||||
int filter_bracketed_tags;
|
||||
|
||||
int sampling_strategy; // 0=greedy, 1=beam search
|
||||
int n_max_text_ctx;
|
||||
int offset_ms;
|
||||
int duration_ms;
|
||||
|
||||
int translate;
|
||||
int detect_language;
|
||||
int no_context;
|
||||
int no_timestamps;
|
||||
int single_segment;
|
||||
|
||||
int token_timestamps;
|
||||
float thold_pt;
|
||||
float thold_ptsum;
|
||||
int max_len;
|
||||
int split_on_word;
|
||||
int max_tokens;
|
||||
|
||||
int audio_ctx;
|
||||
int tdrz_enable;
|
||||
|
||||
int suppress_blank;
|
||||
int suppress_nst;
|
||||
|
||||
float temperature;
|
||||
float max_initial_ts;
|
||||
float length_penalty;
|
||||
|
||||
float temperature_inc;
|
||||
float entropy_thold;
|
||||
float logprob_thold;
|
||||
float no_speech_thold;
|
||||
|
||||
int greedy_best_of;
|
||||
int beam_size;
|
||||
float beam_patience;
|
||||
} transcribe_params_t;
|
||||
|
||||
int transcribe_init(const transcribe_params_t *params);
|
||||
void transcribe_update_params(const transcribe_params_t *params);
|
||||
void transcribe_set_filter_bracketed_tags(int enabled);
|
||||
char *transcribe_buffer(const float *samples, size_t count);
|
||||
void transcribe_cleanup(void);
|
||||
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
#ifndef VOICE_VERSION_H
|
||||
#define VOICE_VERSION_H
|
||||
|
||||
#define VOICE_LINUX_VERSION "v0.0.3"
|
||||
#define VOICE_LINUX_VERSION "v0.0.7"
|
||||
#define VOICE_LINUX_VERSION_MAJOR 0
|
||||
#define VOICE_LINUX_VERSION_MINOR 0
|
||||
#define VOICE_LINUX_VERSION_PATCH 3
|
||||
#define VOICE_LINUX_VERSION_PATCH 7
|
||||
|
||||
#endif
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user