mirror of
https://github.com/lightsoutgames/godot-tts
synced 2025-04-19 23:55:56 +00:00
Compare commits
182 Commits
Author | SHA1 | Date | |
---|---|---|---|
35afabbdca | |||
e331676855 | |||
c92135d452 | |||
|
1d963924ce | ||
5abf6317ef | |||
d83b3689f5 | |||
2dba6d0a55 | |||
6d48c23e3e | |||
f21d3357e9 | |||
dba2d061ac | |||
|
bf2afec4b3 | ||
|
2537d27886 | ||
|
8d95b3b5de | ||
1c7686d09d | |||
b43b4fe19b | |||
5c1cf59d98 | |||
c8aabb02ad | |||
58dfb757c4 | |||
f800aebb14 | |||
890e32df80 | |||
d9d135d530 | |||
65311abdeb | |||
|
de6da09431 | ||
691997023a | |||
30b27c542e | |||
fbdc2a7724 | |||
e8dfbb6a69 | |||
e2c1bfa7c9 | |||
b34c880a36 | |||
e2c162c547 | |||
413b68be63 | |||
3ff48e2601 | |||
a0cc984fa1 | |||
ac0425b652 | |||
140b80738b | |||
47782166d3 | |||
55eea365c4 | |||
e21edf0c31 | |||
5529358651 | |||
3639e98f65 | |||
32c20ddf90 | |||
760a7745f1 | |||
f759a67507 | |||
7c25c6ad0e | |||
d58bb881e5 | |||
0abf1ffcdd | |||
893fae0c99 | |||
93432f2a44 | |||
e483f3467b | |||
8ed356fd47 | |||
e19d154b99 | |||
f7a2ac7760 | |||
3c2e2220b0 | |||
17220b4634 | |||
9243ea4e7f | |||
f105ee702b | |||
1dabd5afd3 | |||
2c73b45300 | |||
69b5c39f66 | |||
ff247a3f19 | |||
ed7f3920f6 | |||
41c4204224 | |||
c7563fa997 | |||
68d1d9e011 | |||
a71bf417b6 | |||
0c9efa5c7b | |||
abe9ab3f55 | |||
795cc4790c | |||
cb7eda45e2 | |||
ddfc262278 | |||
2222e3eda7 | |||
54222b1fcb | |||
4990454320 | |||
8732269699 | |||
6bbddcc6dd | |||
00aadde492 | |||
30eacee6ff | |||
274902432c | |||
afdb73991f | |||
b556c4d2f0 | |||
f9d485faab | |||
f0834feea5 | |||
c5f8e28028 | |||
550bb82c89 | |||
591e9a26c1 | |||
20abaa45ab | |||
d3b90f48e7 | |||
971f0afbfd | |||
4ff4d26c77 | |||
3f611f18b8 | |||
705126ef2f | |||
e05cdde57d | |||
5395256385 | |||
30a82fc6b0 | |||
6fd18b0de1 | |||
8dfa941ba6 | |||
f389e818b9 | |||
8c26502f25 | |||
1eca767056 | |||
bfdedf64db | |||
dec1d2cc17 | |||
e0f419aaf6 | |||
b78848f583 | |||
067b8b43b5 | |||
0c11a70d28 | |||
574edcf93c | |||
aa3c9475ab | |||
a7e457da71 | |||
88b20ad586 | |||
85492b52dc | |||
a4c61a421f | |||
d115470c0d | |||
b076b78eb5 | |||
0d7ba71769 | |||
c7d827da93 | |||
e88641f54d | |||
d7e768e0f4 | |||
fefa09779d | |||
5ca1076bc4 | |||
f81486a02c | |||
aa718c5fe6 | |||
4495b89ff3 | |||
8b591704a3 | |||
495c5eddad | |||
691dd66054 | |||
91faf9197c | |||
f58f309d0c | |||
eb7093ab4e | |||
35a34871a3 | |||
ba22fddf37 | |||
4d883fc420 | |||
251888de76 | |||
4580a5b278 | |||
8ee47306f6 | |||
f833717276 | |||
873a6c749d | |||
befa3affa7 | |||
8069333fde | |||
bade7da45b | |||
eba74f8a0e | |||
40628f01e8 | |||
ea85c3d9dc | |||
71505a99a0 | |||
f88f26cd1e | |||
2bb87b6312 | |||
8a16c3aec1 | |||
e221da0057 | |||
a659d036fe | |||
f49091c7e0 | |||
7ff42deb55 | |||
9a5fd7232d | |||
87a1035a12 | |||
ec3cb01739 | |||
8c96824f11 | |||
6b4463471f | |||
b3fba41012 | |||
69c1049848 | |||
2e50934d68 | |||
14f6626e0a | |||
be52e4c6ae | |||
735c7ebfa6 | |||
f364001480 | |||
aec975ade6 | |||
17517abc98 | |||
35350daa3e | |||
5128b1fa40 | |||
0b92eef5e9 | |||
2049954aa2 | |||
8d373391f3 | |||
f41c34c12f | |||
a7b8ecb775 | |||
e2f81c32dc | |||
934a40e273 | |||
c4646c4b26 | |||
3b067faecd | |||
bf6372a1a8 | |||
ecfee60db5 | |||
e02e1dd2b8 | |||
6d4cbb1b9d | |||
21d8da6a84 | |||
6542234b8e | |||
b751a35337 |
2
.cargo/config
Normal file
2
.cargo/config
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[target.x86_64-pc-windows-msvc]
|
||||||
|
rustflags = ["-C", "target-feature=+crt-static"]
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
*.aar filter=lfs diff=lfs merge=lfs -text
|
55
.github/workflows/build.yml
vendored
Normal file
55
.github/workflows/build.yml
vendored
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
name: Check
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: Swatinem/rust-cache@v1
|
||||||
|
- run: sudo apt-get update; sudo apt-get install -y libspeechd-dev
|
||||||
|
if: ${{ runner.os == 'Linux' }}
|
||||||
|
- run: |
|
||||||
|
choco install -qy llvm
|
||||||
|
echo LIBCLANG_PATH=c:\program` files\llvm\bin >> $Env:GITHUB_ENV
|
||||||
|
if: ${{ runner.os == 'Windows' }}
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: stable
|
||||||
|
components: rustfmt, clippy
|
||||||
|
override: true
|
||||||
|
- uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: check
|
||||||
|
args: --all-features --examples
|
||||||
|
- uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: fmt
|
||||||
|
args: --all -- --check
|
||||||
|
- uses: actions-rs/clippy-check@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
args: --all-features
|
||||||
|
|
||||||
|
build_android:
|
||||||
|
name: Build Android
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
lfs: true
|
||||||
|
- uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ~/.gradle/caches
|
||||||
|
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
|
||||||
|
restore-keys: ${{ runner.os }}-gradle
|
||||||
|
- run: |
|
||||||
|
./gradlew assemble
|
225
.github/workflows/release.yml
vendored
Normal file
225
.github/workflows/release.yml
vendored
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_linux:
|
||||||
|
name: Build Linux
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: Swatinem/rust-cache@v1
|
||||||
|
- run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libspeechd-dev
|
||||||
|
cargo build --release
|
||||||
|
mkdir linux
|
||||||
|
mv target/release/*.so linux
|
||||||
|
- uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: linux
|
||||||
|
path: linux
|
||||||
|
|
||||||
|
build_linux_32:
|
||||||
|
name: Build Linux (32 bits)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: Swatinem/rust-cache@v1
|
||||||
|
- run: |
|
||||||
|
sudo dpkg --add-architecture i386
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libspeechd-dev:i386 gcc-multilib
|
||||||
|
rustup target add i686-unknown-linux-gnu
|
||||||
|
cargo build --release --target i686-unknown-linux-gnu
|
||||||
|
mkdir linux-32
|
||||||
|
mv target/i686-unknown-linux-gnu/release/*.so linux-32
|
||||||
|
- uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: linux-32
|
||||||
|
path: linux-32
|
||||||
|
|
||||||
|
build_windows:
|
||||||
|
name: Build Windows
|
||||||
|
runs-on: windows-latest
|
||||||
|
env:
|
||||||
|
LIBCLANG_PATH: c:\program files\llvm\bin
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: Swatinem/rust-cache@v1
|
||||||
|
- run: |
|
||||||
|
choco install -qy llvm
|
||||||
|
cargo build --release
|
||||||
|
mkdir windows
|
||||||
|
move target\release\*.dll windows
|
||||||
|
- uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: windows
|
||||||
|
path: windows
|
||||||
|
|
||||||
|
build_windows_32:
|
||||||
|
name: Build Windows (32 bits)
|
||||||
|
runs-on: windows-latest
|
||||||
|
env:
|
||||||
|
LIBCLANG_PATH: c:\program files\llvm\bin
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: Swatinem/rust-cache@v1
|
||||||
|
- run: |
|
||||||
|
choco install -qy llvm
|
||||||
|
rustup target add i686-pc-windows-msvc
|
||||||
|
cargo build --release --target i686-pc-windows-msvc
|
||||||
|
mkdir windows-32
|
||||||
|
move target\i686-pc-windows-msvc\release\*.dll windows-32
|
||||||
|
- uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: windows-32
|
||||||
|
path: windows-32
|
||||||
|
|
||||||
|
build_uwp:
|
||||||
|
name: Build UWP
|
||||||
|
runs-on: windows-latest
|
||||||
|
env:
|
||||||
|
LIBCLANG_PATH: c:\program files\llvm\bin
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: Swatinem/rust-cache@v1
|
||||||
|
- run: |
|
||||||
|
choco install -y llvm
|
||||||
|
cargo build --release
|
||||||
|
mkdir uwp
|
||||||
|
move target\release\godot_tts.dll uwp\godot_tts.uwp.dll
|
||||||
|
- uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: uwp
|
||||||
|
path: uwp
|
||||||
|
|
||||||
|
build_macos:
|
||||||
|
name: Build MacOS
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: Swatinem/rust-cache@v1
|
||||||
|
- run: |
|
||||||
|
cargo build --release
|
||||||
|
mkdir macos
|
||||||
|
mv target/release/*.dylib macos
|
||||||
|
- uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: macos
|
||||||
|
path: macos
|
||||||
|
|
||||||
|
build_android:
|
||||||
|
name: Build Android
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
lfs: true
|
||||||
|
- uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ~/.gradle/caches
|
||||||
|
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
|
||||||
|
restore-keys: ${{ runner.os }}-gradle
|
||||||
|
- run: |
|
||||||
|
./gradlew assemble
|
||||||
|
mv build android
|
||||||
|
- uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: android
|
||||||
|
path: android
|
||||||
|
|
||||||
|
build_ios:
|
||||||
|
name: Build iOS
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: Swatinem/rust-cache@v1
|
||||||
|
- run: |
|
||||||
|
rustup target add aarch64-apple-ios x86_64-apple-ios
|
||||||
|
cargo install cargo-lipo
|
||||||
|
cargo lipo --release
|
||||||
|
mkdir ios
|
||||||
|
mv target/universal/release/*.a ios
|
||||||
|
- uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: ios
|
||||||
|
path: ios
|
||||||
|
|
||||||
|
package:
|
||||||
|
name: Package
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
[
|
||||||
|
build_linux,
|
||||||
|
build_linux_32,
|
||||||
|
build_windows,
|
||||||
|
build_windows_32,
|
||||||
|
build_uwp,
|
||||||
|
build_macos,
|
||||||
|
build_android,
|
||||||
|
build_ios,
|
||||||
|
]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: linux
|
||||||
|
- uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: linux-32
|
||||||
|
- uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: windows
|
||||||
|
- uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: windows-32
|
||||||
|
- uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: uwp
|
||||||
|
- uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: macos
|
||||||
|
- uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: android
|
||||||
|
- uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: ios
|
||||||
|
- run: |
|
||||||
|
mkdir -p godot-tts/target/release/32
|
||||||
|
cp linux/*.so godot-tts/target/release
|
||||||
|
cp linux-32/*.so godot-tts/target/release/32
|
||||||
|
cp windows/*.dll godot-tts/target/release
|
||||||
|
cp windows-32/*.dll godot-tts/target/release/32
|
||||||
|
cp uwp/*.dll godot-tts/target/release
|
||||||
|
cp macos/*.dylib godot-tts/target/release
|
||||||
|
cp android/outputs/aar/godot-tts.aar godot-tts/
|
||||||
|
cp ios/*.a godot-tts/target/release
|
||||||
|
cp LICENSE godot-tts
|
||||||
|
cp README.md godot-tts
|
||||||
|
cp TTS.gd godot-tts.g* godot-tts
|
||||||
|
rm godot-tts/*.gdnlib
|
||||||
|
mv godot-tts/godot-tts.gdnlib.release godot-tts/godot-tts.gdnlib
|
||||||
|
zip -r9 godot-tts godot-tts
|
||||||
|
- uses: actions/create-release@v1
|
||||||
|
id: create_release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.ref }}
|
||||||
|
release_name: Release ${{ github.ref }}
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
|
- uses: actions/upload-release-asset@v1
|
||||||
|
id: upload-release-asset
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: ./godot-tts.zip
|
||||||
|
asset_name: godot-tts.zip
|
||||||
|
asset_content_type: application/zip
|
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -1,3 +1,12 @@
|
||||||
/target
|
/target
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
bazel-bin
|
||||||
|
bazel-godot-tts
|
||||||
|
bazel-out
|
||||||
|
bazel-testlogs
|
||||||
|
build
|
||||||
|
.gradle
|
||||||
|
.classpath
|
||||||
|
.project
|
||||||
|
.settings
|
|
@ -1,46 +0,0 @@
|
||||||
stages:
|
|
||||||
- build
|
|
||||||
- package
|
|
||||||
- publish
|
|
||||||
|
|
||||||
build linux:
|
|
||||||
stage: build
|
|
||||||
image: rust
|
|
||||||
script:
|
|
||||||
- apt-get update
|
|
||||||
- apt-get install -y libclang-3.9-dev libspeechd-dev
|
|
||||||
- export CPATH=/usr/lib/llvm-3.9/lib/clang/3.9.1/include/
|
|
||||||
- cargo build --release
|
|
||||||
- mv target linux
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- linux
|
|
||||||
expire_in: 1 day
|
|
||||||
|
|
||||||
package:
|
|
||||||
stage: package
|
|
||||||
script:
|
|
||||||
- mkdir godot-tts
|
|
||||||
- cp godot-tts.gdnlib.release godot-tts/godot-tts.gdnlib
|
|
||||||
- mkdir -p godot-tts/target/release
|
|
||||||
- cp linux/release/*.so godot-tts/target/release
|
|
||||||
- cp LICENSE godot-tts
|
|
||||||
- cp TTS.gd godot-tts.g* godot-tts
|
|
||||||
- cp godot-tts.gdnlib.release godot-tts/godot-tts.gdnlib
|
|
||||||
- rm godot-tts/*.release
|
|
||||||
artifacts:
|
|
||||||
name: godot-tts
|
|
||||||
paths:
|
|
||||||
- godot-tts
|
|
||||||
expire_in: 1 day
|
|
||||||
|
|
||||||
publish:
|
|
||||||
stage: publish
|
|
||||||
script:
|
|
||||||
- echo Publishing...
|
|
||||||
artifacts:
|
|
||||||
name: godot-tts
|
|
||||||
paths:
|
|
||||||
- godot-tts
|
|
||||||
only:
|
|
||||||
- tags
|
|
20
.vscode/tasks.json
vendored
Normal file
20
.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"type": "cargo",
|
||||||
|
"command": "build",
|
||||||
|
"problemMatcher": [
|
||||||
|
"$rustc"
|
||||||
|
],
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
},
|
||||||
|
"label": "rust: cargo build,
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "never"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,12 +1,13 @@
|
||||||
[package]
|
[package]
|
||||||
name = "godot-tts"
|
name = "godot-tts"
|
||||||
version = "0.1.3"
|
version = "0.1.0"
|
||||||
authors = ["Nolan Darilek <nolan@thewordnerd.info>"]
|
authors = ["Nolan Darilek <nolan@thewordnerd.info>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["staticlib", "cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gdnative = { git = "https://github.com/GodotNativeTools/godot-rust/" }
|
env_logger = "0.10"
|
||||||
tts = "0.2"
|
gdnative = "0.11"
|
||||||
|
tts = { version = "0.25", features = ["tolk"] }
|
71
README.md
Normal file
71
README.md
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
# Godot TTS
|
||||||
|
|
||||||
|
This addon was primarily developed for the [Godot Accessibility addon](https://github.com/lightsoutgames/godot-accessibility), but should work well in other contexts where a Godot game might require text-to-speech.
|
||||||
|
|
||||||
|
## Supported Platforms
|
||||||
|
|
||||||
|
Text-to-speech is complicated, and some features may not work everywhere. Most optional features have an associated boolean property used to determine if the feature is available. Further, while I do attempt to ensure that this addon works as well as possible on all platforms, there may be bugs, and pull requests are welcome. Known supported platforms include:
|
||||||
|
|
||||||
|
* Windows
|
||||||
|
* Screen readers/SAPI via Tolk (requires `use_tolk` Cargo feature)
|
||||||
|
* WinRT
|
||||||
|
* Linux via [Speech Dispatcher](https://freebsoft.org/speechd)
|
||||||
|
* MacOS
|
||||||
|
* AppKit on MacOS 10.13 and below
|
||||||
|
* AVFoundation on MacOS 10.14 and above, and iOS
|
||||||
|
* Web
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
Note that not all features are supported on all synthesizers. Any feature that may not be supported has a boolean property that can be checked to determine whether it works on the current platform.
|
||||||
|
|
||||||
|
* Speaking text, with interruption support on most platforms
|
||||||
|
* Stopping speech in progress
|
||||||
|
* Getting and setting speech rate in both native synthesizer units and percentages
|
||||||
|
* Detecting whether a screen reader is active
|
||||||
|
* Determining whether the synthesizer is speaking, and sending a signal on completion
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
The public-facing API is contained entirely within [TTS.GD](https://github.com/lightsoutgames/godot-tts/blob/master/TTS.gd).
|
||||||
|
|
||||||
|
## Download
|
||||||
|
|
||||||
|
Download the [latest release](https://github.com/lightsoutgames/godot-tts/releases).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Inside your projects root folder create a new folder named _addons_. Then extract _godot-tts.zip_ into that folder. After attaching TTS.GD to a node you can make calls to the API.
|
||||||
|
|
||||||
|
## Notes on Android
|
||||||
|
|
||||||
|
Usage on Android is a bit more complicated:
|
||||||
|
|
||||||
|
* Set up a [custom Android build](https://docs.godotengine.org/en/latest/getting_started/workflow/export/android_custom_build.html).
|
||||||
|
* Create a directory reachable at _res://android/plugins_ and place the _godot-tts.aar_ and _.godot-tts.gdap_ files from a release into that directory.
|
||||||
|
* Update _res://android/build/config.gradle_ to specify a `minSdk` of at least 21. Individual versions may vary, but `minSdk` is the critical value from this example:
|
||||||
|
|
||||||
|
```
|
||||||
|
ext.versions = [
|
||||||
|
androidGradlePlugin: '3.5.3',
|
||||||
|
compileSdk : 29,
|
||||||
|
minSdk : 21,
|
||||||
|
targetSdk : 29,
|
||||||
|
buildTools : '29.0.3',
|
||||||
|
supportCoreUtils : '1.0.0',
|
||||||
|
kotlinVersion : '1.3.61',
|
||||||
|
v4Support : '1.0.0'
|
||||||
|
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes on Universal Windows Platform
|
||||||
|
|
||||||
|
Godot's UWP export is [currently broken](https://github.com/godotengine/godot/issues/30558). In order to use this addon in a UWP game, do the following:
|
||||||
|
|
||||||
|
1. [Export to UWP](https://docs.godotengine.org/en/stable/getting_started/workflow/export/exporting_for_uwp.html).
|
||||||
|
2. [Extract the newly-created .appx file with `makeappx unpack`](https://docs.microsoft.com/en-us/windows/msix/package/create-app-package-with-makeappx-tool).
|
||||||
|
3. If you've extracted the .appx file to a directory like _mygame\_, copy _addons\godot-tts\target\release\godot_tts.dll_ to _mygame\game\addons\godot-tts\target\release\godot_tts.dll_, creating the directory if it doesn't exist.
|
||||||
|
4. Repack the appx using _makeappx_.
|
||||||
|
|
||||||
|
It should then be ready to sign and install. Hopefully Godot's UWP export will eventually copy GDNative DLLs correctly so this procedure isn't necessary.
|
313
TTS.gd
313
TTS.gd
|
@ -1,49 +1,326 @@
|
||||||
tool
|
tool
|
||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
const TTS = preload("godot-tts.gdns")
|
signal utterance_begin(utterance)
|
||||||
|
|
||||||
var tts = null
|
signal utterance_end(utterance)
|
||||||
|
|
||||||
func _ready():
|
signal utterance_stop(utterance)
|
||||||
# Only initialize TTS if it's available or if we're in the editor.
|
|
||||||
if TTS.can_instance() or Engine.editor_hint:
|
var TTS
|
||||||
print_debug("Attempting to load TTS.")
|
|
||||||
|
var tts
|
||||||
|
|
||||||
|
|
||||||
|
func _init():
|
||||||
|
if OS.get_name() == "Server" or OS.has_feature("JavaScript"):
|
||||||
|
return
|
||||||
|
elif Engine.has_singleton("GodotTTS"):
|
||||||
|
tts = Engine.get_singleton("GodotTTS")
|
||||||
|
else:
|
||||||
|
TTS = preload("godot-tts.gdns")
|
||||||
|
if TTS and (TTS.can_instance() or Engine.editor_hint):
|
||||||
tts = TTS.new()
|
tts = TTS.new()
|
||||||
|
if tts:
|
||||||
|
if not tts is JNISingleton:
|
||||||
|
self.add_child(tts)
|
||||||
|
if self.are_utterance_callbacks_supported:
|
||||||
|
tts.connect("utterance_begin", self, "_on_utterance_begin")
|
||||||
|
tts.connect("utterance_end", self, "_on_utterance_end")
|
||||||
|
tts.connect("utterance_stop", self, "_on_utterance_stop")
|
||||||
else:
|
else:
|
||||||
print_debug("TTS not available!")
|
print_debug("TTS not available!")
|
||||||
|
|
||||||
func set_rate(rate):
|
|
||||||
if tts != null:
|
|
||||||
tts.rate = rate
|
|
||||||
|
|
||||||
func get_rate():
|
func _ready():
|
||||||
if tts != null:
|
pause_mode = Node.PAUSE_MODE_PROCESS
|
||||||
return tts.rate
|
|
||||||
|
|
||||||
|
func _get_min_volume():
|
||||||
|
if OS.has_feature('JavaScript'):
|
||||||
|
return 0
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
var rate setget set_rate, get_rate
|
var min_volume setget , _get_min_volume
|
||||||
|
|
||||||
|
|
||||||
|
func _get_max_volume():
|
||||||
|
if OS.has_feature('JavaScript'):
|
||||||
|
return 1.0
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
var max_volume setget , _get_max_volume
|
||||||
|
|
||||||
|
|
||||||
|
func _get_normal_volume():
|
||||||
|
if OS.has_feature('JavaScript'):
|
||||||
|
return 1.0
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
var normal_volume setget , _get_normal_volume
|
||||||
|
|
||||||
|
var javascript_volume = self.normal_volume
|
||||||
|
|
||||||
|
|
||||||
|
func _set_volume(volume):
|
||||||
|
if volume < self.min_volume:
|
||||||
|
volume = self.min_volume
|
||||||
|
elif volume > self.max_volume:
|
||||||
|
volume = self.max_volume
|
||||||
|
if OS.has_feature('JavaScript'):
|
||||||
|
javascript_volume = volume
|
||||||
|
|
||||||
|
|
||||||
|
func _get_volume():
|
||||||
|
if OS.has_feature('JavaScript'):
|
||||||
|
return javascript_volume
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
var volume setget _set_volume, _get_volume
|
||||||
|
|
||||||
|
|
||||||
|
func _get_volume_percentage():
|
||||||
|
return range_lerp(self.volume, self.min_volume, self.max_volume, 0, 100)
|
||||||
|
|
||||||
|
|
||||||
|
func _set_volume_percentage(v):
|
||||||
|
self.rate = range_lerp(v, 0, 100, self.min_volume, self.max_volume)
|
||||||
|
|
||||||
|
|
||||||
|
var volume_percentage setget _set_volume_percentage, _get_volume_percentage
|
||||||
|
|
||||||
|
|
||||||
|
func _get_normal_volume_percentage():
|
||||||
|
return range_lerp(self.normal_volume, self.min_volume, self.max_volume, 0, 100)
|
||||||
|
|
||||||
|
var normal_volume_percentage setget , _get_normal_volume_percentage
|
||||||
|
|
||||||
|
|
||||||
|
func _get_min_rate():
|
||||||
|
if OS.has_feature('JavaScript'):
|
||||||
|
return 0.1
|
||||||
|
elif Engine.has_singleton("GodotTTS"):
|
||||||
|
return 0.1
|
||||||
|
elif tts != null:
|
||||||
|
return tts.min_rate
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
var min_rate setget , _get_min_rate
|
||||||
|
|
||||||
|
|
||||||
|
func _get_max_rate():
|
||||||
|
if OS.has_feature('JavaScript'):
|
||||||
|
return 10.0
|
||||||
|
elif Engine.has_singleton("GodotTTS"):
|
||||||
|
return 10.0
|
||||||
|
elif tts != null:
|
||||||
|
return tts.max_rate
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
var max_rate setget , _get_max_rate
|
||||||
|
|
||||||
|
|
||||||
|
func _get_normal_rate():
|
||||||
|
if OS.has_feature('JavaScript'):
|
||||||
|
return 1.0
|
||||||
|
elif Engine.has_singleton("GodotTTS"):
|
||||||
|
return 1.0
|
||||||
|
elif tts != null:
|
||||||
|
return tts.normal_rate
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
var normal_rate setget , _get_normal_rate
|
||||||
|
|
||||||
|
var javascript_rate = self.normal_rate
|
||||||
|
|
||||||
|
|
||||||
|
func _set_rate(rate):
|
||||||
|
if rate < self.min_rate:
|
||||||
|
rate = self.min_rate
|
||||||
|
elif rate > self.max_rate:
|
||||||
|
rate = self.max_rate
|
||||||
|
if Engine.has_singleton("GodotTTS"):
|
||||||
|
return tts.set_rate(rate)
|
||||||
|
elif tts != null:
|
||||||
|
tts.rate = rate
|
||||||
|
elif OS.has_feature('JavaScript'):
|
||||||
|
javascript_rate = rate
|
||||||
|
|
||||||
|
|
||||||
|
func _get_rate():
|
||||||
|
if Engine.has_singleton("GodotTTS"):
|
||||||
|
return tts.get_rate()
|
||||||
|
elif tts != null:
|
||||||
|
return tts.rate
|
||||||
|
elif OS.has_feature('JavaScript'):
|
||||||
|
return javascript_rate
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
var rate setget _set_rate, _get_rate
|
||||||
|
|
||||||
|
|
||||||
|
func _get_rate_percentage():
|
||||||
|
return range_lerp(self.rate, self.min_rate, self.max_rate, 0, 100)
|
||||||
|
|
||||||
|
|
||||||
|
func _set_rate_percentage(v):
|
||||||
|
self.rate = range_lerp(v, 0, 100, self.min_rate, self.max_rate)
|
||||||
|
|
||||||
|
|
||||||
|
var rate_percentage setget _set_rate_percentage, _get_rate_percentage
|
||||||
|
|
||||||
|
|
||||||
|
func _get_normal_rate_percentage():
|
||||||
|
return range_lerp(self.normal_rate, self.min_rate, self.max_rate, 0, 100)
|
||||||
|
|
||||||
|
|
||||||
|
var normal_rate_percentage setget , _get_rate_percentage
|
||||||
|
|
||||||
|
|
||||||
func speak(text, interrupt := true):
|
func speak(text, interrupt := true):
|
||||||
print_debug("%s: %s" % [text, interrupt])
|
var utterance
|
||||||
if tts != null:
|
if tts != null:
|
||||||
tts.speak(text, interrupt)
|
utterance = tts.speak(text, interrupt)
|
||||||
|
elif OS.has_feature('JavaScript'):
|
||||||
|
var code = (
|
||||||
|
"""
|
||||||
|
let utterance = new SpeechSynthesisUtterance("%s")
|
||||||
|
utterance.rate = %s
|
||||||
|
utterance.volume = %s
|
||||||
|
"""
|
||||||
|
% [text.replace("\n", " "), javascript_rate, javascript_volume]
|
||||||
|
)
|
||||||
|
if interrupt:
|
||||||
|
code += """
|
||||||
|
window.speechSynthesis.cancel()
|
||||||
|
"""
|
||||||
|
code += "window.speechSynthesis.speak(utterance)"
|
||||||
|
JavaScript.eval(code)
|
||||||
|
else:
|
||||||
|
print_debug("%s: %s" % [text, interrupt])
|
||||||
|
return utterance
|
||||||
|
|
||||||
|
|
||||||
func stop():
|
func stop():
|
||||||
if tts != null:
|
if tts != null:
|
||||||
tts.stop()
|
tts.stop()
|
||||||
|
elif OS.has_feature('JavaScript'):
|
||||||
|
JavaScript.eval("window.speechSynthesis.cancel()")
|
||||||
|
|
||||||
func get_is_rate_supported():
|
|
||||||
if tts != null:
|
func _get_is_rate_supported():
|
||||||
|
if Engine.has_singleton("GodotTTS"):
|
||||||
|
return true
|
||||||
|
elif OS.has_feature('JavaScript'):
|
||||||
|
return true
|
||||||
|
elif tts != null:
|
||||||
return tts.is_rate_supported()
|
return tts.is_rate_supported()
|
||||||
else:
|
else:
|
||||||
return false
|
return false
|
||||||
|
|
||||||
var is_rate_supported setget , get_is_rate_supported
|
|
||||||
|
var is_rate_supported setget , _get_is_rate_supported
|
||||||
|
|
||||||
|
|
||||||
|
func _get_are_utterance_callbacks_supported():
|
||||||
|
if Engine.has_singleton("GodotTTS"):
|
||||||
|
return true
|
||||||
|
elif OS.has_feature('JavaScript'):
|
||||||
|
return false
|
||||||
|
elif tts != null:
|
||||||
|
return tts.are_utterance_callbacks_supported()
|
||||||
|
else:
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
var are_utterance_callbacks_supported setget , _get_are_utterance_callbacks_supported
|
||||||
|
|
||||||
|
|
||||||
|
func _get_can_detect_is_speaking():
|
||||||
|
if Engine.has_singleton("GodotTTS"):
|
||||||
|
return true
|
||||||
|
elif OS.has_feature('JavaScript'):
|
||||||
|
return true
|
||||||
|
elif tts != null:
|
||||||
|
return tts.can_detect_is_speaking
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
var can_detect_is_speaking setget , _get_can_detect_is_speaking
|
||||||
|
|
||||||
|
|
||||||
|
func _get_is_speaking():
|
||||||
|
if Engine.has_singleton("GodotTTS"):
|
||||||
|
return tts.is_speaking()
|
||||||
|
elif OS.has_feature('JavaScript'):
|
||||||
|
return JavaScript.eval("window.speechSynthesis.speaking")
|
||||||
|
elif tts != null:
|
||||||
|
return tts.is_speaking
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
var is_speaking setget , _get_is_speaking
|
||||||
|
|
||||||
|
|
||||||
|
func _get_can_detect_screen_reader():
|
||||||
|
if Engine.has_singleton("GodotTTS"):
|
||||||
|
return true
|
||||||
|
elif OS.has_feature('JavaScript'):
|
||||||
|
return false
|
||||||
|
elif tts != null:
|
||||||
|
return tts.can_detect_screen_reader
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
var can_detect_screen_reader setget , _get_can_detect_screen_reader
|
||||||
|
|
||||||
|
|
||||||
|
func _get_has_screen_reader():
|
||||||
|
if Engine.has_singleton("GodotTTS"):
|
||||||
|
return tts.has_screen_reader()
|
||||||
|
elif OS.has_feature('JavaScript'):
|
||||||
|
return false
|
||||||
|
elif tts != null:
|
||||||
|
return tts.has_screen_reader
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
var has_screen_reader setget , _get_has_screen_reader
|
||||||
|
|
||||||
|
|
||||||
func singular_or_plural(count, singular, plural):
|
func singular_or_plural(count, singular, plural):
|
||||||
if count == 1:
|
if count == 1:
|
||||||
return singular
|
return singular
|
||||||
else:
|
else:
|
||||||
return plural
|
return plural
|
||||||
|
|
||||||
|
|
||||||
|
func _on_utterance_begin(utterance):
|
||||||
|
emit_signal("utterance_begin", utterance)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_utterance_end(utterance):
|
||||||
|
emit_signal("utterance_end", utterance)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_utterance_stop(utterance):
|
||||||
|
emit_signal("utterance_stop", utterance)
|
||||||
|
|
||||||
|
|
||||||
|
func _exit_tree():
|
||||||
|
if not tts or not TTS:
|
||||||
|
return
|
||||||
|
tts.free()
|
||||||
|
|
41
build.gradle
Normal file
41
build.gradle
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath "com.android.tools.build:gradle:4.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
|
||||||
|
ext.pluginVersionCode = 1
|
||||||
|
ext.pluginVersionName = "0.6.0"
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 29
|
||||||
|
buildToolsVersion "29.0.3"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 21
|
||||||
|
targetSdkVersion 29
|
||||||
|
versionCode pluginVersionCode
|
||||||
|
versionName pluginVersionName
|
||||||
|
}
|
||||||
|
|
||||||
|
libraryVariants.all { variant ->
|
||||||
|
variant.outputs.all { output ->
|
||||||
|
output.outputFileName = "godot-tts.aar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly fileTree(dir: 'libs')
|
||||||
|
}
|
5
godot-tts.gdap
Normal file
5
godot-tts.gdap
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
[config]
|
||||||
|
|
||||||
|
name="GodotTTS"
|
||||||
|
binary_type="local"
|
||||||
|
binary="godot-tts.aar"
|
|
@ -1,15 +1,22 @@
|
||||||
[entry]
|
[entry]
|
||||||
|
|
||||||
|
Server.64="res://addons/godot-tts/target/debug/libgodot_tts.so"
|
||||||
Windows.64="res://addons/godot-tts/target/debug/godot_tts.dll"
|
Windows.64="res://addons/godot-tts/target/debug/godot_tts.dll"
|
||||||
|
Windows.32="res://addons/godot-tts/target/debug/32/godot_tts.dll"
|
||||||
|
UWP.64="res://addons/godot-tts/target/debug/godot_tts.dll"
|
||||||
X11.64="res://addons/godot-tts/target/debug/libgodot_tts.so"
|
X11.64="res://addons/godot-tts/target/debug/libgodot_tts.so"
|
||||||
|
X11.32="res://addons/godot-tts/target/debug/32/libgodot_tts.so"
|
||||||
|
OSX.64="res://addons/godot-tts/target/debug/libgodot_tts.dylib"
|
||||||
|
IOS="res://addons/godot-tts/target/debug/libgodot_tts.a"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
X11.64=[ ]
|
Windows.64=[ "res://addons/godot-tts/target/debug/nvdaControllerClient64.dll", "res://addons/godot-tts/target/debug/SAAPI64.dll" ]
|
||||||
|
Windows.32=[ "res://addons/godot-tts/target/debug/32/dolapi32.dll", "res://addons/godot-tts/target/debug/32/nvdaControllerClient32.dll", "res://addons/godot-tts/target/debug/32/SAAPI32.dll" ]
|
||||||
|
|
||||||
[general]
|
[general]
|
||||||
|
|
||||||
singleton=false
|
singleton=false
|
||||||
load_once=true
|
load_once=true
|
||||||
symbol_prefix="godot_"
|
symbol_prefix="godot_"
|
||||||
reloadable=true
|
reloadable=false
|
||||||
|
|
|
@ -1,15 +1,22 @@
|
||||||
[entry]
|
[entry]
|
||||||
|
|
||||||
|
Server.64="res://addons/godot-tts/target/release/libgodot_tts.so"
|
||||||
Windows.64="res://addons/godot-tts/target/release/godot_tts.dll"
|
Windows.64="res://addons/godot-tts/target/release/godot_tts.dll"
|
||||||
|
Windows.32="res://addons/godot-tts/target/release/32/godot_tts.dll"
|
||||||
|
UWP.64="res://addons/godot-tts/target/release/godot_tts.uwp.dll"
|
||||||
X11.64="res://addons/godot-tts/target/release/libgodot_tts.so"
|
X11.64="res://addons/godot-tts/target/release/libgodot_tts.so"
|
||||||
|
X11.32="res://addons/godot-tts/target/release/32/libgodot_tts.so"
|
||||||
|
OSX.64="res://addons/godot-tts/target/release/libgodot_tts.dylib"
|
||||||
|
IOS="res://addons/godot-tts/target/release/libgodot_tts.a"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
X11.64=[ ]
|
Windows.64=[ "res://addons/godot-tts/target/release/nvdaControllerClient64.dll", "res://addons/godot-tts/target/release/SAAPI64.dll" ]
|
||||||
|
Windows.32=[ "res://addons/godot-tts/target/release/32/dolapi32.dll", "res://addons/godot-tts/target/release/32/nvdaControllerClient32.dll", "res://addons/godot-tts/target/release/32/SAAPI32.dll" ]
|
||||||
|
|
||||||
[general]
|
[general]
|
||||||
|
|
||||||
singleton=false
|
singleton=false
|
||||||
load_once=true
|
load_once=true
|
||||||
symbol_prefix="godot_"
|
symbol_prefix="godot_"
|
||||||
reloadable=true
|
reloadable=false
|
||||||
|
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
234
gradlew
vendored
Executable file
234
gradlew
vendored
Executable file
|
@ -0,0 +1,234 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015-2021 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$( uname )" in #(
|
||||||
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
|
Darwin* ) darwin=true ;; #(
|
||||||
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
|
NONSTOP* ) nonstop=true ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
|
else
|
||||||
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD=java
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
|
# * args from the command line
|
||||||
|
# * the main class name
|
||||||
|
# * -classpath
|
||||||
|
# * -D...appname settings
|
||||||
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command;
|
||||||
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
|
# double quotes to make sure that they get re-expanded; and
|
||||||
|
# * put everything else in single quotes, so that it's not re-expanded.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
89
gradlew.bat
vendored
Normal file
89
gradlew.bat
vendored
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
BIN
libs/godot-lib.3.2.3.stable.release.aar
(Stored with Git LFS)
Normal file
BIN
libs/godot-lib.3.2.3.stable.release.aar
(Stored with Git LFS)
Normal file
Binary file not shown.
378
src/lib.rs
378
src/lib.rs
|
@ -1,87 +1,329 @@
|
||||||
use std::u8;
|
use std::sync::mpsc::{channel, Receiver};
|
||||||
|
|
||||||
use gdnative::*;
|
use gdnative::prelude::*;
|
||||||
use gdnative::init::*;
|
use tts::{Features, Tts, UtteranceId};
|
||||||
use tts::{Features, TTS as Tts};
|
|
||||||
|
|
||||||
struct TTS(Tts);
|
#[derive(NativeClass)]
|
||||||
|
struct Utterance(pub(crate) Option<UtteranceId>);
|
||||||
|
|
||||||
impl NativeClass for TTS {
|
#[methods]
|
||||||
type Base = Node;
|
impl Utterance {
|
||||||
type UserData = user_data::MutexData<TTS>;
|
fn new(_owner: &Reference) -> Self {
|
||||||
|
Self(None)
|
||||||
fn class_name() -> &'static str {
|
}
|
||||||
"TTS"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(owner: Self::Base) -> Self {
|
#[allow(clippy::enum_variant_names)]
|
||||||
Self::_init(owner)
|
enum Msg {
|
||||||
|
UtteranceBegin(UtteranceId),
|
||||||
|
UtteranceEnd(UtteranceId),
|
||||||
|
UtteranceStop(UtteranceId),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_properties(builder: &ClassBuilder<Self>) {
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
builder.add_property(Property {
|
#[derive(NativeClass)]
|
||||||
name: "rate",
|
#[inherit(Node)]
|
||||||
default: 50,
|
#[register_with(Self::register)]
|
||||||
hint: PropertyHint::Range {
|
struct TTS(Tts, Receiver<Msg>);
|
||||||
range: 0.0..100.0,
|
|
||||||
step: 1.,
|
|
||||||
slider: true,
|
|
||||||
},
|
|
||||||
getter: |this: &TTS| {
|
|
||||||
match this.0.get_rate() {
|
|
||||||
Ok(rate) => rate / u8::MAX * 100,
|
|
||||||
_ => 0,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setter: |this: &mut TTS, mut v: u8| {
|
|
||||||
if v > 100 {
|
|
||||||
v = 100;
|
|
||||||
}
|
|
||||||
let mut v = v as f32;
|
|
||||||
v = v * u8::MAX as f32 / 100.;
|
|
||||||
let Features {
|
|
||||||
rate: rate_supported, ..
|
|
||||||
} = this.0.supported_features();
|
|
||||||
if rate_supported {
|
|
||||||
this.0.set_rate(v as u8).unwrap();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
usage: PropertyUsage::DEFAULT,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[methods]
|
#[methods]
|
||||||
impl TTS {
|
impl TTS {
|
||||||
fn _init(_owner: gdnative::Node) -> Self {
|
fn new(owner: &Node) -> Self {
|
||||||
let tts = Tts::default().unwrap();
|
owner.set_pause_mode(2);
|
||||||
Self(tts)
|
let tts = Tts::default().expect("Failed to initialize TTS");
|
||||||
}
|
let (tx, rx) = channel();
|
||||||
|
|
||||||
#[export]
|
|
||||||
fn speak(&mut self, _owner: Node, message: GodotString, interrupt: bool) {
|
|
||||||
let message = message.to_string();
|
|
||||||
self.0.speak(message, interrupt).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[export]
|
|
||||||
fn stop(&mut self, _owner: Node) {
|
|
||||||
self.0.stop().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[export]
|
|
||||||
fn is_rate_supported(&mut self, _owner: Node) -> bool {
|
|
||||||
let Features {
|
let Features {
|
||||||
rate: rate_supported, ..
|
utterance_callbacks,
|
||||||
|
..
|
||||||
|
} = tts.supported_features();
|
||||||
|
if utterance_callbacks {
|
||||||
|
let tx_end = tx.clone();
|
||||||
|
let tx_stop = tx.clone();
|
||||||
|
tts.on_utterance_begin(Some(Box::new(move |utterance| {
|
||||||
|
tx.send(Msg::UtteranceBegin(utterance))
|
||||||
|
.expect("Failed to send UtteranceBegin");
|
||||||
|
})))
|
||||||
|
.expect("Failed to set utterance_begin callback");
|
||||||
|
tts.on_utterance_end(Some(Box::new(move |utterance| {
|
||||||
|
tx_end
|
||||||
|
.send(Msg::UtteranceEnd(utterance))
|
||||||
|
.expect("Failed to send UtteranceEnd");
|
||||||
|
})))
|
||||||
|
.expect("Failed to set utterance_end callback");
|
||||||
|
tts.on_utterance_stop(Some(Box::new(move |utterance| {
|
||||||
|
tx_stop
|
||||||
|
.send(Msg::UtteranceStop(utterance))
|
||||||
|
.expect("Failed to send UtteranceStop");
|
||||||
|
})))
|
||||||
|
.expect("Failed to set utterance_stop callback");
|
||||||
|
}
|
||||||
|
Self(tts, rx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register(builder: &ClassBuilder<Self>) {
|
||||||
|
builder
|
||||||
|
.property("volume")
|
||||||
|
.with_getter(|this: &TTS, _| match this.0.get_volume() {
|
||||||
|
Ok(volume) => volume,
|
||||||
|
_ => 0.,
|
||||||
|
})
|
||||||
|
.with_setter(|this: &mut TTS, _, v: f32| {
|
||||||
|
let Features {
|
||||||
|
volume: volume_supported,
|
||||||
|
..
|
||||||
|
} = this.0.supported_features();
|
||||||
|
if volume_supported {
|
||||||
|
let mut v = v;
|
||||||
|
if v < this.0.min_volume() {
|
||||||
|
v = this.0.min_volume();
|
||||||
|
} else if v > this.0.max_volume() {
|
||||||
|
v = this.0.max_volume();
|
||||||
|
}
|
||||||
|
this.0.set_volume(v).expect("Failed to set volume");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.done();
|
||||||
|
builder
|
||||||
|
.property("min_volume")
|
||||||
|
.with_getter(|this: &TTS, _| {
|
||||||
|
let Features {
|
||||||
|
volume: volume_supported,
|
||||||
|
..
|
||||||
|
} = this.0.supported_features();
|
||||||
|
if volume_supported {
|
||||||
|
this.0.min_volume()
|
||||||
|
} else {
|
||||||
|
0.
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.done();
|
||||||
|
builder
|
||||||
|
.property("max_volume")
|
||||||
|
.with_getter(|this: &TTS, _| {
|
||||||
|
let Features {
|
||||||
|
volume: volume_supported,
|
||||||
|
..
|
||||||
|
} = this.0.supported_features();
|
||||||
|
if volume_supported {
|
||||||
|
this.0.max_volume()
|
||||||
|
} else {
|
||||||
|
0.
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.done();
|
||||||
|
builder
|
||||||
|
.property("normal_volume")
|
||||||
|
.with_getter(|this: &TTS, _| {
|
||||||
|
let Features {
|
||||||
|
volume: volume_supported,
|
||||||
|
..
|
||||||
|
} = this.0.supported_features();
|
||||||
|
if volume_supported {
|
||||||
|
this.0.normal_volume()
|
||||||
|
} else {
|
||||||
|
0.
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.done();
|
||||||
|
builder
|
||||||
|
.property("rate")
|
||||||
|
.with_getter(|this: &TTS, _| match this.0.get_rate() {
|
||||||
|
Ok(rate) => rate,
|
||||||
|
_ => 0.,
|
||||||
|
})
|
||||||
|
.with_setter(|this: &mut TTS, _, v: f32| {
|
||||||
|
let Features {
|
||||||
|
rate: rate_supported,
|
||||||
|
..
|
||||||
|
} = this.0.supported_features();
|
||||||
|
if rate_supported {
|
||||||
|
let mut v = v;
|
||||||
|
if v < this.0.min_rate() {
|
||||||
|
v = this.0.min_rate();
|
||||||
|
} else if v > this.0.max_rate() {
|
||||||
|
v = this.0.max_rate();
|
||||||
|
}
|
||||||
|
this.0.set_rate(v).expect("Failed to set rate");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.done();
|
||||||
|
builder
|
||||||
|
.property("min_rate")
|
||||||
|
.with_getter(|this: &TTS, _| {
|
||||||
|
let Features {
|
||||||
|
rate: rate_supported,
|
||||||
|
..
|
||||||
|
} = this.0.supported_features();
|
||||||
|
if rate_supported {
|
||||||
|
this.0.min_rate()
|
||||||
|
} else {
|
||||||
|
0.
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.done();
|
||||||
|
builder
|
||||||
|
.property("max_rate")
|
||||||
|
.with_getter(|this: &TTS, _| {
|
||||||
|
let Features {
|
||||||
|
rate: rate_supported,
|
||||||
|
..
|
||||||
|
} = this.0.supported_features();
|
||||||
|
if rate_supported {
|
||||||
|
this.0.max_rate()
|
||||||
|
} else {
|
||||||
|
0.
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.done();
|
||||||
|
builder
|
||||||
|
.property("normal_rate")
|
||||||
|
.with_getter(|this: &TTS, _| {
|
||||||
|
let Features {
|
||||||
|
rate: rate_supported,
|
||||||
|
..
|
||||||
|
} = this.0.supported_features();
|
||||||
|
if rate_supported {
|
||||||
|
this.0.normal_rate()
|
||||||
|
} else {
|
||||||
|
0.
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.done();
|
||||||
|
builder
|
||||||
|
.property("can_detect_screen_reader")
|
||||||
|
.with_getter(|_: &TTS, _| cfg!(windows))
|
||||||
|
.done();
|
||||||
|
builder
|
||||||
|
.property("has_screen_reader")
|
||||||
|
.with_getter(|_, _| Tts::screen_reader_available())
|
||||||
|
.done();
|
||||||
|
builder
|
||||||
|
.property("can_detect_is_speaking")
|
||||||
|
.with_getter(|this: &TTS, _| {
|
||||||
|
let Features {
|
||||||
|
is_speaking: is_speaking_supported,
|
||||||
|
..
|
||||||
|
} = this.0.supported_features();
|
||||||
|
is_speaking_supported
|
||||||
|
})
|
||||||
|
.done();
|
||||||
|
builder
|
||||||
|
.property("is_speaking")
|
||||||
|
.with_getter(|this: &TTS, _| {
|
||||||
|
let Features {
|
||||||
|
is_speaking: is_speaking_supported,
|
||||||
|
..
|
||||||
|
} = this.0.supported_features();
|
||||||
|
if is_speaking_supported {
|
||||||
|
this.0
|
||||||
|
.is_speaking()
|
||||||
|
.expect("Failed to determine if speaking")
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.done();
|
||||||
|
builder
|
||||||
|
.signal("utterance_begin")
|
||||||
|
.with_param_custom(SignalParam {
|
||||||
|
name: "utterance".into(),
|
||||||
|
default: Variant::default(),
|
||||||
|
export_info: ExportInfo::new(VariantType::Object),
|
||||||
|
usage: PropertyUsage::DEFAULT,
|
||||||
|
})
|
||||||
|
.done();
|
||||||
|
builder
|
||||||
|
.signal("utterance_end")
|
||||||
|
.with_param_custom(SignalParam {
|
||||||
|
name: "utterance".into(),
|
||||||
|
default: Variant::default(),
|
||||||
|
export_info: ExportInfo::new(VariantType::Object),
|
||||||
|
usage: PropertyUsage::DEFAULT,
|
||||||
|
})
|
||||||
|
.done();
|
||||||
|
builder
|
||||||
|
.signal("utterance_stop")
|
||||||
|
.with_param_custom(SignalParam {
|
||||||
|
name: "utterance".into(),
|
||||||
|
default: Variant::default(),
|
||||||
|
export_info: ExportInfo::new(VariantType::Object),
|
||||||
|
usage: PropertyUsage::DEFAULT,
|
||||||
|
})
|
||||||
|
.done();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[method]
|
||||||
|
fn speak(&mut self, message: String, interrupt: bool) -> Variant {
|
||||||
|
if let Ok(id) = self.0.speak(message, interrupt) {
|
||||||
|
let utterance: Instance<Utterance, Unique> = Instance::new();
|
||||||
|
if id.is_some() {
|
||||||
|
utterance
|
||||||
|
.map_mut(|u, _| u.0 = id)
|
||||||
|
.expect("Failed to set utterance ID");
|
||||||
|
}
|
||||||
|
utterance.owned_to_variant()
|
||||||
|
} else {
|
||||||
|
Variant::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[method]
|
||||||
|
fn stop(&mut self) {
|
||||||
|
self.0.stop().expect("Failed to stop");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[method]
|
||||||
|
fn is_rate_supported(&mut self) -> bool {
|
||||||
|
let Features {
|
||||||
|
rate: rate_supported,
|
||||||
|
..
|
||||||
} = self.0.supported_features();
|
} = self.0.supported_features();
|
||||||
rate_supported
|
rate_supported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[method]
|
||||||
|
fn are_utterance_callbacks_supported(&mut self) -> bool {
|
||||||
|
let Features {
|
||||||
|
utterance_callbacks: supported,
|
||||||
|
..
|
||||||
|
} = self.0.supported_features();
|
||||||
|
supported
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(handle: gdnative::init::InitHandle) {
|
#[method]
|
||||||
|
fn _process(&mut self, #[base] base: &Node, _delta: f32) {
|
||||||
|
if let Ok(msg) = self.1.try_recv() {
|
||||||
|
match msg {
|
||||||
|
Msg::UtteranceBegin(utterance_id) => {
|
||||||
|
let utterance: Instance<Utterance, Unique> = Instance::new();
|
||||||
|
utterance
|
||||||
|
.map_mut(|u, _| u.0 = Some(utterance_id))
|
||||||
|
.expect("Failed to set utterance ID");
|
||||||
|
base.emit_signal("utterance_begin", &[utterance.owned_to_variant()]);
|
||||||
|
}
|
||||||
|
Msg::UtteranceEnd(utterance_id) => {
|
||||||
|
let utterance: Instance<Utterance, Unique> = Instance::new();
|
||||||
|
utterance
|
||||||
|
.map_mut(|u, _| u.0 = Some(utterance_id))
|
||||||
|
.expect("Failed to set utterance ID");
|
||||||
|
base.emit_signal("utterance_end", &[utterance.owned_to_variant()]);
|
||||||
|
}
|
||||||
|
Msg::UtteranceStop(utterance_id) => {
|
||||||
|
let utterance: Instance<Utterance, Unique> = Instance::new();
|
||||||
|
utterance
|
||||||
|
.map_mut(|u, _| u.0 = Some(utterance_id))
|
||||||
|
.expect("Failed to set utterance ID");
|
||||||
|
base.emit_signal("utterance_stop", &[utterance.owned_to_variant()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(handle: InitHandle) {
|
||||||
|
env_logger::init();
|
||||||
|
handle.add_tool_class::<Utterance>();
|
||||||
handle.add_class::<TTS>();
|
handle.add_class::<TTS>();
|
||||||
}
|
}
|
||||||
|
|
||||||
godot_gdnative_init!();
|
godot_init!(init);
|
||||||
godot_nativescript_init!(init);
|
|
||||||
godot_gdnative_terminate!();
|
|
||||||
|
|
6
src/main/AndroidManifest.xml
Normal file
6
src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="games.lightsout.godot.tts">
|
||||||
|
<application>
|
||||||
|
<meta-data android:name="org.godotengine.plugin.v1.GodotTTS" android:value="games.lightsout.godot.tts.TTS" />
|
||||||
|
</application>
|
||||||
|
</manifest>
|
123
src/main/java/games/lightsout/godot/tts/TTS.java
Normal file
123
src/main/java/games/lightsout/godot/tts/TTS.java
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
package games.lightsout.godot.tts;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.godotengine.godot.Godot;
|
||||||
|
import org.godotengine.godot.plugin.GodotPlugin;
|
||||||
|
import org.godotengine.godot.plugin.SignalInfo;
|
||||||
|
|
||||||
|
import android.accessibilityservice.AccessibilityServiceInfo;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.speech.tts.TextToSpeech;
|
||||||
|
import android.speech.tts.UtteranceProgressListener;
|
||||||
|
import android.view.accessibility.AccessibilityManager;
|
||||||
|
|
||||||
|
public class TTS extends GodotPlugin implements TextToSpeech.OnInitListener {
|
||||||
|
private TextToSpeech tts = null;
|
||||||
|
|
||||||
|
private float volume = 1f;
|
||||||
|
private float rate = 1f;
|
||||||
|
|
||||||
|
private Integer utteranceId = 0;
|
||||||
|
|
||||||
|
public int speak(String text, boolean interrupt) {
|
||||||
|
int mode = TextToSpeech.QUEUE_ADD;
|
||||||
|
if (interrupt)
|
||||||
|
mode = TextToSpeech.QUEUE_FLUSH;
|
||||||
|
tts.speak(text, mode, null, this.utteranceId.toString());
|
||||||
|
int rv = this.utteranceId.intValue();
|
||||||
|
this.utteranceId++;
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
tts.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public float get_volume() {
|
||||||
|
return this.volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set_volume(float volume) {
|
||||||
|
this.volume = volume;
|
||||||
|
tts.Engine.KEY_PARAM_VOLUME = volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float get_rate() {
|
||||||
|
return this.rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set_rate(float rate) {
|
||||||
|
this.rate = rate;
|
||||||
|
tts.setSpeechRate(rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean is_speaking() {
|
||||||
|
return tts.isSpeaking();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean has_screen_reader() {
|
||||||
|
AccessibilityManager accessibilityManager = (AccessibilityManager) getActivity()
|
||||||
|
.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
||||||
|
if (accessibilityManager != null) {
|
||||||
|
List<AccessibilityServiceInfo> screenReaders = accessibilityManager
|
||||||
|
.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
|
||||||
|
return screenReaders.size() != 0;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Listener extends UtteranceProgressListener {
|
||||||
|
public void onStart(String utteranceId) {
|
||||||
|
Integer id = Integer.parseInt(utteranceId);
|
||||||
|
TTS.this.emitSignal("utterance_begin", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onStop(String utteranceId, Boolean interrupted) {
|
||||||
|
Integer id = Integer.parseInt(utteranceId);
|
||||||
|
TTS.this.emitSignal("utterance_stop", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onDone(String utteranceId) {
|
||||||
|
Integer id = Integer.parseInt(utteranceId);
|
||||||
|
TTS.this.emitSignal("utterance_end", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onError(String utteranceId) {
|
||||||
|
Integer id = Integer.parseInt(utteranceId);
|
||||||
|
TTS.this.emitSignal("utterance_end", id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TTS(Godot godot) {
|
||||||
|
super(godot);
|
||||||
|
this.tts = new TextToSpeech(this.getActivity(), this);
|
||||||
|
tts.setOnUtteranceProgressListener(new Listener());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPluginName() {
|
||||||
|
return "GodotTTS";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getPluginMethods() {
|
||||||
|
return Arrays.asList("speak", "stop", "get_rate", "set_rate", "has_screen_reader", "is_speaking");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<SignalInfo> getPluginSignals() {
|
||||||
|
SignalInfo begin = new SignalInfo("utterance_begin", Integer.class);
|
||||||
|
SignalInfo end = new SignalInfo("utterance_end", Integer.class);
|
||||||
|
SignalInfo stop = new SignalInfo("utterance_stop", Integer.class);
|
||||||
|
return new HashSet(Arrays.asList(begin, end, stop));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onInit(int status) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user