mirror of
https://github.com/lightsoutgames/bevy_tts.git
synced 2024-12-22 07:25:57 +00:00
feat: Tts
can now be used as a component, and multiple TTS instances are supported.
This commit is contained in:
parent
0ef7579f91
commit
3891f289d8
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
91
src/lib.rs
91
src/lib.rs
|
@ -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,45 +28,68 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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::Tts::default().unwrap();
|
let tts = Tts::default();
|
||||||
let (tx, rx) = unbounded();
|
let channel = setup_tts(&tts);
|
||||||
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();
|
|
||||||
}
|
|
||||||
let tts = 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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user