Compare commits

..

5 Commits
v0.9.0 ... main

Author SHA1 Message Date
a6c22b4eb0 Release
All checks were successful
Test / test (ubuntu-latest) (push) Successful in 2m17s
2024-12-05 16:17:26 -06:00
a1c7c48003 Update CHANGELOG. 2024-12-05 16:17:05 -06:00
3891f289d8 feat: Tts can now be used as a component, and multiple TTS instances are supported. 2024-12-05 16:16:23 -06:00
0ef7579f91 chore: Upgrade to Bevy 0.15. 2024-12-05 15:25:34 -06:00
5c682ce6ee Fix workflow.
All checks were successful
Test / test (ubuntu-latest) (push) Successful in 2m7s
2024-07-07 09:57:55 -05:00
5 changed files with 101 additions and 35 deletions

View File

@ -33,7 +33,7 @@ jobs:
if: runner.os == 'linux' if: runner.os == 'linux'
- uses: actions/setup-python@v3 - uses: actions/setup-python@v3
- uses: pre-commit/action@v3.0.1 - uses: pre-commit/action@v3.0.1
- ame: Publish - name: Publish
run: cargo publish run: cargo publish
env: env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

View File

@ -2,6 +2,16 @@
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 ## Version 0.9.0 - 2024-07-07
### Miscellaneous Tasks ### Miscellaneous Tasks

View File

@ -1,6 +1,6 @@
[package] [package]
name = "bevy_tts" name = "bevy_tts"
version = "0.9.0" 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"
authors = ["Nolan Darilek <nolan@thewordnerd.info>"] authors = ["Nolan Darilek <nolan@thewordnerd.info>"]
@ -17,12 +17,12 @@ speech_dispatcher_0_11 = ["tts/speech_dispatcher_0_11"]
tolk = ["tts/tolk"] tolk = ["tts/tolk"]
[dependencies] [dependencies]
bevy = { version = "0.14", default-features = false } bevy = { version = "0.15", default-features = false }
crossbeam-channel = "0.5" crossbeam-channel = "0.5"
tts = { version = "0.26", default-features = false } tts = { version = "0.26", default-features = false }
[dev-dependencies] [dev-dependencies]
bevy = { version = "0.14", default-features = true } bevy = { version = "0.15", default-features = true }
[package.metadata.release] [package.metadata.release]
publish = false publish = false

View File

@ -6,11 +6,22 @@ fn main() {
.add_plugins((DefaultPlugins, bevy_tts::TtsPlugin)) .add_plugins((DefaultPlugins, bevy_tts::TtsPlugin))
.add_systems(Startup, setup) .add_systems(Startup, setup)
.add_systems(Update, (event_poll, greet)) .add_systems(Update, (event_poll, greet))
.add_observer(|trigger: Trigger<TtsEvent>| {
println!("{:?}", trigger.event());
})
.run(); .run();
} }
// Speaks a bunch of messages and changes TTS properties. // Speaks a bunch of messages and changes TTS properties.
fn setup(mut tts: ResMut<Tts>) { fn setup(mut commands: Commands, 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 {
@ -57,6 +68,11 @@ fn setup(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.
@ -67,8 +83,15 @@ fn event_poll(mut events: EventReader<TtsEvent>) {
} }
// 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>) { fn greet(input: Res<ButtonInput<KeyCode>>, mut tts: ResMut<Tts>, mut speaker: Query<&mut Tts>) {
if input.just_pressed(KeyCode::KeyG) { if input.just_pressed(KeyCode::KeyG) {
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,10 +1,20 @@
use bevy::prelude::*; use bevy::{
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::{self, Backends, Error, Features, UtteranceId};
#[derive(Resource, Clone, Deref, DerefMut)] #[derive(Component, Resource, Clone, Deref, DerefMut)]
#[component(on_add = on_tts_added, on_remove=on_tts_removed)]
pub struct Tts(pub tts::Tts); pub struct Tts(pub tts::Tts);
impl Default for Tts {
fn default() -> Self {
Self(tts::Tts::default().unwrap())
}
}
impl Tts { impl Tts {
pub fn screen_reader_available() -> bool { pub fn screen_reader_available() -> bool {
tts::Tts::screen_reader_available() tts::Tts::screen_reader_available()
@ -18,20 +28,36 @@ pub enum TtsEvent {
UtteranceStop(UtteranceId), UtteranceStop(UtteranceId),
} }
#[derive(Resource)] #[derive(Component, Resource)]
struct TtsChannel(Receiver<TtsEvent>); struct TtsChannel(Receiver<TtsEvent>);
fn poll_callbacks(channel: Res<TtsChannel>, mut events: EventWriter<TtsEvent>) { fn on_tts_added(mut world: DeferredWorld, entity: Entity, _: ComponentId) {
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);
}
}
} }
pub struct TtsPlugin; fn setup_tts(tts: &Tts) -> TtsChannel {
impl Plugin for TtsPlugin {
fn build(&self, app: &mut App) {
let tts = tts::Tts::default().unwrap();
let (tx, rx) = unbounded(); let (tx, rx) = unbounded();
let tx_begin = tx.clone(); let tx_begin = tx.clone();
let tx_end = tx.clone(); let tx_end = tx.clone();
@ -54,9 +80,16 @@ impl Plugin for TtsPlugin {
}))) })))
.unwrap(); .unwrap();
} }
let tts = Tts(tts); TtsChannel(rx)
}
pub struct TtsPlugin;
impl Plugin for TtsPlugin {
fn build(&self, app: &mut App) {
let tts = Tts::default();
let channel = setup_tts(&tts);
app.add_event::<TtsEvent>() app.add_event::<TtsEvent>()
.insert_resource(TtsChannel(rx)) .insert_resource(channel)
.insert_resource(tts) .insert_resource(tts)
.add_systems(Update, poll_callbacks); .add_systems(Update, poll_callbacks);
} }