Compare commits

..

No commits in common. "main" and "v0.1.0" have entirely different histories.
main ... v0.1.0

10 changed files with 241 additions and 279 deletions

40
.drone.yml Normal file
View File

@ -0,0 +1,40 @@
kind: pipeline
type: docker
name: default
steps:
- name: test
image: rust
commands:
- rustup component add clippy rustfmt
- apt-get update -qq
- apt-get install -qqy llvm-dev libclang-dev clang libspeechd-dev pkg-config libx11-dev libasound2-dev libudev-dev libxcb-xfixes0-dev libwayland-dev libxkbcommon-dev libvulkan-dev libpulse-dev
- cargo fmt --check
- cargo test
- cargo clippy
- name: release
image: rust
commands:
- cargo publish
when:
ref:
- refs/tags/v*
environment:
CARGO_REGISTRY_TOKEN:
from_secret: cargo_registry_token
- name: discord notification
image: appleboy/drone-discord
when:
status: [success, failure]
settings:
webhook_id:
from_secret: discord_webhook_id
webhook_token:
from_secret: discord_webhook_token
tts: true
message: >
{{#success build.status}}
{{repo.name}} build {{build.number}} succeeded: <{{build.link}}>
{{else}}
{{repo.name}} build {{build.number}} failed: <{{build.link}}>
{{/success}}

View File

@ -1,39 +0,0 @@
name: Release
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+*"
workflow_dispatch:
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: ~/.cache/pre-commit
key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }}
- uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
components: rustfmt, clippy
- name: install Linux build dependencies
run: sudo apt-get update; sudo apt-get install -y --no-install-recommends libasound2-dev libudev-dev libwayland-dev libclang-dev libspeechd-dev
if: runner.os == 'linux'
- uses: actions/setup-python@v3
- uses: pre-commit/action@v3.0.1
- name: Publish
run: cargo publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

View File

@ -1,37 +0,0 @@
name: Test
on:
pull_request:
push:
jobs:
test:
strategy:
matrix:
# os: [windows-latest, ubuntu-latest, macos-latest]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: ~/.cache/pre-commit
key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }}
- uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
components: rustfmt, clippy
- name: install Linux build dependencies
run: sudo apt-get update; sudo apt-get install -y --no-install-recommends libasound2-dev libudev-dev libwayland-dev libclang-dev libspeechd-dev
if: runner.os == 'linux'
- uses: actions/setup-python@v3
- uses: pre-commit/action@v3.0.1

105
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,105 @@
name: Release
on:
push:
tags:
- "v*"
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' }}
- 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
check_web:
name: Check Web
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: Swatinem/rust-cache@v1
- uses: actions-rs/toolchain@v1
with:
target: wasm32-unknown-unknown
profile: minimal
toolchain: stable
components: rustfmt, clippy
override: true
- uses: actions-rs/cargo@v1
with:
command: check
args: --all-features --examples --target wasm32-unknown-unknown
- 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 --target wasm32-unknown-unknown
publish_winrt_bindings:
name: Publish winrt_bindings
runs-on: windows-latest
needs: [check]
env:
CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }}
steps:
- uses: actions/checkout@v2
- uses: Swatinem/rust-cache@v1
- uses: actions-rs/toolchain@v1
with:
target: wasm32-unknown-unknown
profile: minimal
toolchain: stable
components: rustfmt, clippy
override: true
- run: |
cargo login $CARGO_TOKEN
cd winrt_bindings
cargo publish || true
publish:
name: Publish
runs-on: ubuntu-latest
needs: [check, check_web]
env:
CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }}
steps:
- uses: actions/checkout@v2
- uses: Swatinem/rust-cache@v1
- uses: actions-rs/toolchain@v1
with:
target: wasm32-unknown-unknown
profile: minimal
toolchain: stable
override: true
- run: |
sudo apt-get update
sudo apt-get install -y libspeechd-dev
cargo login $CARGO_TOKEN
cargo publish

