Compare commits

..

101 Commits

Author SHA1 Message Date
35afabbdca Upgrade Gradle. 2023-04-26 09:55:33 -05:00
e331676855
Merge pull request #13 from retrohub-org/contribution_32-bits-ci
Add 32 bits builds in CI
2023-04-26 09:53:01 -05:00
c92135d452 Merge branch 'master' into pr/dylanjaide/7 2023-04-26 09:48:23 -05:00
Ricardo Subtil
1d963924ce Add 32 bits builds in CI 2023-03-18 15:43:44 +00:00
5abf6317ef Remove missing features. 2022-12-16 10:46:29 -06:00
d83b3689f5 Simplify screen reader detection code to use Tts APIs. 2022-12-16 10:24:32 -06:00
2dba6d0a55 Revert "Test on ubuntu-20.04 for older libc releases."
Ubuntu 20.04 doesn't appear to be supported by the Godot Rust bindings
due to only shipping glibc 2.31 when 2.32 is the minimum required
version.

This reverts commit 6d48c23e3e.
2022-12-12 08:18:06 -06:00
6d48c23e3e Test on ubuntu-20.04 for older libc releases. 2022-12-12 07:45:08 -06:00
f21d3357e9 Update dependencies from most recent work. 2022-12-07 09:39:52 -06:00
dba2d061ac Bump dependencies and upgrade to gdnative-rust 0.11. 2022-12-06 09:57:47 -06:00
Dylan Jaide White
bf2afec4b3 Best guess at Android implementation 2022-03-12 21:25:53 +00:00
Dylan Jaide White
2537d27886 Add Rust implementation of volume 2022-03-12 20:42:20 +00:00
Dylan Jaide White
8d95b3b5de Initial test of adding volume, javascript only for now 2022-03-12 18:04:53 +00:00
1c7686d09d Bump dependency. 2021-03-11 14:30:14 -06:00
b43b4fe19b Refactor to tolk feature. 2021-01-21 11:26:59 -06:00
5c1cf59d98 Bump dependency. 2021-01-18 11:10:43 -06:00
c8aabb02ad Bump dependencies. 2020-12-07 23:37:04 -06:00
58dfb757c4 Rustup is failing for some reason. 2020-12-03 15:07:14 -06:00
f800aebb14 Bump dependency. 2020-12-03 13:52:49 -06:00
890e32df80
Merge pull request #3 from metinc/patch-1
Add a short usage description.
2020-11-25 10:31:05 -06:00
d9d135d530 Bump dependency. 2020-11-25 10:25:14 -06:00
65311abdeb Remove unwrap in favor of expect. 2020-11-24 12:20:02 -06:00
Metin Celik
de6da09431
Add a short usage description. 2020-11-21 10:21:33 +01:00
691997023a Bump dependency version. 2020-11-18 08:40:21 -06:00
30b27c542e If removing quotes doesn't work, then I got nothing. 2020-11-17 22:07:55 -06:00
fbdc2a7724 I keep forgetting this is Powershell. 2020-11-17 21:56:19 -06:00
e8dfbb6a69 Escape and quote. 2020-11-17 20:22:01 -06:00
e2c1bfa7c9 Eliminate Clippy warnings. 2020-11-17 19:43:57 -06:00
b34c880a36 Switch to Rust GitHub actions. 2020-11-17 19:27:40 -06:00
e2c162c547 Cache Gradle artifacts in release builds. 2020-11-17 13:32:42 -06:00
413b68be63 Cache Gradle artifacts. 2020-11-17 13:24:58 -06:00
3ff48e2601 Quiet LLVM installation on release. 2020-11-17 13:17:17 -06:00
a0cc984fa1 Add caching for release builds. 2020-11-17 13:12:28 -06:00
ac0425b652 Set up environment variable for release. 2020-11-17 12:41:18 -06:00
140b80738b Introduce caching. 2020-11-17 12:39:51 -06:00
47782166d3 Tweak environment variable and try to install quietly. 2020-11-17 12:28:08 -06:00
55eea365c4 Not sure why we suddenly need this. 2020-11-17 12:10:21 -06:00
e21edf0c31 Bump dependency. 2020-11-17 10:54:15 -06:00
5529358651 Bump dependency. 2020-11-11 10:39:17 -06:00
3639e98f65 Bump dependency. 2020-11-03 12:15:24 -06:00
32c20ddf90 Bump tts-rs version. 2020-11-02 13:56:35 -06:00
760a7745f1 Bump dependency. 2020-11-02 11:38:34 -06:00
f759a67507 Upgrade env_logger. 2020-10-30 10:49:15 -05:00
7c25c6ad0e Bump crate version to fix WinRT bug. 2020-10-30 10:48:28 -05:00
d58bb881e5 Fix Android initialization. 2020-10-14 13:15:03 -05:00
0abf1ffcdd Update instructions for Godot 3.2.3 Android custom template changes. 2020-10-14 10:37:50 -05:00
893fae0c99 rm rm 2020-10-09 11:15:52 -05:00
93432f2a44 Fix broken setup of gdnlib file for release. 2020-10-09 10:23:01 -05:00
e483f3467b Add Utterance as tool script to work around https://github.com/godot-rust/godot-rust/issues/614. 2020-10-09 09:35:55 -05:00
8ed356fd47 Update .gitignore. 2020-10-09 07:16:58 -05:00
e19d154b99 Add support for separating Tolk dependency for use on UWP. 2020-10-08 20:44:05 -05:00
f7a2ac7760 Reference separate UWP DLL. 2020-10-08 20:31:56 -05:00
3c2e2220b0 Build separate UWP DLL. 2020-10-08 20:28:19 -05:00
17220b4634 Use cargo check and build with --all-features under Windows. 2020-10-08 20:18:45 -05:00
9243ea4e7f Implement utterance_stop signal on Android. 2020-10-08 11:10:16 -05:00
f105ee702b Statically link MSVC runtime on Windows. 2020-10-08 10:40:36 -05:00
1dabd5afd3 Implement stop signals. 2020-10-08 08:56:56 -05:00
2c73b45300 Bump tts dependency. 2020-10-08 08:46:38 -05:00
69b5c39f66 Eliminate Option in return. 2020-09-30 16:03:04 -05:00
ff247a3f19 Only set an utterance ID on the returned variant if we have one. 2020-09-30 15:25:59 -05:00
ed7f3920f6 Add signal support to Android. 2020-09-30 15:13:17 -05:00
41c4204224 Bump Android dependency to Godot 3.2.3. 2020-09-30 13:50:51 -05:00
c7563fa997 Pass utterance IDs into signals, and return an utterance ID when speaking. 2020-09-30 13:45:20 -05:00
68d1d9e011 WIP: Try passing in utterance to signals. 2020-09-30 11:57:50 -05:00
a71bf417b6 Bridge TTS callbacks to signals. 2020-09-29 11:41:00 -05:00
0c9efa5c7b Minor convention cleanup. 2020-09-29 10:56:32 -05:00
abe9ab3f55 Add property indicating whether utterance callbacks are supported. 2020-09-29 10:53:35 -05:00
795cc4790c Add utterance_begin and utterance_end signals.
Unfortunately, I can't figure out how to pass the utterance ID to these signals because I don't see that the GDNative Rust bindings support objects of one type creating objects of another (I.e. nodes creating references.) If I'm wrong, PRs or suggestions welcome.
2020-09-29 10:44:52 -05:00
cb7eda45e2 Pin minimum tts version to work around Tolk string-handling issue. 2020-09-21 15:19:36 -05:00
ddfc262278 We do, in fact, seem to need 0.9.0-preview.0.
This resolves the previously-noticed bindgen issue, where gdnative and speech-dispatcher require conflicting versions.

