Commands/voices multilanguage support + Ukranian vosk model added

This commit is contained in:
Priler 2026-01-13 02:21:59 +05:00
parent e2370dc046
commit 11c2500d9c
67 changed files with 214473 additions and 293 deletions

View file

@ -1,7 +1,8 @@
use std::sync::mpsc::Receiver;
use std::time::SystemTime;
use jarvis_core::{audio_buffer::AudioRingBuffer, audio_processing, commands, config, listener, recorder, stt, COMMANDS_LIST, intent, voices, ipc::{self, IpcEvent}};
use jarvis_core::{audio_buffer::AudioRingBuffer, audio_processing, commands, config, listener, recorder, stt, COMMANDS_LIST, intent, voices, ipc::{self, IpcEvent}, i18n};
use rand::seq::SliceRandom;
use crate::should_stop;
@ -183,7 +184,8 @@ fn recognize_command(
recognized_voice = recognized_voice.to_lowercase();
// check if wake word repeated (reactivate)
if recognized_voice.contains(config::VOSK_FETCH_PHRASE) {
let wake_phrase = config::get_wake_phrase(&i18n::get_language());
if recognized_voice.contains(wake_phrase) {
info!("Wake word detected during chaining, reactivating...");
voices::play_reply();
stt::reset_speech_recognizer();
@ -198,9 +200,13 @@ fn recognize_command(
}
// filter activation phrases
for tbr in config::ASSISTANT_PHRASES_TBR {
// for tbr in config::ASSISTANT_PHRASES_TBR {
// recognized_voice = recognized_voice.replace(tbr, "");
// }
for tbr in config::get_phrases_to_remove(&i18n::get_language()) {
recognized_voice = recognized_voice.replace(tbr, "");
}
recognized_voice = recognized_voice.trim().to_string();
if recognized_voice.is_empty() {
@ -257,9 +263,13 @@ fn process_text_command(text: &str, rt: &tokio::runtime::Runtime) {
ipc::send(IpcEvent::SpeechRecognized { text: text.to_string() });
let mut filtered = text.to_lowercase();
for tbr in config::ASSISTANT_PHRASES_TBR {
// for tbr in config::ASSISTANT_PHRASES_TBR {
// filtered = filtered.replace(tbr, "");
// }
for tbr in config::get_phrases_to_remove(&i18n::get_language()) {
filtered = filtered.replace(tbr, "");
}
let filtered = filtered.trim();
if filtered.is_empty() {
@ -299,7 +309,8 @@ fn execute_command(text: &str, rt: &tokio::runtime::Runtime) -> bool {
match commands::execute_command(&cmd_path, &cmd_config) {
Ok(chain) => {
info!("Command executed successfully");
voices::play_ok();
// voices::play_ok();
voices::play_random_from(cmd_config.get_sounds(&i18n::get_language()).as_slice());
ipc::send(IpcEvent::CommandExecuted {
id: cmd_config.id.clone(),
success: true,

View file

@ -82,7 +82,7 @@ fn main() -> Result<(), String> {
Vec::new()
}
};
info!("Commands initialized. Count: {}, List: {:?}", cmds.len(), commands::list(&cmds));
info!("Commands initialized. Count: {}, List: {:?}", cmds.len(), commands::list_paths(&cmds));
COMMANDS_LIST.set(cmds).unwrap();
// init audio

View file

@ -1,40 +1,24 @@
use crate::APP_DIR;
use rand::prelude::*;
use seqdiff::ratio;
// use serde_yaml;
use std::path::Path;
use std::{fs, fs::File};
use core::time::Duration;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::fs;
use std::time::Duration;
use std::process::{Child, Command};
// use tauri::Manager;
use seqdiff::ratio;
mod structs;
pub use structs::*;
use std::collections::HashMap;
use crate::{config, i18n, APP_DIR};
use crate::{config};
// @TODO. Allow commands both in yaml and json format.
pub fn parse_commands() -> Result<Vec<JCommandsList>, String> {
// collect commands
let mut commands: Vec<JCommandsList> = Vec::new();
let commands_path = APP_DIR.join(config::COMMANDS_PATH);
let cmd_dirs = fs::read_dir(&commands_path)
.map_err(|e| format!("Error reading commands directory {:?}: {}", commands_path, e))?;
for entry in cmd_dirs {
let entry = match entry {
Ok(e) => e,
Err(e) => {
warn!("Failed to read command directory entry: {}", e);
continue;
}
};
for entry in cmd_dirs.flatten() {
let cmd_path = entry.path();
let toml_file = cmd_path.join("command.toml");
@ -42,7 +26,6 @@ pub fn parse_commands() -> Result<Vec<JCommandsList>, String> {
continue;
}
// read and parse TOML
let content = match fs::read_to_string(&toml_file) {
Ok(c) => c,
Err(e) => {
@ -68,27 +51,30 @@ pub fn parse_commands() -> Result<Vec<JCommandsList>, String> {
if commands.is_empty() {
Err("No commands found".into())
} else {
info!("Loaded {} commands", commands.len());
info!("Loaded {} command pack(s)", commands.len());
Ok(commands)
}
}
// Commands hash generation for cache invalidation (deterministi c)
pub fn commands_hash(commands: &Vec<JCommandsList>) -> String {
pub fn commands_hash(commands: &[JCommandsList]) -> String {
use sha2::{Sha256, Digest};
let mut hasher = Sha256::new();
// collect all command ids and phrases, sorted
let mut all_ids: Vec<_> = commands.iter()
.flat_map(|ac| ac.commands.iter().map(|c| (&c.id, &c.phrases)))
.collect();
all_ids.sort_by_key(|(id, _)| *id);
let lang = i18n::get_language();
hasher.update(lang.as_bytes());
hasher.update(b"|");
for (id, phrases) in all_ids {
// collect all command ids and phrases for current language, sorted
let mut all_data: Vec<(&str, _)> = commands.iter()
.flat_map(|ac| ac.commands.iter().map(|c| (c.id.as_str(), c.get_phrases(&lang))))
.collect();
all_data.sort_by_key(|(id, _)| *id);
for (id, phrases) in all_data {
hasher.update(id.as_bytes());
for phrase in phrases {
for phrase in phrases.iter() {
hasher.update(phrase.as_bytes());
}
}
@ -99,12 +85,10 @@ pub fn commands_hash(commands: &Vec<JCommandsList>) -> String {
pub fn fetch_command<'a>(
phrase: &str,
commands: &'a Vec<JCommandsList>,
commands: &'a [JCommandsList],
) -> Option<(&'a PathBuf, &'a JCommand)> {
let mut result: Option<(&PathBuf, &JCommand)> = None;
let mut best_score = config::CMD_RATIO_THRESHOLD;
let lang = i18n::get_language();
// normalize input
let phrase = phrase.trim().to_lowercase();
if phrase.is_empty() {
return None;
@ -113,25 +97,30 @@ pub fn fetch_command<'a>(
let phrase_chars: Vec<char> = phrase.chars().collect();
let phrase_words: Vec<&str> = phrase.split_whitespace().collect();
let mut result: Option<(&PathBuf, &JCommand)> = None;
let mut best_score = config::CMD_RATIO_THRESHOLD;
for cmd_list in commands {
for cmd in &cmd_list.commands {
for cmd_phrase in &cmd.phrases {
let cmd_phrase = cmd_phrase.trim().to_lowercase();
let cmd_phrase_chars: Vec<char> = cmd_phrase.chars().collect();
let cmd_phrases = cmd.get_phrases(&lang);
for cmd_phrase in cmd_phrases.iter() {
let cmd_phrase_lower = cmd_phrase.trim().to_lowercase();
let cmd_phrase_chars: Vec<char> = cmd_phrase_lower.chars().collect();
// character-level similarity
let char_ratio = ratio(&phrase_chars, &cmd_phrase_chars);
// word-level similarity (handles word order)
let cmd_words: Vec<&str> = cmd_phrase.split_whitespace().collect();
// word-level similarity
let cmd_words: Vec<&str> = cmd_phrase_lower.split_whitespace().collect();
let word_score = word_overlap_score(&phrase_words, &cmd_words);
// combined score (weighted average)
// combined score
let score = (char_ratio * 0.6) + (word_score * 0.4);
// early exit on perfect match
if score >= 99.0 {
debug!("Perfect match: '{}' -> '{}'", phrase, cmd_phrase);
debug!("Perfect match: '{}' -> '{}'", phrase, cmd_phrase_lower);
return Some((&cmd_list.path, cmd));
}
@ -143,16 +132,13 @@ pub fn fetch_command<'a>(
}
}
if let Some((cmd_path, cmd)) = result {
info!(
"Fuzzy match: '{}' -> cmd '{}' (score: {:.1}%)",
phrase, cmd.id, best_score
);
Some((cmd_path, cmd))
if let Some((_, cmd)) = result {
info!("Fuzzy match: '{}' -> cmd '{}' (score: {:.1}%)", phrase, cmd.id, best_score);
} else {
debug!("No match for '{}' (best: {:.1}%)", phrase, best_score);
None
}
result
}
@ -163,36 +149,38 @@ fn word_overlap_score(input_words: &[&str], cmd_words: &[&str]) -> f64 {
let mut matched = 0.0;
for input_word in input_words {
// find best matching word in command
let best_word_match = cmd_words
.iter()
.map(|cmd_word| {
let iw: Vec<char> = input_word.chars().collect();
let cw: Vec<char> = cmd_word.chars().collect();
ratio(&iw, &cw)
})
.fold(0.0_f64, |a, b| a.max(b));
// pre-compute cmd word chars to avoid repeated allocations
let cmd_word_chars: Vec<Vec<char>> = cmd_words
.iter()
.map(|w| w.chars().collect())
.collect();
for input_word in input_words {
let input_chars: Vec<char> = input_word.chars().collect();
let best_word_match = cmd_word_chars
.iter()
.map(|cw| ratio(&input_chars, cw))
.fold(0.0_f64, f64::max);
// count as match if word similarity > 70%
if best_word_match > 70.0 {
matched += best_word_match / 100.0;
}
}
// normalize by max word count
let max_words = input_words.len().max(cmd_words.len()) as f64;
(matched / max_words) * 100.0
}
// @TODO. Rewrite executors by executor type struct. (with match arms)
pub fn execute_exe(exe: &str, args: &Vec<String>) -> std::io::Result<Child> {
pub fn execute_exe(exe: &str, args: &[String]) -> std::io::Result<Child> {
Command::new(exe).args(args).spawn()
}
pub fn execute_cli(cmd: &str, args: &Vec<String>) -> std::io::Result<Child> {
debug!("Spawning cmd as: cmd /C {} {:?}", cmd, args);
pub fn execute_cli(cmd: &str, args: &[String]) -> std::io::Result<Child> {
debug!("Spawning: cmd /C {} {:?}", cmd, args);
if cfg!(target_os = "windows") {
Command::new("cmd").arg("/C").arg(cmd).args(args).spawn()
@ -201,124 +189,42 @@ pub fn execute_cli(cmd: &str, args: &Vec<String>) -> std::io::Result<Child> {
}
}
pub fn execute_command(
cmd_path: &PathBuf,
cmd_config: &JCommand,
// app_handle: &tauri::AppHandle,
) -> Result<bool, String> {
// let sounds_directory = audio::get_sound_directory().unwrap();
pub fn execute_command(cmd_path: &Path, cmd_config: &JCommand) -> Result<bool, String> {
match cmd_config.action.as_str() {
"voice" => {
// VOICE command type
let random_cmd_sound = format!(
"{}.wav",
cmd_config
.sounds
.choose(&mut rand::thread_rng())
.unwrap()
);
// events::play(random_cmd_sound, app_handle);
// audio::play_sound(&sounds_directory.join(random_cmd_sound));
"voice" => Ok(true),
Ok(true)
}
"ahk" => {
// AutoHotkey command type
let exe_path_absolute = Path::new(&cmd_config.exe_path);
let exe_path_local = Path::new(&cmd_path).join(&cmd_config.exe_path);
let exe_path_local = cmd_path.join(&cmd_config.exe_path);
if let Ok(_) = execute_exe(
if exe_path_absolute.exists() {
exe_path_absolute.to_str().unwrap()
} else {
exe_path_local.to_str().unwrap()
},
&cmd_config.exe_args,
) {
let random_cmd_sound = format!(
"{}.wav",
cmd_config
.sounds
.choose(&mut rand::thread_rng())
.unwrap()
);
// events::play(random_cmd_sound, app_handle);
// audio::play_sound(&sounds_directory.join(random_cmd_sound));
Ok(true)
let exe_path = if exe_path_absolute.exists() {
exe_path_absolute
} else {
error!("AHK process spawn error (does exe path is valid?)");
Err("AHK process spawn error (does exe path is valid?)".into())
}
exe_path_local.as_path()
};
execute_exe(exe_path.to_str().unwrap(), &cmd_config.exe_args)
.map(|_| true)
.map_err(|e| format!("AHK process spawn error: {}", e))
}
"cli" => {
// CLI command type
let cli_cmd = &cmd_config.cli_cmd;
match execute_cli(cli_cmd, &cmd_config.cli_args) {
Ok(_) => {
let random_cmd_sound = format!(
"{}.wav",
cmd_config
.sounds
.choose(&mut rand::thread_rng())
.unwrap()
);
// events::play(random_cmd_sound, app_handle);
// audio::play_sound(&sounds_directory.join(random_cmd_sound));
Ok(true)
}
Err(msg) => {
error!("CLI command error ({})", msg);
Err(format!("Shell command error ({})", msg).into())
}
}
execute_cli(&cmd_config.cli_cmd, &cmd_config.cli_args)
.map(|_| true)
.map_err(|e| format!("CLI command error: {}", e))
}
"terminate" => {
// TERMINATE command type
let random_cmd_sound = format!(
"{}.wav",
cmd_config
.sounds
.choose(&mut rand::thread_rng())
.unwrap()
);
// events::play(random_cmd_sound, app_handle);
// audio::play_sound(&sounds_directory.join(random_cmd_sound));
"terminate" => {
std::thread::sleep(Duration::from_secs(2));
std::process::exit(0);
}
"stop_chaining" => {
// STOP_CHAINING command type
let random_cmd_sound = format!(
"{}.wav",
cmd_config
.sounds
.choose(&mut rand::thread_rng())
.unwrap()
);
// events::play(random_cmd_sound, app_handle);
// audio::play_sound(&sounds_directory.join(random_cmd_sound));
Ok(false)
}
_ => {
error!("Command type unknown");
Err("Command type unknown".into())
}
"stop_chaining" => Ok(false),
_ => Err(format!("Unknown command type: {}", cmd_config.action)),
}
}
pub fn list(from: &Vec<JCommandsList>) -> Vec<String> {
let mut out: Vec<String> = vec![];
for x in from.iter() {
out.push(String::from(x.path.to_str().unwrap()));
// out.append()
}
out
pub fn list_paths(commands: &[JCommandsList]) -> Vec<&Path> {
commands.iter().map(|x| x.path.as_path()).collect()
}

View file

@ -1,5 +1,6 @@
use std::path::PathBuf;
use std::{collections::HashMap, path::PathBuf, sync::Arc};
use serde::{Serialize, Deserialize};
use parking_lot::RwLock;
#[derive(Serialize, Deserialize, Debug)]
pub struct JCommandsList {
@ -11,7 +12,7 @@ pub struct JCommandsList {
#[derive(Serialize, Deserialize, Debug, Clone)]
#[derive(Serialize, Deserialize, Debug)]
pub struct JCommand {
pub id: String,
pub action: String,
@ -31,8 +32,98 @@ pub struct JCommand {
#[serde(default)]
pub cli_args: Vec<String>,
#[serde(default)]
pub sounds: Vec<String>,
// #[serde(default)]
// pub sounds: Vec<String>,
pub phrases: Vec<String>,
// Multi-language sounds
#[serde(default)]
pub sounds: HashMap<String, Vec<String>>,
// Multi-language phrases
#[serde(default)]
pub phrases: HashMap<String, Vec<String>>,
// CACHE
#[serde(skip, default)]
sounds_cache: RwLock<HashMap<String, Arc<Vec<String>>>>,
#[serde(skip, default)]
phrases_cache: RwLock<HashMap<String, Arc<Vec<String>>>>,
}
// custom Clone
impl Clone for JCommand {
fn clone(&self) -> Self {
Self {
id: self.id.clone(),
action: self.action.clone(),
description: self.description.clone(),
exe_path: self.exe_path.clone(),
exe_args: self.exe_args.clone(),
cli_cmd: self.cli_cmd.clone(),
cli_args: self.cli_args.clone(),
sounds: self.sounds.clone(),
phrases: self.phrases.clone(),
// empty caches for cloned instance
sounds_cache: RwLock::new(HashMap::new()),
phrases_cache: RwLock::new(HashMap::new()),
}
}
}
impl JCommand {
// get phrases for current language
pub fn get_phrases(&self, lang: &str) -> Arc<Vec<String>> {
if let Some(cached) = self.phrases_cache.read().get(lang) {
return Arc::clone(cached);
}
let result = Arc::new(self.resolve_localized(&self.phrases, lang));
self.phrases_cache.write().insert(lang.to_string(), Arc::clone(&result));
result
}
// get all phrases (for backwards compat)
pub fn get_all_phrases(&self) -> Vec<String> {
self.phrases.values().flatten().cloned().collect()
}
// get sounds for current language
pub fn get_sounds(&self, lang: &str) -> Arc<Vec<String>> {
if let Some(cached) = self.sounds_cache.read().get(lang) {
return Arc::clone(cached);
}
let result = Arc::new(self.resolve_localized(&self.sounds, lang));
self.sounds_cache.write().insert(lang.to_string(), Arc::clone(&result));
result
}
// get all sounds (for backwards compat)
pub fn get_all_sounds(&self) -> Vec<String> {
self.sounds.values().flatten().cloned().collect()
}
// shared fallback
fn resolve_localized(&self, map: &HashMap<String, Vec<String>>, lang: &str) -> Vec<String> {
// exact match
if let Some(values) = map.get(lang) {
return values.clone();
}
// fallback to "en"
if lang != "en" {
if let Some(values) = map.get("en") {
return values.clone();
}
}
// fallback to first available
map.values().next().cloned().unwrap_or_default()
}
}

View file

@ -180,23 +180,72 @@ pub const NNNOISELESS_FRAME_SIZE: usize = 480;
pub const CMD_RATIO_THRESHOLD: f64 = 65f64;
pub const CMS_WAIT_DELAY: std::time::Duration = std::time::Duration::from_secs(15);
pub const ASSISTANT_GREET_PHRASES: [&str; 3] = ["greet1", "greet2", "greet3"];
pub const ASSISTANT_PHRASES_TBR: [&str; 17] = [
"джарвис",
"сэр",
"слушаю сэр",
"всегда к услугам",
"произнеси",
"ответь",
"покажи",
"скажи",
"давай",
"да сэр",
"к вашим услугам сэр",
"всегда к вашим услугам сэр",
"запрос выполнен сэр",
"выполнен сэр",
"есть",
"загружаю сэр",
"очень тонкое замечание сэр",
];
// pub const ASSISTANT_GREET_PHRASES: [&str; 3] = ["greet1", "greet2", "greet3"];
// pub const ASSISTANT_PHRASES_TBR: [&str; 17] = [
// "джарвис",
// "сэр",
// "слушаю сэр",
// "всегда к услугам",
// "произнеси",
// "ответь",
// "покажи",
// "скажи",
// "давай",
// "да сэр",
// "к вашим услугам сэр",
// "всегда к вашим услугам сэр",
// "запрос выполнен сэр",
// "выполнен сэр",
// "есть",
// "загружаю сэр",
// "очень тонкое замечание сэр",
// ];
pub fn get_wake_phrase(lang: &str) -> &'static str {
match lang {
"ru" => "джарвис",
"ua" => "джарвіс",
"en" => "jarvis",
_ => "jarvis",
}
}
pub fn get_phrases_to_remove(lang: &str) -> &'static [&'static str] {
match lang {
"ru" => &[
"джарвис", "сэр", "слушаю сэр", "всегда к услугам",
"произнеси", "ответь", "покажи", "скажи", "давай",
"да сэр", "к вашим услугам сэр", "загружаю сэр",
],
"ua" => &[
"джарвіс", "сер", "слухаю сер", "завжди до послуг",
"скажи", "покажи", "відповідай", "давай",
"так сер", "до ваших послуг сер",
],
"en" => &[
"jarvis", "sir", "yes sir", "at your service",
"please", "say", "show", "tell", "hey",
],
_ => &["jarvis"],
}
}
pub fn get_wake_grammar(lang: &str) -> &'static [&'static str] {
match lang {
"ru" => &[
"джарвис", "[unk]", "джон", "джони", "джей",
"джонстон", "привет", "давай",
],
"ua" => &[
"джарвіс", "[unk]", "джон", "джоні", "джей",
"привіт", "давай",
],
"en" => &[
"jarvis", "[unk]", "john", "johnny", "jay",
"hello", "hey", "hi",
],
_ => &["jarvis", "[unk]"],
}
}

View file

@ -92,7 +92,9 @@ settings-picovoice-key = Picovoice Key
# settings - vosk
settings-auto-detect = Auto-detect
settings-vosk-model = Speech recognition model (Vosk)
settings-vosk-model-desc = Select Vosk model for speech recognition.
settings-vosk-model-desc =
Select Vosk model for speech recognition.
You can download models here: https://alphacephei.com/vosk/models
settings-models-not-found = Models not found
settings-models-hint = Place Vosk models in resources/vosk folder

View file

@ -92,7 +92,9 @@ settings-picovoice-key = Ключ Picovoice
# settings - vosk
settings-auto-detect = Авто-определение
settings-vosk-model = Модель распознавания речи (Vosk)
settings-vosk-model-desc = Выберите модель Vosk для распознавания речи.
settings-vosk-model-desc =
Выберите модель Vosk для распознавания речи.
Вы можете скачать модели здесь: https://alphacephei.com/vosk/models
settings-models-not-found = Модели не найдены
settings-models-hint = Поместите модели Vosk в папку resources/vosk

View file

@ -92,7 +92,9 @@ settings-picovoice-key = Ключ Picovoice
# settings - vosk
settings-auto-detect = Авто-визначення
settings-vosk-model = Модель розпізнавання мовлення (Vosk)
settings-vosk-model-desc = Виберіть модель Vosk для розпізнавання мовлення.
settings-vosk-model-desc =
Виберіть модель Vosk для розпізнавання мовлення.
Ви можете завантажити моделі тут: https://alphacephei.com/vosk/models
settings-models-not-found = Моделі не знайдено
settings-models-hint = Помістіть моделі Vosk в папку resources/vosk

View file

@ -8,7 +8,7 @@ use std::path::PathBuf;
use std::fs;
use crate::commands::{self, JCommand, JCommandsList};
use crate::{APP_CONFIG_DIR};
use crate::{APP_CONFIG_DIR, i18n};
static CLASSIFIER: OnceCell<IntentClassifier> = OnceCell::const_new();
// static COMMANDS_MAP: OnceCell<Vec<JCommandsList>> = OnceCell::const_new();
@ -16,7 +16,7 @@ static CLASSIFIER: OnceCell<IntentClassifier> = OnceCell::const_new();
const TRAINING_CACHE_FILE: &str = "intent_training.json";
const COMMANDS_HASH_FILE: &str = "commands_hash.txt";
pub async fn init(commands: &Vec<JCommandsList>) -> Result<(), String> {
pub async fn init(commands: &[JCommandsList]) -> Result<(), String> {
// parse commands first
// let commands = commands::parse_commands()?;
let current_hash = commands::commands_hash(&commands); // regen hash for current commands set
@ -68,7 +68,7 @@ pub async fn classify(text: &str) -> Result<IntentPrediction, IntentError> {
}
// get command by intent ID
pub fn get_command(commands: &'static Vec<JCommandsList>, intent_id: &str) -> Option<(&'static PathBuf, &'static JCommand)> {
pub fn get_command(commands: &'static [JCommandsList], intent_id: &str) -> Option<(&'static PathBuf, &'static JCommand)> {
// let commands = COMMANDS_MAP.get()?;
for assistant_cmd in commands {
@ -85,11 +85,19 @@ pub fn get_command(commands: &'static Vec<JCommandsList>, intent_id: &str) -> Op
// based on: https://github.com/ciresnave/intent-classifier/blob/main/examples/basic_usage.rs
async fn train_classifier(
classifier: &IntentClassifier,
commands: &Vec<JCommandsList>
commands: &[JCommandsList]
) -> Result<(), String> {
let lang = i18n::get_language();
info!("Training intent classifier for language: {}", lang);
let mut total_examples = 0;
for assistant_cmd in commands {
for cmd in &assistant_cmd.commands {
for phrase in &cmd.phrases {
// use language-specific phrases
let phrases = cmd.get_phrases(&lang);
for phrase in phrases.iter() {
let example = TrainingExample {
text: phrase.clone(),
intent: IntentId::from(cmd.id.as_str()),
@ -99,9 +107,12 @@ async fn train_classifier(
classifier.add_training_example(example).await
.map_err(|e| format!("Failed to add training example: {}", e))?;
total_examples += 1;
}
}
}
info!("Added {} training examples for language '{}'", total_examples, lang);
Ok(())
}

View file

@ -1,4 +1,4 @@
use crate::{config, stt};
use crate::{config, stt, i18n};
pub fn init() -> Result<(), ()> {
Ok(()) // nothing to init for Vosk
@ -15,8 +15,12 @@ pub fn data_callback(frame_buffer: &[i16]) -> Option<i32> {
info!("Wake word candidate: '{}'", recognized);
// language-specific wake phrase
let lang = i18n::get_language();
let wake_phrase = config::get_wake_phrase(&lang);
// verify with seqdiff ratio
let wake_chars: Vec<char> = config::VOSK_FETCH_PHRASE.chars().collect();
let wake_chars: Vec<char> = wake_phrase.chars().collect();
let recognized_chars: Vec<char> = recognized.chars().collect();
let similarity = seqdiff::ratio(&wake_chars, &recognized_chars);

View file

@ -4,8 +4,7 @@ use vosk::{DecodingState, Model, Recognizer};
use std::sync::Mutex;
// use crate::config::VOSK_MODEL_PATH;
use crate::config;
use crate::stt::vosk_models;
use crate::{stt::vosk_models, i18n, config};
use crate::DB;
static MODEL: OnceCell<Model> = OnceCell::new();
@ -23,19 +22,14 @@ pub fn init_vosk() -> Result<(), String> {
let model = Model::new(model_path.to_str().unwrap())
.ok_or_else(|| format!("Failed to load Vosk model from: {}", model_path.display()))?;
// language-specific wake grammar
let lang = i18n::get_language();
let wake_grammar = config::get_wake_grammar(&lang);
info!("Wake grammar for '{}': {:?}", lang, wake_grammar);
//let mut recognizer = Recognizer::new(&model, 16000.0)
// .ok_or("Failed to create Vosk recognizer")?;
let wake_phrases: &[&str] = &[
config::VOSK_FETCH_PHRASE,
"[unk]",
"джон",
"джони",
"джей",
"джонстон",
"привет",
"давай",
];
let mut wake_recognizer = Recognizer::new_with_grammar(&model, 16000.0, wake_phrases)
let mut wake_recognizer = Recognizer::new_with_grammar(&model, 16000.0, wake_grammar)
.ok_or("Failed to create wake word recognizer")?;
wake_recognizer.set_max_alternatives(1); // required for confidence check later on

View file

@ -78,15 +78,15 @@ fn load_voice_config(toml_path: &Path, voice_path: &Path) -> Result<structs::Voi
pub fn list_voices() -> Vec<structs::VoiceConfig> {
VOICES.get().cloned().unwrap_or_default()
pub fn list_voices() -> &'static [structs::VoiceConfig] {
VOICES.get().map(|v| v.as_slice()).unwrap_or(&[])
}
pub fn get_voice(voice_id: &str) -> Option<structs::VoiceConfig> {
VOICES.get()?.iter().find(|v| v.voice.id == voice_id).cloned()
pub fn get_voice(voice_id: &str) -> Option<&'static structs::VoiceConfig> {
VOICES.get()?.iter().find(|v| v.voice.id == voice_id)
}
pub fn get_current_voice() -> Option<structs::VoiceConfig> {
pub fn get_current_voice() -> Option<&'static structs::VoiceConfig> {
let current_id = CURRENT_VOICE_ID.get()?.read().clone();
get_voice(&current_id)
}
@ -106,11 +106,11 @@ fn get_current_language() -> String {
fn find_sound_file(voice_path: &Path, lang: &str, sound_name: &str) -> Option<PathBuf> {
let extensions = ["mp3", "wav", "ogg"];
const EXTENSIONS: &[&str] = &["mp3", "wav", "ogg"];
let lang_path = voice_path.join(lang);
// try language subfolder first
for ext in &extensions {
// try language subfolder first (/en, /ua, /ru, etc)
for ext in EXTENSIONS {
let file_path = lang_path.join(format!("{}.{}", sound_name, ext));
if file_path.exists() {
return Some(file_path);
@ -118,7 +118,7 @@ fn find_sound_file(voice_path: &Path, lang: &str, sound_name: &str) -> Option<Pa
}
// fallback to root voice folder
for ext in &extensions {
for ext in EXTENSIONS {
let file_path = voice_path.join(format!("{}.{}", sound_name, ext));
if file_path.exists() {
return Some(file_path);
@ -128,29 +128,20 @@ fn find_sound_file(voice_path: &Path, lang: &str, sound_name: &str) -> Option<Pa
None
}
fn play_random_from(sounds: &[String]) {
fn play_random_from_list(voice_path: &Path, lang: &str, sounds: &[String]) {
if sounds.is_empty() {
return;
}
let voice = match get_current_voice() {
Some(v) => v,
None => {
warn!("No current voice set");
return;
}
};
let lang = get_current_language();
let sound_name = sounds.choose(&mut rand::thread_rng()).unwrap();
match find_sound_file(&voice.path, &lang, sound_name) {
match find_sound_file(voice_path, lang, sound_name) {
Some(path) => {
debug!("Playing: {:?}", path);
audio::play_sound(&path);
}
None => {
warn!("Sound not found: {} (lang: {}, voice: {})", sound_name, lang, voice.voice.id);
warn!("Sound not found: {} (lang: {})", sound_name, lang);
}
}
}
@ -164,32 +155,53 @@ pub fn play(reaction: structs::Reaction) {
}
};
let lang = get_current_language();
let reactions = match voice.reactions.get(&lang) {
Some(r) => r,
None => {
warn!("No reactions for language: {}", lang);
return;
}
};
let sounds = match reaction {
structs::Reaction::Greet => {
// try time specific first
// try time-specific first
let time_specific = match time::TimeOfDay::now() {
time::TimeOfDay::Morning => &voice.reactions.greet_morning,
time::TimeOfDay::Day => &voice.reactions.greet_day,
time::TimeOfDay::Evening => &voice.reactions.greet_evening,
time::TimeOfDay::Night => &voice.reactions.greet_night,
time::TimeOfDay::Morning => &reactions.greet_morning,
time::TimeOfDay::Day => &reactions.greet_day,
time::TimeOfDay::Evening => &reactions.greet_evening,
time::TimeOfDay::Night => &reactions.greet_night,
};
if time_specific.is_empty() {
// fallback to simple run voice (not time specific)
&voice.reactions.greet
&reactions.greet
} else {
time_specific
}
}
structs::Reaction::Reply => &voice.reactions.reply,
structs::Reaction::Ok => &voice.reactions.ok,
structs::Reaction::NotFound => &voice.reactions.not_found,
structs::Reaction::Thanks => &voice.reactions.thanks,
structs::Reaction::Error => &voice.reactions.error,
structs::Reaction::Goodbye => &voice.reactions.goodbye,
structs::Reaction::Reply => &reactions.reply,
structs::Reaction::Ok => &reactions.ok,
structs::Reaction::NotFound => &reactions.not_found,
structs::Reaction::Thanks => &reactions.thanks,
structs::Reaction::Error => &reactions.error,
structs::Reaction::Goodbye => &reactions.goodbye,
};
play_random_from(sounds);
play_random_from_list(&voice.path, &lang, sounds);
}
pub fn play_random_from(sounds: &[String]) {
let voice = match get_current_voice() {
Some(v) => v,
None => {
warn!("No current voice set");
return;
}
};
play_random_from_list(&voice.path, &get_current_language(), sounds);
}
// Play a preview sound for a specific voice
@ -204,10 +216,18 @@ pub fn play_preview(voice_id: &str) {
let lang = get_current_language();
// pick from reply or ok sounds for preview
let sounds: Vec<&String> = voice.reactions.reply.iter()
.chain(voice.reactions.ok.iter())
.chain(voice.reactions.greet.iter())
let reactions = match voice.reactions.get(&lang) {
Some(r) => r,
None => {
warn!("No reactions for language {} in voice {}", lang, voice_id);
return;
}
};
// pick from reply, ok, or greet sounds for preview
let sounds: Vec<&String> = reactions.reply.iter()
.chain(reactions.ok.iter())
.chain(reactions.greet.iter())
.collect();
if sounds.is_empty() {

View file

@ -1,20 +1,26 @@
use std::path::PathBuf;
use std::{collections::HashMap, path::PathBuf};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VoiceConfig {
#[serde(skip)]
pub path: PathBuf,
pub voice: VoiceMeta,
pub reactions: VoiceReactions,
// Multi-language reactions
pub reactions: HashMap<String, VoiceReactions>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VoiceMeta {
pub id: String,
pub name: String,
#[serde(default)]
pub author: String,
pub languages: Vec<String>,
}

View file

@ -2,12 +2,12 @@ use jarvis_core::voices::{self, structs::VoiceConfig};
#[tauri::command]
pub fn list_voices() -> Vec<VoiceConfig> {
voices::list_voices()
voices::list_voices().to_vec()
}
#[tauri::command]
pub fn get_voice(voice_id: String) -> Option<VoiceConfig> {
voices::get_voice(&voice_id)
voices::get_voice(&voice_id).cloned()
}
#[tauri::command]

View file

@ -32,6 +32,10 @@ body {
border-radius: 3px;
}
.svelteui-InputWrapper-description {
white-space: pre-line;
}
// ### FONTS
$prim-font: "Roboto", sans-serif;
$sec-font: "Roboto Condensed", sans-serif;

View file

@ -158,9 +158,18 @@
]
// load vosk models
const languageNames: Record<string, string> = {
us: 'English',
ru: 'Русский',
uk: 'Українська',
de: 'German',
fr: 'French',
es: 'Spanish',
// ..
};
const voskModels = await invoke<{ name: string; language: string; size: string }[]>("list_vosk_models")
availableVoskModels = voskModels.map(m => ({
label: `${m.name} (${m.language}, ${m.size})`,
label: `${m.name} (${languageNames[m.language] ?? m.language}, ${m.size})`,
value: m.name
}))

View file

@ -2,51 +2,90 @@
id = "browser_open"
action = "ahk"
exe_path = "ahk/Run browser.exe"
sounds = ["ok1", "ok2", "ok3"]
phrases = [
sounds.ru = ["ok1", "ok2", "ok3", "ok4"]
sounds.en = ["ok1", "ok2", "ok3"]
sounds.ua = ["ok1", "ok2", "ok3", "ok4", "ok5", "ok6"]
phrases.ru = [
"открой браузер",
"запусти браузер",
"открой хром",
"запусти хром",
"открой гугл хром",
"запусти гугл хром",
"включи браузер",
"браузер открой",
"нужен браузер",
"мне нужен браузер",
]
phrases.en = [
"open browser",
"launch browser",
"open chrome",
"launch chrome",
"start browser",
"i need browser",
]
phrases.ua = [
"відкрий браузер",
"запусти браузер",
"відкрий хром",
"запусти хром",
"увімкни браузер",
"потрібен браузер",
]
[[commands]]
id = "browser_close"
action = "ahk"
exe_path = "ahk/Close browser.exe"
sounds = ["ok1", "ok2", "ok3", "ok4"]
phrases = [
sounds.ru = ["ok1", "ok2", "ok3", "ok4"]
sounds.en = ["ok1", "ok2", "ok3"]
sounds.ua = ["ok1", "ok2", "ok3", "ok4", "ok5", "ok6"]
phrases.ru = [
"закрой браузер",
"закрой все браузеры",
"выключи браузер",
"убери браузер",
"закрой хром",
"выключи хром",
"закрой гугл хром",
"браузер закрой",
"сверни браузер",
]
phrases.en = [
"close browser",
"close all browsers",
"shut down browser",
"exit browser",
]
phrases.ua = [
"закрий браузер",
"закрий всі браузери",
"вимкни браузер",
]
[[commands]]
id = "open_google"
action = "ahk"
exe_path = "ahk/Run website.exe"
exe_args = ["http://google.com"]
sounds = ["ok1", "ok2", "ok3", "ok4"]
phrases = [
sounds.ru = ["ok1", "ok2", "ok3", "ok4"]
sounds.en = ["ok1", "ok2", "ok3"]
sounds.ua = ["ok1", "ok2", "ok3", "ok4", "ok5", "ok6"]
phrases.ru = [
"открой гугл",
"открой гугл поиск",
"запусти гугл",
"перейди в гугл",
"перейди на гугл",
"открой поисковик",
"зайди в гугл",
"гугл поиск",
"поиск гугл",
]
phrases.en = [
"open google",
"google search",
"launch google",
"go to google",
]
phrases.ua = [
"відкрий гугл",
"відкрий гугл пошук",
"запусти гугл",
"зайди в гугл",
]

View file

@ -0,0 +1,19 @@
; Rerun as admin, if required
If Not A_IsAdmin
{
Run *RunAs "%A_ScriptFullPath%"
ExitApp
}
; set partial title matching mode
SetTitleMatchMode, 2
; list of all browsers to close
GroupAdd, browsers, ahk_class MozillaWindowClass
GroupAdd, browsers, ahk_class IEFrame
GroupAdd, browsers, ahk_exe msedge.exe
GroupAdd, browsers, ahk_exe chrome.exe
GroupAdd, browsers, ahk_exe firefox.exe
; kill them all
Winclose, ahk_group browsers

View file

@ -0,0 +1,11 @@
#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
; #Warn ; Enable warnings to assist with detecting common errors.
SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory.
RegRead, BrowserKeyName, HKEY_CURRENT_USER, Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.html\UserChoice, Progid
RegRead, BrowserFullCommand, HKEY_CLASSES_ROOT, %BrowserKeyName%\shell\open\command
StringGetPos, pos, BrowserFullCommand, ",,1
pos := --pos
StringMid, BrowserPathandEXE, BrowserFullCommand, 2, %pos%
Run, % BrowserPathandEXE

View file

@ -0,0 +1,11 @@
#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
; #Warn ; Enable warnings to assist with detecting common errors.
SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory.
#Include _include.ahk
args = %1%
path := DefaultBrowser()
Run, %path% %args%

View file

@ -0,0 +1,19 @@
DefaultBrowser() {
; Find the Registry key name for the default browser
RegRead, BrowserKeyName, HKEY_CURRENT_USER, Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.html\UserChoice, Progid
; Find the executable command associated with the above Registry key
RegRead, BrowserFullCommand, HKEY_CLASSES_ROOT, %BrowserKeyName%\shell\open\command
; The above RegRead will return the path and executable name of the brower contained within quotes and optional parameters
; We only want the text contained inside the first set of quotes which is the path and executable
; Find the ending quote position (we know the beginning quote is in position 0 so start searching at position 1)
StringGetPos, pos, BrowserFullCommand, ",,1
; Decrement the found position by one to work correctly with the StringMid function
pos := --pos
; Extract and return the path and executable of the browser
StringMid, BrowserPathandEXE, BrowserFullCommand, 2, %pos%
Return BrowserPathandEXE
}

View file

@ -0,0 +1,18 @@
[[commands]]
id = "weather"
action = "cli"
cli_cmd = "weather.py"
[commands.phrases]
ru = [
"какая погода",
"погода"
]
en = [
"what's the weather",
"weather"
]
[commands.sounds]
ru = ["weather_ru_1", "weather_ru_2"]
en = ["weather_en_1", "weather_en_2"]

View file

@ -4,7 +4,7 @@ name = "Jarvis Howdy"
author = "Abraham (Priler)"
languages = ["ru"]
[reactions]
[reactions.ru]
# app startup
greet = ["run", "ready"]

View file

@ -4,7 +4,7 @@ name = "Jarvis OG"
author = "Abraham (Priler)"
languages = ["ru"]
[reactions]
[reactions.ru]
# app startup
greet = ["run"]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -2,9 +2,9 @@
id = "jarvis-remaster"
name = "Jarvis New"
author = "Abraham (Priler)"
languages = ["ru", "en"]
languages = ["ru", "ua", "en"]
[reactions]
[reactions.ru]
# app startup
greet = ["greet1"]
greet_morning = ["greet_morning"]
@ -29,3 +29,59 @@ error = []
# goodbye
goodbye = []
[reactions.ua]
# app startup
greet = []
greet_morning = ["greet_morning1", "greet_morning2", "greet_morning3"]
greet_day = ["greet_day1", "greet_day2"]
greet_evening = ["greet_evening1", "greet_evening2"]
greet_night = ["greet_night1", "greet_night2"]
# wake word detected
reply = ["reply1", "reply2", "reply3", "reply4", "reply5"]
# command executed
ok = ["ok1", "ok2", "ok3", "ok4", "ok5", "ok6"]
# command not found
not_found = []
# thanks
thanks = []
# error
error = []
# goodbye
goodbye = []
[reactions.en]
# app startup
greet = []
greet_morning = ["greet_morning"]
greet_day = ["greet_day"]
greet_evening = ["greet_evening"]
greet_night = ["greet_night"]
# wake word detected
reply = ["reply1", "reply2", "reply3", "reply4", "reply5"]
# command executed
ok = ["ok1", "ok2", "ok3", "ok4", "ok5", "ok6"]
# command not found
not_found = []
# thanks
thanks = []
# error
error = []
# goodbye
goodbye = []

View file

@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. this License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View file

@ -0,0 +1,11 @@
# config for high-resolution MFCC features, intended for neural network training
# Note: we keep all cepstra, so it has the same info as filterbank features,
# but MFCC is more easily compressible (because less correlated) which is why
# we prefer this method.
--use-energy=false # use average of log energy, not energy.
--sample-frequency=16000
--num-mel-bins=40 # similar to Google's setup.
--num-ceps=40 # there is no dimensionality reduction.
--low-freq=20 # low cutoff frequency for mel bins... this is high-bandwidth data, so
# there might be some information at the low end.
--high-freq=-400 # high cutoff frequently, relative to Nyquist of 8000 (=7600)

View file

@ -0,0 +1,10 @@
--min-active=200
--max-active=3000
--beam=13.0
--lattice-beam=2.0
--acoustic-scale=1.0
--frame-subsampling-factor=3
--endpoint.silence-phones=1:2:3:4:5:6:7:8:9:10
--endpoint.rule2.min-trailing-silence=0.5
--endpoint.rule3.min-trailing-silence=1.0
--endpoint.rule4.min-trailing-silence=2.0

View file

@ -0,0 +1,6 @@
28121
28122
28123
28124
28125
28126

View file

@ -0,0 +1,386 @@
1 nonword
2 begin
3 end
4 internal
5 singleton
6 nonword
7 begin
8 end
9 internal
10 singleton
11 begin
12 end
13 internal
14 singleton
15 begin
16 end
17 internal
18 singleton
19 begin
20 end
21 internal
22 singleton
23 begin
24 end
25 internal
26 singleton
27 begin
28 end
29 internal
30 singleton
31 begin
32 end
33 internal
34 singleton
35 begin
36 end
37 internal
38 singleton
39 begin
40 end
41 internal
42 singleton
43 begin
44 end
45 internal
46 singleton
47 begin
48 end
49 internal
50 singleton
51 begin
52 end
53 internal
54 singleton
55 begin
56 end
57 internal
58 singleton
59 begin
60 end
61 internal
62 singleton
63 begin
64 end
65 internal
66 singleton
67 begin
68 end
69 internal
70 singleton
71 begin
72 end
73 internal
74 singleton
75 begin
76 end
77 internal
78 singleton
79 begin
80 end
81 internal
82 singleton
83 begin
84 end
85 internal
86 singleton
87 begin
88 end
89 internal
90 singleton
91 begin
92 end
93 internal
94 singleton
95 begin
96 end
97 internal
98 singleton
99 begin
100 end
101 internal
102 singleton
103 begin
104 end
105 internal
106 singleton
107 begin
108 end
109 internal
110 singleton
111 begin
112 end
113 internal
114 singleton
115 begin
116 end
117 internal
118 singleton
119 begin
120 end
121 internal
122 singleton
123 begin
124 end
125 internal
126 singleton
127 begin
128 end
129 internal
130 singleton
131 begin
132 end
133 internal
134 singleton
135 begin
136 end
137 internal
138 singleton
139 begin
140 end
141 internal
142 singleton
143 begin
144 end
145 internal
146 singleton
147 begin
148 end
149 internal
150 singleton
151 begin
152 end
153 internal
154 singleton
155 begin
156 end
157 internal
158 singleton
159 begin
160 end
161 internal
162 singleton
163 begin
164 end
165 internal
166 singleton
167 begin
168 end
169 internal
170 singleton
171 begin
172 end
173 internal
174 singleton
175 begin
176 end
177 internal
178 singleton
179 begin
180 end
181 internal
182 singleton
183 begin
184 end
185 internal
186 singleton
187 begin
188 end
189 internal
190 singleton
191 begin
192 end
193 internal
194 singleton
195 begin
196 end
197 internal
198 singleton
199 begin
200 end
201 internal
202 singleton
203 begin
204 end
205 internal
206 singleton
207 begin
208 end
209 internal
210 singleton
211 begin
212 end
213 internal
214 singleton
215 begin
216 end
217 internal
218 singleton
219 begin
220 end
221 internal
222 singleton
223 begin
224 end
225 internal
226 singleton
227 begin
228 end
229 internal
230 singleton
231 begin
232 end
233 internal
234 singleton
235 begin
236 end
237 internal
238 singleton
239 begin
240 end
241 internal
242 singleton
243 begin
244 end
245 internal
246 singleton
247 begin
248 end
249 internal
250 singleton
251 begin
252 end
253 internal
254 singleton
255 begin
256 end
257 internal
258 singleton
259 begin
260 end
261 internal
262 singleton
263 begin
264 end
265 internal
266 singleton
267 begin
268 end
269 internal
270 singleton
271 begin
272 end
273 internal
274 singleton
275 begin
276 end
277 internal
278 singleton
279 begin
280 end
281 internal
282 singleton
283 begin
284 end
285 internal
286 singleton
287 begin
288 end
289 internal
290 singleton
291 begin
292 end
293 internal
294 singleton
295 begin
296 end
297 internal
298 singleton
299 begin
300 end
301 internal
302 singleton
303 begin
304 end
305 internal
306 singleton
307 begin
308 end
309 internal
310 singleton
311 begin
312 end
313 internal
314 singleton
315 begin
316 end
317 internal
318 singleton
319 begin
320 end
321 internal
322 singleton
323 begin
324 end
325 internal
326 singleton
327 begin
328 end
329 internal
330 singleton
331 begin
332 end
333 internal
334 singleton
335 begin
336 end
337 internal
338 singleton
339 begin
340 end
341 internal
342 singleton
343 begin
344 end
345 internal
346 singleton
347 begin
348 end
349 internal
350 singleton
351 begin
352 end
353 internal
354 singleton
355 begin
356 end
357 internal
358 singleton
359 begin
360 end
361 internal
362 singleton
363 begin
364 end
365 internal
366 singleton
367 begin
368 end
369 internal
370 singleton
371 begin
372 end
373 internal
374 singleton
375 begin
376 end
377 internal
378 singleton
379 begin
380 end
381 internal
382 singleton
383 begin
384 end
385 internal
386 singleton

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,3 @@
[
4.5109e+10 -2.00471e+09 -2.672177e+09 1.114113e+09 -8.008282e+09 -5.54918e+09 -8.087527e+09 -5.556585e+09 -6.347063e+09 -3.373851e+09 -3.843346e+09 -2.503764e+09 -4.038778e+09 -1.628978e+09 -3.658344e+09 -1.833892e+09 -2.750521e+09 -8.853997e+08 -1.515349e+09 -3.647572e+08 -6.377884e+08 -3.654417e+07 -9.805578e+07 -1849224 1.814711e+08 3.480522e+07 3.80909e+08 -4.716804e+07 3.213146e+08 -1.504913e+08 2.643303e+08 -3.810504e+07 3.958002e+08 1.283459e+08 4.22026e+08 1.628813e+08 2.36883e+08 -1.055832e+08 -1.122014e+08 -1.099238e+08 4.526635e+08
4.711843e+12 2.762046e+11 2.171106e+11 2.422162e+11 4.382205e+11 3.213519e+11 3.843864e+11 3.195329e+11 3.268667e+11 2.552171e+11 2.418821e+11 2.378349e+11 2.052809e+11 1.633811e+11 1.478665e+11 1.004429e+11 8.785726e+10 5.500246e+10 4.097437e+10 2.183258e+10 1.194666e+10 3.761198e+09 5.177095e+08 1.362906e+08 1.739844e+09 4.326937e+09 7.570374e+09 9.9598e+09 1.258423e+10 1.362375e+10 1.458388e+10 1.413189e+10 1.478221e+10 1.534696e+10 1.329504e+10 1.025456e+10 9.890456e+09 7.986768e+09 6.064231e+09 3.940631e+09 0 ]

View file

@ -0,0 +1 @@
# configuration file for apply-cmvn-online, used in the script ../local/run_online_decoding.sh

View file

@ -0,0 +1,2 @@
--left-context=3
--right-context=3

View file

@ -0,0 +1 @@
--left-context=3 --right-context=3