50
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,50 @@
name: Test
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 pkg-config libx11-dev libasound2-dev libudev-dev
if: ${{ runner.os == 'Linux' }}
- 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
if: ${{ runner.os != 'Linux' }}
- uses: actions-rs/cargo@v1
with:
command: check
args: --no-default-features --examples
if: ${{ runner.os == 'Linux' }}
- 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
if: ${{ runner.os != 'Linux' }}
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --no-default-features
if: ${{ runner.os == 'Linux' }}

View File

@ -1,10 +0,0 @@
fail_fast: true
repos:
- repo: https://github.com/doublify/pre-commit-rust
rev: v1.0
hooks:
- id: fmt
args: [--, --check]
- id: cargo-check
args: [--bins, --examples]
- id: clippy

View File

@ -2,88 +2,6 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## Version 0.10.0 - 2024-12-05
### Features
- `Tts` can now be used as a component, and multiple TTS instances are supported.
### Miscellaneous Tasks
- Upgrade to Bevy 0.15.
## Version 0.9.0 - 2024-07-07
### Miscellaneous Tasks
- Switch to Gitea Actions.
- Integrate pre-commit.
- Update to Bevy 0.14.
## Version 0.8.0 - 2024-03-14
### Miscellaneous Tasks
- Upgrade to Bevy v0.13.
## Version 0.7.0 - 2024-02-09
### Miscellaneous Tasks
- Bump dependencies.
## Version 0.6.0 - 2023-07-16
### Miscellaneous Tasks
- Update to Bevy 0.11.
## Version 0.5.0 - 2023-03-06
### Miscellaneous Tasks
- Update Bevy to 0.10.
## Version 0.4.0 - 2022-12-20
### Features
- Make wrapped `Tts` instance public.
## Version 0.3.0 - 2022-12-20
### Features
- Add `Tts::screen_reader_available()` to `Tts` resource.
## Version 0.2.1 - 2022-12-20
### Features
- Re-export top-level `tts` crate.
### Miscellaneous Tasks
- Always pull Rust image when building in CI.
## Version 0.2.0 - 2022-12-06
### Miscellaneous Tasks
- Update to Bevy 0.9 and TTS 0.25.
## Version 0.1.2 - 2022-09-07
### Miscellaneous Tasks
- Update tts dependency.
## Version 0.1.1 - 2022-08-29
### Miscellaneous Tasks
- Update automated build configuration to install dependencies on publish step.
## Version 0.1.0 - 2022-08-29 ## Version 0.1.0 - 2022-08-29
### Miscellaneous Tasks ### Miscellaneous Tasks

View File

@ -1,8 +1,8 @@
[package] [package]
name = "bevy_tts" name = "bevy_tts"
version = "0.10.0"
description = "Text-to-speech for the Bevy game engine" description = "Text-to-speech for the Bevy game engine"
repository = "https://labs.lightsout.games/projects/bevy_tts" repository = "https://labs.lightsout.games/projects/bevy_tts"
version = "0.1.0"
authors = ["Nolan Darilek <nolan@thewordnerd.info>"] authors = ["Nolan Darilek <nolan@thewordnerd.info>"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
@ -10,22 +10,21 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]
default = ["speech_dispatcher_0_11", "tolk"] default = ["speech_dispatcher_0_10_2", "tolk"]
speech_dispatcher_0_9 = ["tts/speech_dispatcher_0_9"]
speech_dispatcher_0_10 = ["tts/speech_dispatcher_0_10"] speech_dispatcher_0_10 = ["tts/speech_dispatcher_0_10"]
speech_dispatcher_0_11 = ["tts/speech_dispatcher_0_11"] speech_dispatcher_0_10_2 = ["tts/speech_dispatcher_0_10_2"]
tolk = ["tts/tolk"] tolk = ["tts/tolk"]
[dependencies] [dependencies]
bevy = { version = "0.15", default-features = false } bevy = { version = "0.8", default-features = false }
crossbeam-channel = "0.5" crossbeam-channel = "0.5"
tts = { version = "0.26", default-features = false } tts = { version = "0.23.1", default-features = false }
[dev-dependencies] [dev-dependencies]
bevy = { version = "0.15", default-features = true } bevy = { version = "0.8", default-features = true }
[package.metadata.release] [package.metadata.release]
tag-prefix = ""
publish = false publish = false
push = false push = false
pre-release-hook = ["git-cliff", "-o", "CHANGELOG.md", "--tag", "{{version}}"] pre-release-hook = ["git-cliff", "-o", "CHANGELOG.md", "--tag", "{{version}}"]
pre-release-commit-message = "Release"