The early iOS build failures appear due to attempting to link AppKit on iOS, where it doesn't exist. This seemed to work on Rust 1.45, but now fails on 1.46.

Hopefully, this plus tts-rs changes get this building under iOS again.
2020-09-02 17:49:37 -05:00
2222e3eda7 Separate out iOS builds, and ensure Rust is up-to-date. 2020-09-02 17:05:35 -05:00
54222b1fcb Ugh, off my game today. For some reason, the previous revert was impartial. 2020-09-02 14:55:20 -05:00
4990454320 Revert "Bump to 0.9.0 preview API for godot-rust."
I'd initially done this because 0.9 uses a newer bindgen and, for some reason, I can't build this with a local tts-rs under Windows. This, despite bindgen only being a Linux dependency. Seems to work fine with the dependency from crates.io, though.

0.9 also appears to be broken on MacOS/iOS targets.

Apologies for the impending force-push to update the release tag.
This reverts commit 6bbddcc6dd.
2020-09-02 14:34:13 -05:00
8732269699 I thought, if the path wasn't found, then crates.io would be the fallback. Apparently not. 2020-09-02 13:39:51 -05:00
6bbddcc6dd Bump to 0.9.0 preview API for godot-rust. 2020-09-02 13:19:25 -05:00
00aadde492 README polish. 2020-09-02 12:26:28 -05:00
30eacee6ff Use actual normal_rate getter so we don't repeat ourselves. 2020-08-27 15:31:31 -05:00
274902432c Fix JavaScript rate issues.
* Replace ints with floats when non-zero. Not sure if this is explicitly necessary, but at least it makes the type more obvious.
* Set initial JavaScript rate to 1.0, not 50. 50 was a remnant of the rate being a percentage, rather than a value between platform-specific minimums and maximums.
2020-08-27 15:27:50 -05:00
afdb73991f Add dependencies to debug gdnlib so debug exports work. 2020-08-25 12:41:04 -05:00
b556c4d2f0 Fix cut-and-paste error on iOS release action. 2020-08-21 11:16:18 -05:00
f9d485faab Fix broken Android singleton names. 2020-08-21 10:31:59 -05:00
f0834feea5 Changes necessary to get working on Android. 2020-08-18 17:23:05 -05:00
c5f8e28028 Add iOS libraries to Godot configuration. 2020-08-18 16:51:49 -05:00
550bb82c89 Add iOS test and release builds. 2020-08-18 15:59:27 -05:00
591e9a26c1 Add staticlib crate type for iOS. 2020-08-18 15:54:58 -05:00
20abaa45ab Bump version. 2020-08-18 15:51:36 -05:00
d3b90f48e7 Use deprecated API so compatibility down to 18 can be maintained. 2020-08-18 13:50:32 -05:00
971f0afbfd Filename consistency. 2020-08-18 11:02:02 -05:00
4ff4d26c77 Add Android plugin configuration. 2020-08-18 10:48:24 -05:00
3f611f18b8 Update release and test builds to use LFS, and to incorporate new Android build process. 2020-08-18 10:48:04 -05:00
705126ef2f Merge branch 'master' into v0.6 2020-08-18 10:34:08 -05:00
e05cdde57d Add Android build action. 2020-08-18 10:30:30 -05:00
5395256385 Switch to Gradle for builds. 2020-08-18 10:28:53 -05:00
30a82fc6b0 WIP: Try downloading Godot AAR. 2020-08-14 09:43:09 -05:00
6fd18b0de1 Update release build to include MacOS, and to package README.md. 2020-08-14 08:37:11 -05:00
8dfa941ba6 Remove unnecessary logging. 2020-08-13 12:56:14 -05:00
f389e818b9 Bump dependency. 2020-08-13 12:12:48 -05:00
8c26502f25 Add OSX library to gdnlib files. 2020-08-12 18:59:03 -05:00
1eca767056 Add MacOS test build. 2020-08-12 16:34:49 -05:00
bfdedf64db Bump dependency. 2020-08-12 16:31:39 -05:00
e0f419aaf6 Add Bazel build for standalone Android plugin. 2020-07-09 12:32:14 -05:00
21 changed files with 1269 additions and 370 deletions

