diff --git a/Cargo.lock b/Cargo.lock index 1d1c591..7ba812f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,6 +41,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + [[package]] name = "android_glue" version = "0.2.3" @@ -76,6 +85,17 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b5e5f48b927f04e952dedc932f31995a65a0bf65ec971c74436e51bf6e970d" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.9", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -88,6 +108,29 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "bindgen" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c0310d795615ac3541fa6312cf5b26b520852118e66165fcd23af4d544dc90a" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "clap", + "env_logger", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "which", +] + [[package]] name = "bit_field" version = "0.10.1" @@ -178,6 +221,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "0.1.10" @@ -199,6 +251,41 @@ dependencies = [ "libc", ] +[[package]] +name = "clang-sys" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "3.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b" +dependencies = [ + "atty", + "bitflags", + "clap_lex", + "indexmap", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "clipboard-win" version = "4.4.1" @@ -486,6 +573,33 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +[[package]] +name = "dyn-clonable" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e9232f0e607a262ceb9bd5141a3dfb3e4db6994b31989bbfd845878cba59fd4" +dependencies = [ + "dyn-clonable-impl", + "dyn-clone", +] + +[[package]] +name = "dyn-clonable-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dyn-clone" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28" + [[package]] name = "eframe" version = "0.18.0" @@ -573,6 +687,19 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "epaint" version = "0.18.1" @@ -726,6 +853,12 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "glow" version = "0.11.2" @@ -816,6 +949,12 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -825,6 +964,12 @@ dependencies = [ "libc", ] +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "ident_case" version = "1.0.1" @@ -889,6 +1034,16 @@ dependencies = [ "transpose 0.2.1", ] +[[package]] +name = "indexmap" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "inflate" version = "0.4.5" @@ -972,6 +1127,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "lebe" version = "0.5.1" @@ -1316,6 +1477,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", + "objc_exception", ] [[package]] @@ -1329,6 +1491,15 @@ dependencies = [ "objc_id", ] +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + [[package]] name = "objc_id" version = "0.1.1" @@ -1344,6 +1515,12 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b10983b38c53aebdf33f542c6275b0f58a238129d00c4ae0e6fb59738d783ca" +[[package]] +name = "os_str_bytes" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" + [[package]] name = "osmesa-sys" version = "0.1.2" @@ -1410,6 +1587,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "percent-encoding" version = "2.1.0" @@ -1566,12 +1749,35 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +[[package]] +name = "regex-syntax" +version = "0.6.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustdct" version = "0.4.0" @@ -1680,6 +1886,12 @@ dependencies = [ "libc", ] +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + [[package]] name = "slotmap" version = "1.0.6" @@ -1714,6 +1926,25 @@ dependencies = [ "wayland-protocols", ] +[[package]] +name = "speech-dispatcher" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "011a4f56750a1a31b41df25e27e55a4d46b3190bfa8f8d2f4c097446ad9702af" +dependencies = [ + "lazy_static", + "speech-dispatcher-sys", +] + +[[package]] +name = "speech-dispatcher-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c3e8acdf2b1f4bb13f1813b40b52f3edf4cc94d8a55fe713a584f672a10388d" +dependencies = [ + "bindgen", +] + [[package]] name = "spin" version = "0.9.3" @@ -1756,6 +1987,7 @@ dependencies = [ "serde", "serde_json", "time", + "tts", ] [[package]] @@ -1769,6 +2001,21 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" + [[package]] name = "thiserror" version = "1.0.31" @@ -1820,6 +2067,12 @@ dependencies = [ "num_threads", ] +[[package]] +name = "tinystr" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29738eedb4388d9ea620eeab9384884fc3f06f586a2eddb56bedc5885126c7c1" + [[package]] name = "tinyvec" version = "1.6.0" @@ -1898,6 +2151,47 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c74c96594835e10fa545e2a51e8709f30b173a092bfd6036ef2cec53376244f3" +[[package]] +name = "tts" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35f58408d8e9315bb8b4de7be684f638c666380576d184542dbb794f5ce39fdf" +dependencies = [ + "cocoa-foundation", + "core-foundation 0.9.3", + "dyn-clonable", + "jni", + "lazy_static", + "libc", + "log", + "ndk-glue 0.6.2", + "objc", + "speech-dispatcher", + "thiserror", + "unic-langid", + "wasm-bindgen", + "web-sys", + "windows", +] + +[[package]] +name = "unic-langid" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73328fcd730a030bdb19ddf23e192187a6b01cd98be6d3140622a89129459ce5" +dependencies = [ + "unic-langid-impl", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a4a8eeaf0494862c1404c95ec2f4c33a2acff5076f64314b465e3ddae1b934d" +dependencies = [ + "tinystr", +] + [[package]] name = "unicode-bidi" version = "0.3.8" @@ -2139,6 +2433,17 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c97e489d8f836838d497091de568cf16b117486d529ec5579233521065bd5e4" +[[package]] +name = "which" +version = "4.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" +dependencies = [ + "either", + "lazy_static", + "libc", +] + [[package]] name = "widestring" version = "0.5.1" @@ -2191,17 +2496,30 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647" +dependencies = [ + "windows_aarch64_msvc 0.37.0", + "windows_i686_gnu 0.37.0", + "windows_i686_msvc 0.37.0", + "windows_x86_64_gnu 0.37.0", + "windows_x86_64_msvc 0.37.0", +] + [[package]] name = "windows-sys" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", ] [[package]] @@ -2210,30 +2528,60 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +[[package]] +name = "windows_aarch64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a" + [[package]] name = "windows_i686_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +[[package]] +name = "windows_i686_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1" + [[package]] name = "windows_i686_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +[[package]] +name = "windows_i686_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c" + [[package]] name = "windows_x86_64_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +[[package]] +name = "windows_x86_64_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d" + [[package]] name = "windows_x86_64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +[[package]] +name = "windows_x86_64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d" + [[package]] name = "winit" version = "0.26.1" diff --git a/Cargo.toml b/Cargo.toml index 6ab2c4a..abf082b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,4 +21,6 @@ img_hash = "3" csv = "1" time = { version = "0.3", features = ["formatting", "local-offset"] } -rand = "0.8" \ No newline at end of file +rand = "0.8" + +tts = "0.22" \ No newline at end of file diff --git a/config.json b/config.json index 12d5a21..47cb404 100644 --- a/config.json +++ b/config.json @@ -91,5 +91,6 @@ "track_recognition_threshold": 10, "dump_frame_fraction": null, "light_mode": false, - "font_scale": 1.2 + "font_scale": 1.2, + "tts": {} } \ No newline at end of file diff --git a/src/analysis.rs b/src/analysis.rs index 52c3793..d7d14e1 100644 --- a/src/analysis.rs +++ b/src/analysis.rs @@ -38,6 +38,14 @@ fn check_penalty(image: &RgbImage, config: &Config) -> bool { } fn is_finished_lap(state: &AppState, frame: &LapState) -> bool { + let lap_time = match frame.lap_time { + Some(l) => l, + None => return false, + }; + if lap_time < Duration::from_secs(5) { + return false; + } + if let Some(race) = &state.current_race { if let Some(last_finish) = &race.last_lap_record_time { let diff = Instant::now().duration_since(*last_finish); @@ -47,9 +55,7 @@ fn is_finished_lap(state: &AppState, frame: &LapState) -> bool { } } if let Some(prev_frame) = state.buffered_frames.back() { - prev_frame.lap_time.is_some() - && prev_frame.lap_time == frame.lap_time - && prev_frame.lap_time.unwrap() > Duration::from_secs(5) + prev_frame.lap_time.is_some() && (prev_frame.lap_time == frame.lap_time || prev_frame.lap.unwrap_or(usize::MAX - 1) + 1 == frame.lap.unwrap_or_default()) } else { false } @@ -90,6 +96,7 @@ fn handle_new_frame(state: &mut AppState, lap_state: LapState, image: &RgbImage) state.frames_without_lap = 0; if state.current_race.is_none() { + crate::tts::pre_race_summary(&state.tts, state.config.as_ref(), &lap_state); state.penalties_this_lap = 0; let track_hash = get_track_hash(state.config.as_ref(), image); @@ -154,6 +161,9 @@ fn handle_new_frame(state: &mut AppState, lap_state: LapState, image: &RgbImage) if merged.lap.is_none() { merged.lap = Some(race.laps.len() + 1); } + + crate::tts::lap_summary(&state.tts, state.config.as_ref(), &merged, race); + race.laps.push(merged); race.last_lap_record_time = Some(Instant::now()); } diff --git a/src/config.rs b/src/config.rs index cad16ae..a449c9e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -14,6 +14,17 @@ fn default_ocr_interval_ms() -> u64 { fn default_font_scale() -> f32 { 1.0 } +fn default_true() -> bool { true } + +#[derive(Default, Deserialize, Serialize, Clone)] +pub struct TtsConfig { + #[serde(default = "default_true")] + pub pre_race: bool, + #[serde(default = "default_true")] + pub first_lap_summary: bool, + #[serde(default = "Default::default")] + pub every_lap_summary: bool, +} #[derive(Default, Serialize, Deserialize, Clone)] pub struct Config { @@ -35,6 +46,8 @@ pub struct Config { #[serde(default = "default_font_scale")] pub font_scale: f32, + + pub tts: Option, } impl Config { diff --git a/src/configs/config.default.json b/src/configs/config.default.json index 47b2f6d..12d5a21 100644 --- a/src/configs/config.default.json +++ b/src/configs/config.default.json @@ -46,18 +46,22 @@ "use_ocr_cache": null }, { - "name": "position", - "x": 50, - "y": 44, - "width": 250, - "height": 100 + "name": "position", + "x": 50, + "y": 44, + "width": 250, + "height": 100, + "threshold": null, + "use_ocr_cache": null }, { - "name": "lap", - "x": 2290, - "y": 44, - "width": 230, - "height": 100 + "name": "lap", + "x": 2290, + "y": 44, + "width": 230, + "height": 100, + "threshold": null, + "use_ocr_cache": null } ], "track_region": { @@ -75,13 +79,17 @@ "y": 120, "width": 50, "height": 30, - "threshold": 0.90, + "threshold": 0.9, "use_ocr_cache": null }, - "penalty_orange_color": [255, 177, 0], + "penalty_orange_color": [ + 255, + 177, + 0 + ], "ocr_interval_ms": 500, "track_recognition_threshold": 10, "dump_frame_fraction": null, "light_mode": false, - "font_scale": 1.3 + "font_scale": 1.2 } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 0e2610b..0b5585c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ mod ocr_db; mod state; mod stats_writer; mod training_ui; +mod tts; use std::{ collections::HashMap, @@ -32,16 +33,20 @@ use learned_tracks::LearnedTracks; use ocr_db::OcrDatabase; use state::{AppState, DebugOcrFrame, LapState, RaceState, SharedAppState}; use stats_writer::export_race_stats; +use crate::tts::Tts; fn main() -> anyhow::Result<()> { let mode = std::env::args().nth(1).unwrap_or_default(); if mode == "train" { return training_ui::training_ui(); } + let config = Arc::new(Config::load().unwrap()); + let tts = Tts::load(config.as_ref()); let app_state = AppState { - config: Arc::new(Config::load().unwrap()), + config, learned_tracks: Arc::new(LearnedTracks::load().unwrap()), ocr_db: Arc::new(OcrDatabase::load().unwrap()), + tts, ..Default::default() }; let state = Arc::new(Mutex::new(app_state)); diff --git a/src/state.rs b/src/state.rs index 293287e..36a34dd 100644 --- a/src/state.rs +++ b/src/state.rs @@ -8,7 +8,7 @@ use egui_extras::RetainedImage; use image::RgbImage; use time::{format_description, OffsetDateTime}; -use crate::{config::Config, learned_tracks::LearnedTracks, ocr_db::OcrDatabase}; +use crate::{config::Config, learned_tracks::LearnedTracks, ocr_db::OcrDatabase, tts::Tts}; #[derive(Debug, Clone, Default)] pub struct LapState { @@ -88,15 +88,20 @@ impl LapState { } } -fn median_wear(values: Vec>) -> Option { +fn wear_values(values: &[Option]) -> Vec { let mut wear_values = Vec::new(); let mut last_value = 100; - for val in values.into_iter().flatten() { - if val < last_value { + for val in values.iter().flatten() { + if *val < last_value { wear_values.push(last_value - val); } - last_value = val; + last_value = *val; } + wear_values +} + +fn median_wear(values: Vec>) -> Option { + let mut wear_values = wear_values(&values); wear_values.sort_unstable(); wear_values.get(wear_values.len() / 2).cloned() } @@ -148,6 +153,23 @@ impl RaceState { pub fn gas_per_lap(&self) -> Option { median_wear(self.laps.iter().map(|lap| lap.gas).collect()) } + + pub fn latest_gas_diff(&self, to: usize) -> Option { + let last_gas = self.laps.iter().last().map(|l| l.gas).unwrap_or(Some(100))?; + if to < last_gas { + Some(last_gas - to) + } else { + None + } + } + pub fn latest_tyre_diff(&self, to: usize) -> Option { + let last_tyres = self.laps.iter().last().map(|l| l.tyres).unwrap_or(Some(100))?; + if to < last_tyres { + Some(last_tyres - to) + } else { + None + } + } } pub struct DebugOcrFrame { @@ -177,6 +199,7 @@ pub struct AppState { pub config: Arc, pub learned_tracks: Arc, pub ocr_db: Arc, + pub tts: Tts, } pub type SharedAppState = Arc>; diff --git a/src/tts.rs b/src/tts.rs new file mode 100644 index 0000000..7cc31d3 --- /dev/null +++ b/src/tts.rs @@ -0,0 +1,101 @@ +use crate::{ + config::Config, + state::{LapState, RaceState}, +}; + +enum TtsKind { + PreRace, + FirstLap, + LapSummary, +} + +impl TtsKind { + fn is_enabled(&self, config: &Config) -> bool { + if let Some(cfg) = &config.tts { + match self { + TtsKind::PreRace => cfg.pre_race, + TtsKind::FirstLap => cfg.first_lap_summary, + TtsKind::LapSummary => cfg.every_lap_summary, + } + } else { + false + } + } +} + +#[derive(Clone, Default)] +pub struct Tts { + engine: Option, +} + +impl Tts { + pub fn load(config: &Config) -> Self { + let engine = if config.tts.is_some() { + Some(tts::Tts::default().unwrap()) + } else { + None + }; + Self { engine } + } + + fn speak(&self, config: &Config, kind: TtsKind, phrase: &str) { + if !kind.is_enabled(config) { + return; + } + + if let Some(mut engine) = self.engine.clone() { + let _ignored_error = engine.speak(phrase, false); + } + } +} + +pub fn pre_race_summary(tts: &Tts, config: &Config, lap_state: &LapState) { + if let Some(total_laps) = lap_state.total_laps { + if total_laps > 1 { + tts.speak( + config, + TtsKind::PreRace, + &format!( + "You are P {}. {} laps. For no pit {} gas per lap or less.", + lap_state.position.unwrap_or_default(), + total_laps, + 100 / total_laps + ), + ); + } + } +} + +pub fn lap_summary(tts: &Tts, config: &Config, lap_state: &LapState, race: &RaceState) { + let (lap, gas, tyres) = match (lap_state.lap, lap_state.gas, lap_state.tyres) { + (Some(l), Some(g), Some(t)) => (l, g, t), + _ => return, + }; + if lap >= race.total_laps { + return; + } + + let (gas_wear, tyre_wear) = match (race.latest_gas_diff(gas), race.latest_tyre_diff(tyres)) { + (Some(gw), Some(tw)) => (gw, tw), + _ => return, + }; + + if lap == 1 { + let laps_per_tank = 100 / gas_wear; + let pit_stops = race.total_laps / laps_per_tank; + tts.speak( + config, + TtsKind::FirstLap, + &format!( + "{} gas per lap, {} tires per lap, likely {} pits", + gas_wear, tyre_wear, pit_stops + ), + ) + } else { + tts.speak( + config, + TtsKind::LapSummary, + &format!("{} gas, {} tires", gas_wear, tyre_wear,), + ) + } +} diff --git a/suggestions.md b/suggestions.md index c55b0c3..a0dc30b 100644 --- a/suggestions.md +++ b/suggestions.md @@ -4,8 +4,8 @@ - Editable lap stats - Autocomplete for car/track - Detect car name from load screen - - TTS for pit strategies - Post race metric charts + - [PARTIAL] TTS for pit strategies - [DONE] Show delta positions - [DONE] Track penalties - [DONE] Re-do debug/learn functionality