View File

@ -3,25 +3,17 @@ use bevy_tts::*;
fn main() { fn main() {
App::new() App::new()
.add_plugins((DefaultPlugins, bevy_tts::TtsPlugin)) .add_plugins(DefaultPlugins)
.add_systems(Startup, setup) .add_plugin(bevy_tts::TtsPlugin)
.add_systems(Update, (event_poll, greet)) .add_system(bevy::window::close_on_esc)
.add_observer(|trigger: Trigger<TtsEvent>| { .add_startup_system(setup)
println!("{:?}", trigger.event()); .add_system(event_poll)
}) .add_system(greet)
.run(); .run();
} }
// Speaks a bunch of messages and changes TTS properties. // Speaks a bunch of messages and changes TTS properties.
fn setup(mut commands: Commands, mut tts: ResMut<Tts>) { fn setup(mut tts: ResMut<Tts>) {
let mut other_tts = Tts::default();
let Features { voice, .. } = other_tts.supported_features();
if voice {
let voices = other_tts.voices().unwrap();
let v = voices.last().unwrap();
other_tts.set_voice(&v.clone()).unwrap();
}
commands.spawn(other_tts);
tts.speak("Hello, world.", false).unwrap(); tts.speak("Hello, world.", false).unwrap();
let Features { rate, .. } = tts.supported_features(); let Features { rate, .. } = tts.supported_features();
if rate { if rate {
@ -68,30 +60,18 @@ fn setup(mut commands: Commands, mut tts: ResMut<Tts>) {
tts.set_volume(original_volume).unwrap(); tts.set_volume(original_volume).unwrap();
} }
tts.speak("Press G for a greeting.", false).unwrap(); tts.speak("Press G for a greeting.", false).unwrap();
tts.speak(
"Press S to speak with a second voice, if you're lucky.",
false,
)
.unwrap();
} }
// Reports events from TTS subsystem. // Reports events from TTS subsystem.
fn event_poll(mut events: EventReader<TtsEvent>) { fn event_poll(mut events: EventReader<TtsEvent>) {
for event in events.read() { for event in events.iter() {
println!("{:?}", event); println!("{:?}", event);
} }
} }
// Shows how to output speech in response to a keypress. // Shows how to output speech in response to a keypress.
fn greet(input: Res<ButtonInput<KeyCode>>, mut tts: ResMut<Tts>, mut speaker: Query<&mut Tts>) { fn greet(input: Res<Input<KeyCode>>, mut tts: ResMut<Tts>) {
if input.just_pressed(KeyCode::KeyG) { if input.just_pressed(KeyCode::G) {
tts.speak("Hey there!", true).unwrap(); tts.speak("Hey there!", true).unwrap();
} }
if input.just_pressed(KeyCode::KeyS) {
if let Ok(mut speaker) = speaker.get_single_mut() {
speaker
.speak("Hey there from the TTS component!", true)
.unwrap();
}
}
} }

View File

@ -1,96 +1,52 @@
use bevy::{ use bevy::prelude::*;
ecs::{component::ComponentId, world::DeferredWorld},
prelude::*,
};
use crossbeam_channel::{unbounded, Receiver}; use crossbeam_channel::{unbounded, Receiver};
pub use tts::{self, Backends, Error, Features, UtteranceId}; pub use tts::{Backends, Error, Features, Tts, UtteranceId};
#[derive(Component, Resource, Clone, Deref, DerefMut)] #[derive(Debug)]
#[component(on_add = on_tts_added, on_remove=on_tts_removed)]
pub struct Tts(pub tts::Tts);
impl Default for Tts {
fn default() -> Self {
Self(tts::Tts::default().unwrap())
}
}
impl Tts {
pub fn screen_reader_available() -> bool {
tts::Tts::screen_reader_available()
}
}
#[derive(Event, Debug)]
pub enum TtsEvent { pub enum TtsEvent {
UtteranceBegin(UtteranceId), UtteranceBegin(UtteranceId),
UtteranceEnd(UtteranceId), UtteranceEnd(UtteranceId),
UtteranceStop(UtteranceId), UtteranceStop(UtteranceId),
} }
#[derive(Component, Resource)]
struct TtsChannel(Receiver<TtsEvent>); struct TtsChannel(Receiver<TtsEvent>);
fn on_tts_added(mut world: DeferredWorld, entity: Entity, _: ComponentId) { fn poll_callbacks(channel: Res<TtsChannel>, mut events: EventWriter<TtsEvent>) {
let tts = &world.get::<Tts>(entity).unwrap();
let channel = setup_tts(tts);
world.commands().entity(entity).insert(channel);
}
fn on_tts_removed(mut world: DeferredWorld, entity: Entity, _: ComponentId) {
world.commands().entity(entity).remove::<TtsChannel>();
}
fn poll_callbacks(
mut commands: Commands,
channel: Res<TtsChannel>,
mut events: EventWriter<TtsEvent>,
speakers: Query<(Entity, &TtsChannel), With<Tts>>,
) {
if let Ok(msg) = channel.0.try_recv() { if let Ok(msg) = channel.0.try_recv() {
events.send(msg); events.send(msg);
} }
for (entity, channel) in &speakers {
if let Ok(msg) = channel.0.try_recv() {
commands.entity(entity).trigger(msg);
}
}
} }
fn setup_tts(tts: &Tts) -> TtsChannel {
let (tx, rx) = unbounded();
let tx_begin = tx.clone();
let tx_end = tx.clone();
let tx_stop = tx;
let Features {
utterance_callbacks,
..
} = tts.supported_features();
if utterance_callbacks {
tts.on_utterance_begin(Some(Box::new(move |utterance| {
tx_begin.send(TtsEvent::UtteranceBegin(utterance)).unwrap();
})))
.unwrap();
tts.on_utterance_end(Some(Box::new(move |utterance| {
tx_end.send(TtsEvent::UtteranceEnd(utterance)).unwrap();
})))
.unwrap();
tts.on_utterance_stop(Some(Box::new(move |utterance| {
tx_stop.send(TtsEvent::UtteranceStop(utterance)).unwrap();
})))
.unwrap();
}
TtsChannel(rx)
}
pub struct TtsPlugin; pub struct TtsPlugin;
impl Plugin for TtsPlugin { impl Plugin for TtsPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
let tts = Tts::default(); let tts = Tts::default().unwrap();
let channel = setup_tts(&tts); let (tx, rx) = unbounded();
let tx_begin = tx.clone();
let tx_end = tx.clone();
let tx_stop = tx;
let Features {
utterance_callbacks,
..
} = tts.supported_features();
if utterance_callbacks {
tts.on_utterance_begin(Some(Box::new(move |utterance| {
tx_begin.send(TtsEvent::UtteranceBegin(utterance)).unwrap();
})))
.unwrap();
tts.on_utterance_end(Some(Box::new(move |utterance| {
tx_end.send(TtsEvent::UtteranceEnd(utterance)).unwrap();
})))
.unwrap();
tts.on_utterance_stop(Some(Box::new(move |utterance| {
tx_stop.send(TtsEvent::UtteranceStop(utterance)).unwrap();
})))
.unwrap();
}
app.add_event::<TtsEvent>() app.add_event::<TtsEvent>()
.insert_resource(channel) .insert_resource(TtsChannel(rx))
.insert_resource(tts) .insert_resource(tts)
.add_systems(Update, poll_callbacks); .add_system(poll_callbacks);
} }
} }