2
.cargo/config Normal file
View File

@ -0,0 +1,2 @@
[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static"]

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.aar filter=lfs diff=lfs merge=lfs -text

View File

@ -2,27 +2,54 @@ name: Build
on: on:
push: push:
branches: [ master ]
pull_request: pull_request:
branches: [ master ]
jobs: 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_linux: build_android:
name: Build Linux name: Build Android
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- run: | with:
sudo apt-get update lfs: true
sudo apt-get install -y libspeechd-dev - uses: actions/cache@v2
cargo build --release --verbose with:
path: ~/.gradle/caches
build_windows: key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
name: Build Windows restore-keys: ${{ runner.os }}-gradle
runs-on: windows-latest - run: |
steps: ./gradlew assemble
- uses: actions/checkout@v2
- run: |
choco install -y llvm
cargo build --release --verbose

View File

@ -3,78 +3,223 @@ name: Release
on: on:
push: push:
tags: tags:
- 'v*' - "v*"
jobs: jobs:
build_linux: build_linux:
name: Build Linux name: Build Linux
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- run: | - uses: Swatinem/rust-cache@v1
sudo apt-get update - run: |
sudo apt-get install -y libspeechd-dev sudo apt-get update
cargo build --release --verbose sudo apt-get install -y libspeechd-dev
mv target linux cargo build --release
- uses: actions/upload-artifact@v1 mkdir linux
with: mv target/release/*.so linux
name: linux - uses: actions/upload-artifact@v1
path: linux 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: build_windows:
name: Build Windows name: Build Windows
runs-on: windows-latest runs-on: windows-latest
env:
LIBCLANG_PATH: c:\program files\llvm\bin
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- run: | - uses: Swatinem/rust-cache@v1
choco install -y llvm - run: |
cargo build --release --verbose choco install -qy llvm
move target windows cargo build --release
- uses: actions/upload-artifact@v1 mkdir windows
with: move target\release\*.dll windows
name: windows - uses: actions/upload-artifact@v1
path: windows 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: package:
name: Package name: Package
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [build_linux, build_windows] needs:
[
build_linux,
build_linux_32,
build_windows,
build_windows_32,
build_uwp,
build_macos,
build_android,
build_ios,
]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/download-artifact@v1 - uses: actions/download-artifact@v1
with: with:
name: linux name: linux
- uses: actions/download-artifact@v1 - uses: actions/download-artifact@v1
with: with:
name: windows name: linux-32
- run: | - uses: actions/download-artifact@v1
mkdir godot-tts with:
cp godot-tts.gdnlib.release godot-tts/godot-tts.gdnlib name: windows
mkdir -p godot-tts/target/release - uses: actions/download-artifact@v1
cp linux/release/*.so godot-tts/target/release with:
cp windows/release/*.dll godot-tts/target/release name: windows-32
cp LICENSE godot-tts - uses: actions/download-artifact@v1
cp TTS.gd godot-tts.g* godot-tts with:
cp godot-tts.gdnlib.release godot-tts/godot-tts.gdnlib name: uwp
rm godot-tts/*.release - uses: actions/download-artifact@v1
cp -R android godot-tts with:
zip -r9 godot-tts godot-tts name: macos
- uses: actions/create-release@v1 - uses: actions/download-artifact@v1
id: create_release with:
env: name: android
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: actions/download-artifact@v1
with: with:
tag_name: ${{ github.ref }} name: ios
release_name: Release ${{ github.ref }} - run: |
draft: false mkdir -p godot-tts/target/release/32
prerelease: false cp linux/*.so godot-tts/target/release
- uses: actions/upload-release-asset@v1 cp linux-32/*.so godot-tts/target/release/32
id: upload-release-asset cp windows/*.dll godot-tts/target/release
env: cp windows-32/*.dll godot-tts/target/release/32
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} cp uwp/*.dll godot-tts/target/release
with: cp macos/*.dylib godot-tts/target/release
upload_url: ${{ steps.create_release.outputs.upload_url }} cp android/outputs/aar/godot-tts.aar godot-tts/
asset_path: ./godot-tts.zip cp ios/*.a godot-tts/target/release
asset_name: godot-tts.zip cp LICENSE godot-tts
asset_content_type: application/zip 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

5
.gitignore vendored
View File

@ -5,3 +5,8 @@ bazel-bin
bazel-godot-tts bazel-godot-tts
bazel-out bazel-out
bazel-testlogs bazel-testlogs
build
.gradle
.classpath
.project
.settings

View File

@ -5,12 +5,9 @@ authors = ["Nolan Darilek <nolan@thewordnerd.info>"]
edition = "2018" edition = "2018"
[lib] [lib]
crate-type = ["cdylib"] crate-type = ["staticlib", "cdylib"]
[dependencies] [dependencies]
env_logger = "0.7" env_logger = "0.10"
gdnative = "0.8" gdnative = "0.11"
tts = "0.3" tts = { version = "0.25", features = ["tolk"] }
[target.'cfg(windows)'.dependencies]
tolk = "0.2"

View File

@ -7,11 +7,13 @@ This addon was primarily developed for the [Godot Accessibility addon](https://g
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: 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 * Windows
* Screen readers via [Tolk](https://github.com/dkager/tolk/) * Screen readers/SAPI via Tolk (requires `use_tolk` Cargo feature)
* Native WinRT * WinRT
* Linux via [Speech Dispatcher](https://freebsoft.org/speechd) * Linux via [Speech Dispatcher](https://freebsoft.org/speechd)
* HTML 5 * MacOS
* Android * AppKit on MacOS 10.13 and below
* AVFoundation on MacOS 10.14 and above, and iOS
* Web
## Features ## Features
@ -31,6 +33,32 @@ The public-facing API is contained entirely within [TTS.GD](https://github.com/l
Download the [latest release](https://github.com/lightsoutgames/godot-tts/releases). 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 ## 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: 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:

163
TTS.gd
View File

@ -1,33 +1,110 @@
tool tool
extends Node extends Node
signal utterance_begin(utterance)
signal utterance_end(utterance)
signal utterance_stop(utterance)
var TTS var TTS
var tts var tts
signal done
func _init(): func _init():
if OS.get_name() == "Server" or OS.has_feature("JavaScript"): if OS.get_name() == "Server" or OS.has_feature("JavaScript"):
return return
elif Engine.has_singleton("AndroidTTS"): elif Engine.has_singleton("GodotTTS"):
tts = Engine.get_singleton("AndroidTTS") tts = Engine.get_singleton("GodotTTS")
else: else:
TTS = preload("godot-tts.gdns") TTS = preload("godot-tts.gdns")
if TTS and (TTS.can_instance() or Engine.editor_hint): 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 _ready(): func _ready():
pause_mode = Node.PAUSE_MODE_PROCESS pause_mode = Node.PAUSE_MODE_PROCESS
func _get_min_volume():
if OS.has_feature('JavaScript'):
return 0
else:
return 0
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(): func _get_min_rate():
if OS.has_feature('JavaScript'): if OS.has_feature('JavaScript'):
return 0.1 return 0.1
elif Engine.has_singleton("AndroidTTS"): elif Engine.has_singleton("GodotTTS"):
return 0.1 return 0.1
elif tts != null: elif tts != null:
return tts.min_rate return tts.min_rate
@ -40,8 +117,8 @@ var min_rate setget , _get_min_rate
func _get_max_rate(): func _get_max_rate():
if OS.has_feature('JavaScript'): if OS.has_feature('JavaScript'):
return 10 return 10.0
elif Engine.has_singleton("AndroidTTS"): elif Engine.has_singleton("GodotTTS"):
return 10.0 return 10.0
elif tts != null: elif tts != null:
return tts.max_rate return tts.max_rate
@ -54,8 +131,8 @@ var max_rate setget , _get_max_rate
func _get_normal_rate(): func _get_normal_rate():
if OS.has_feature('JavaScript'): if OS.has_feature('JavaScript'):
return 1 return 1.0
elif Engine.has_singleton("AndroidTTS"): elif Engine.has_singleton("GodotTTS"):
return 1.0 return 1.0
elif tts != null: elif tts != null:
return tts.normal_rate return tts.normal_rate
@ -65,7 +142,7 @@ func _get_normal_rate():
var normal_rate setget , _get_normal_rate var normal_rate setget , _get_normal_rate
var javascript_rate = 50 var javascript_rate = self.normal_rate
func _set_rate(rate): func _set_rate(rate):
@ -73,7 +150,7 @@ func _set_rate(rate):
rate = self.min_rate rate = self.min_rate
elif rate > self.max_rate: elif rate > self.max_rate:
rate = self.max_rate rate = self.max_rate
if Engine.has_singleton("AndroidTTS"): if Engine.has_singleton("GodotTTS"):
return tts.set_rate(rate) return tts.set_rate(rate)
elif tts != null: elif tts != null:
tts.rate = rate tts.rate = rate
@ -82,7 +159,7 @@ func _set_rate(rate):
func _get_rate(): func _get_rate():
if Engine.has_singleton("AndroidTTS"): if Engine.has_singleton("GodotTTS"):
return tts.get_rate() return tts.get_rate()
elif tts != null: elif tts != null:
return tts.rate return tts.rate
@ -114,24 +191,27 @@ var normal_rate_percentage setget , _get_rate_percentage
func speak(text, interrupt := true): func speak(text, interrupt := true):
var utterance
if tts != null: if tts != null:
tts.speak(text, interrupt) utterance = tts.speak(text, interrupt)
elif OS.has_feature('JavaScript'): elif OS.has_feature('JavaScript'):
var code = ( var code = (
""" """
let utterance = new SpeechSynthesisUtterance("%s") let utterance = new SpeechSynthesisUtterance("%s")
utterance.rate = %s utterance.rate = %s
""" utterance.volume = %s
% [text.replace("\n", " "), javascript_rate] """
% [text.replace("\n", " "), javascript_rate, javascript_volume]
) )
if interrupt: if interrupt:
code += """ code += """
window.speechSynthesis.cancel() window.speechSynthesis.cancel()
""" """
code += "window.speechSynthesis.speak(utterance)" code += "window.speechSynthesis.speak(utterance)"
JavaScript.eval(code) JavaScript.eval(code)
else: else:
print_debug("%s: %s" % [text, interrupt]) print_debug("%s: %s" % [text, interrupt])
return utterance
func stop(): func stop():
@ -141,8 +221,8 @@ func stop():
JavaScript.eval("window.speechSynthesis.cancel()") JavaScript.eval("window.speechSynthesis.cancel()")
func get_is_rate_supported(): func _get_is_rate_supported():
if Engine.has_singleton("AndroidTTS"): if Engine.has_singleton("GodotTTS"):
return true return true
elif OS.has_feature('JavaScript'): elif OS.has_feature('JavaScript'):
return true return true
@ -152,11 +232,25 @@ func get_is_rate_supported():
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(): func _get_can_detect_is_speaking():
if Engine.has_singleton("AndroidTTS"): if Engine.has_singleton("GodotTTS"):
return true return true
elif OS.has_feature('JavaScript'): elif OS.has_feature('JavaScript'):
return true return true
@ -169,7 +263,7 @@ var can_detect_is_speaking setget , _get_can_detect_is_speaking
func _get_is_speaking(): func _get_is_speaking():
if Engine.has_singleton("AndroidTTS"): if Engine.has_singleton("GodotTTS"):
return tts.is_speaking() return tts.is_speaking()
elif OS.has_feature('JavaScript'): elif OS.has_feature('JavaScript'):
return JavaScript.eval("window.speechSynthesis.speaking") return JavaScript.eval("window.speechSynthesis.speaking")
@ -182,7 +276,7 @@ var is_speaking setget , _get_is_speaking
func _get_can_detect_screen_reader(): func _get_can_detect_screen_reader():
if Engine.has_singleton("AndroidTTS"): if Engine.has_singleton("GodotTTS"):
return true return true
elif OS.has_feature('JavaScript'): elif OS.has_feature('JavaScript'):
return false return false
@ -195,7 +289,7 @@ var can_detect_screen_reader setget , _get_can_detect_screen_reader
func _get_has_screen_reader(): func _get_has_screen_reader():
if Engine.has_singleton("AndroidTTS"): if Engine.has_singleton("GodotTTS"):
return tts.has_screen_reader() return tts.has_screen_reader()
elif OS.has_feature('JavaScript'): elif OS.has_feature('JavaScript'):
return false return false
@ -214,17 +308,16 @@ func singular_or_plural(count, singular, plural):
return plural return plural
var _was_speaking = false func _on_utterance_begin(utterance):
emit_signal("utterance_begin", utterance)
func _process(delta): func _on_utterance_end(utterance):
if self.is_speaking: emit_signal("utterance_end", utterance)
print("xxx Speaking")
_was_speaking = true
elif _was_speaking: func _on_utterance_stop(utterance):
print("xxx Done") emit_signal("utterance_stop", utterance)
emit_signal("done")
_was_speaking = false
func _exit_tree(): func _exit_tree():

View File

@ -1,91 +0,0 @@
package games.lightsout.godot.tts;
import java.util.List;
import org.godotengine.godot.Godot;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
import android.content.Context;
import android.speech.tts.TextToSpeech;
import android.view.accessibility.AccessibilityManager;
public class TTS extends Godot.SingletonBase implements TextToSpeech.OnInitListener {
protected Activity appActivity;
protected Context appContext;
private Godot activity = null;
private int instanceId = 0;
private TextToSpeech tts = null;
private float rate = 1f;
private Integer utteranceId = 0;
public void speak(String text, boolean interrupt) {
int mode = TextToSpeech.QUEUE_ADD;
if (interrupt)
mode = TextToSpeech.QUEUE_FLUSH;
tts.speak(text, mode, null, this.utteranceId.toString());
this.utteranceId++;
}
public void stop() {
tts.stop();
}
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) appContext
.getSystemService(Context.ACCESSIBILITY_SERVICE);
if (accessibilityManager != null) {
List<AccessibilityServiceInfo> screenReaders = accessibilityManager
.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
return screenReaders.size() != 0;
} else {
return false;
}
}
public void getInstanceId(int pInstanceId) {
// You will need to call this method from Godot and pass in the
// get_instance_id().
instanceId = pInstanceId;
}
static public Godot.SingletonBase initialize(Activity p_activity) {
return new TTS(p_activity);
}
public TTS(Activity p_activity) {
this.activity = (Godot) p_activity;
this.appActivity = p_activity;
this.appContext = appActivity.getApplicationContext();
this.tts = new TextToSpeech(this.appContext, this);
// Register class name and functions to bind.
registerClass("AndroidTTS", new String[] { "speak", "stop", "get_rate", "set_rate", "has_screen_reader",
"is_speaking", "getInstanceId" });
this.activity.runOnUiThread(new Runnable() {
public void run() {
}
});
}
public void onInit(int status) {
}
}

41
build.gradle Normal file
View 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
View File

@ -0,0 +1,5 @@
[config]
name="GodotTTS"
binary_type="local"
binary="godot-tts.aar"

View File

@ -2,12 +2,17 @@
Server.64="res://addons/godot-tts/target/debug/libgodot_tts.so" 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" 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]

View File

@ -2,13 +2,17 @@
Server.64="res://addons/godot-tts/target/release/libgodot_tts.so" 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"
UWP.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]
Windows.64=["res://addons/godot-tts/target/release/nvdaControllerClient64.dll", "res://addons/godot-tts/target/release/SAAPI64.dll"] Windows.64=[ "res://addons/godot-tts/target/release/nvdaControllerClient64.dll", "res://addons/godot-tts/target/release/SAAPI64.dll" ]
X11.64=[ ] 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]

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View 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
View 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
View 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

Binary file not shown.

View File

@ -1,152 +1,329 @@
use gdnative::init::*; use std::sync::mpsc::{channel, Receiver};
use gdnative::*;
use tts::{Features, TTS as Tts}; use gdnative::prelude::*;
use tts::{Features, Tts, UtteranceId};
#[derive(NativeClass)]
#[inherit(Node)] #[derive(NativeClass)]
#[register_with(Self::register_properties)] struct Utterance(pub(crate) Option<UtteranceId>);
struct TTS(Tts);
#[methods]
#[methods] impl Utterance {
impl TTS { fn new(_owner: &Reference) -> Self {
fn _init(_owner: gdnative::Node) -> Self { Self(None)
let tts = Tts::default().unwrap(); }
Self(tts) }
}
#[allow(clippy::enum_variant_names)]
fn register_properties(builder: &ClassBuilder<Self>) { enum Msg {
builder UtteranceBegin(UtteranceId),
.add_property("rate") UtteranceEnd(UtteranceId),
.with_getter(|this: &TTS, _| match this.0.get_rate() { UtteranceStop(UtteranceId),
Ok(rate) => rate, }
_ => 0.,
}) #[allow(clippy::upper_case_acronyms)]
.with_setter(|this: &mut TTS, _, v: f32| { #[derive(NativeClass)]
let Features { #[inherit(Node)]
rate: rate_supported, #[register_with(Self::register)]
.. struct TTS(Tts, Receiver<Msg>);
} = this.0.supported_features();
if rate_supported { #[methods]
let mut v = v; impl TTS {
if v < this.0.min_rate() { fn new(owner: &Node) -> Self {
v = this.0.min_rate(); owner.set_pause_mode(2);
} else if v > this.0.max_rate() { let tts = Tts::default().expect("Failed to initialize TTS");
v = this.0.max_rate(); let (tx, rx) = channel();
} let Features {
this.0.set_rate(v).unwrap(); utterance_callbacks,
} ..
}) } = tts.supported_features();
.done(); if utterance_callbacks {
builder let tx_end = tx.clone();
.add_property("min_rate") let tx_stop = tx.clone();
.with_getter(|this: &TTS, _| { tts.on_utterance_begin(Some(Box::new(move |utterance| {
let Features { tx.send(Msg::UtteranceBegin(utterance))
rate: rate_supported, .expect("Failed to send UtteranceBegin");
.. })))
} = this.0.supported_features(); .expect("Failed to set utterance_begin callback");
if rate_supported { tts.on_utterance_end(Some(Box::new(move |utterance| {
this.0.min_rate() tx_end
} else { .send(Msg::UtteranceEnd(utterance))
0. .expect("Failed to send UtteranceEnd");
} })))
}) .expect("Failed to set utterance_end callback");
.done(); tts.on_utterance_stop(Some(Box::new(move |utterance| {
builder tx_stop
.add_property("max_rate") .send(Msg::UtteranceStop(utterance))
.with_getter(|this: &TTS, _| { .expect("Failed to send UtteranceStop");
let Features { })))
rate: rate_supported, .expect("Failed to set utterance_stop callback");
.. }
} = this.0.supported_features(); Self(tts, rx)
if rate_supported { }
this.0.max_rate()
} else { fn register(builder: &ClassBuilder<Self>) {
0. builder
} .property("volume")
}) .with_getter(|this: &TTS, _| match this.0.get_volume() {
.done(); Ok(volume) => volume,
builder _ => 0.,
.add_property("normal_rate") })
.with_getter(|this: &TTS, _| { .with_setter(|this: &mut TTS, _, v: f32| {
let Features { let Features {
rate: rate_supported, volume: volume_supported,
.. ..
} = this.0.supported_features(); } = this.0.supported_features();
if rate_supported { if volume_supported {
this.0.normal_rate() let mut v = v;
} else { if v < this.0.min_volume() {
0. v = this.0.min_volume();
} } else if v > this.0.max_volume() {
}) v = this.0.max_volume();
.done(); }
builder this.0.set_volume(v).expect("Failed to set volume");
.add_property("can_detect_screen_reader") }
.with_getter(|_: &TTS, _| if cfg!(windows) { true } else { false }) })
.done(); .done();
#[allow(unreachable_code)] builder
builder .property("min_volume")
.add_property("has_screen_reader") .with_getter(|this: &TTS, _| {
.with_getter(|_: &TTS, _| { let Features {
#[cfg(windows)] volume: volume_supported,
{ ..
let tolk = tolk::Tolk::new(); } = this.0.supported_features();
return tolk.detect_screen_reader().is_some(); if volume_supported {
} this.0.min_volume()
false } else {
}) 0.
.done(); }
builder })
.add_property("can_detect_is_speaking") .done();
.with_getter(|this: &TTS, _| { builder
let Features { .property("max_volume")
is_speaking: is_speaking_supported, .with_getter(|this: &TTS, _| {
.. let Features {
} = this.0.supported_features(); volume: volume_supported,
return is_speaking_supported; ..
}) } = this.0.supported_features();
.done(); if volume_supported {
builder this.0.max_volume()
.add_property("is_speaking") } else {
.with_getter(|this: &TTS, _| { 0.
let Features { }
is_speaking: is_speaking_supported, })
.. .done();
} = this.0.supported_features(); builder
if is_speaking_supported { .property("normal_volume")
return this.0.is_speaking().unwrap(); .with_getter(|this: &TTS, _| {
} else { let Features {
return false; volume: volume_supported,
} ..
}) } = this.0.supported_features();
.done(); if volume_supported {
} this.0.normal_volume()
} else {
#[export] 0.
fn speak(&mut self, _owner: Node, message: GodotString, interrupt: bool) { }
let message = message.to_string(); })
self.0.speak(message, interrupt).unwrap(); .done();
} builder
.property("rate")
#[export] .with_getter(|this: &TTS, _| match this.0.get_rate() {
fn stop(&mut self, _owner: Node) { Ok(rate) => rate,
self.0.stop().unwrap(); _ => 0.,
} })
.with_setter(|this: &mut TTS, _, v: f32| {
#[export] let Features {
fn is_rate_supported(&mut self, _owner: Node) -> bool { rate: rate_supported,
let Features { ..
rate: rate_supported, } = this.0.supported_features();
.. if rate_supported {
} = self.0.supported_features(); let mut v = v;
rate_supported if v < this.0.min_rate() {
} v = this.0.min_rate();
} } else if v > this.0.max_rate() {
v = this.0.max_rate();
fn init(handle: gdnative::init::InitHandle) { }
env_logger::init(); this.0.set_rate(v).expect("Failed to set rate");
handle.add_class::<TTS>(); }
} })
.done();
godot_gdnative_init!(); builder
godot_nativescript_init!(init); .property("min_rate")
godot_gdnative_terminate!(); .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();
rate_supported
}
#[method]
fn are_utterance_callbacks_supported(&mut self) -> bool {
let Features {
utterance_callbacks: supported,
..
} = self.0.supported_features();
supported
}
#[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>();
}
godot_init!(init);

View 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>

View 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) {
}
}