AudioGenerationPlayer shows a generated sound's prompt, model and length, a bar while it is made, then chat::AudioMessage to play it; Regenerate and Download wait for it. TTSVoicePicker lists voices with a sample to play, the chosen one washed with a check, tags dropping below when narrow. VideoGenerationTimeline lays shots along their time under a ruler, with posters, placeholders, a playhead and Extend. AudioMessage's waveform is one Tab stop where Left and Right seek, and it gives way at 280px. debug::ruler serves the timeline's ruler. A mask shown to read no longer follows the pointer.
190 lines
6.8 KiB
Rust
190 lines
6.8 KiB
Rust
use std::time::Duration;
|
|
|
|
use ely_gpui_component::generative::{
|
|
AudioGenerationPlayer, Outcome, Shot, TTSVoicePicker, VideoGenerationTimeline, Voice,
|
|
};
|
|
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
|
|
|
|
use crate::{
|
|
probe::probe,
|
|
ui::{keep, section, set},
|
|
};
|
|
|
|
/// A waveform for the demo: a swell and a fall, with a beat.
|
|
fn peaks() -> Vec<f32> {
|
|
(0..56)
|
|
.map(|ix| {
|
|
let t = ix as f32 / 55.0;
|
|
let swell = (t * std::f32::consts::PI).sin();
|
|
let beat = 0.6 + 0.4 * ((ix * 7 % 11) as f32 / 10.0);
|
|
(0.15 + 0.8 * swell * beat).min(1.0)
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub fn audio(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let player = keep("gen-audio", || (Duration::from_secs(12), false), window, cx);
|
|
let (played, playing) = *player.read(cx);
|
|
let (toggled, sought) = (player.clone(), player);
|
|
let length = Duration::from_secs(32);
|
|
section(
|
|
"AudioGenerationPlayer",
|
|
"A generated sound: its prompt, model and length, a bar while it is made, then a player whose waveform lights as it plays. A press on the waveform seeks, and so do Left and Right once it has focus.",
|
|
cx,
|
|
)
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.flex_wrap()
|
|
.items_start()
|
|
.gap_4()
|
|
.child(
|
|
probe(
|
|
"gen-audio",
|
|
div().w(px(440.)).child(
|
|
AudioGenerationPlayer::new("gen-audio", "rain on a limestone courtyard, far thunder, a slow piano", "Orchid Audio", peaks(), length)
|
|
.played(played, playing)
|
|
.on_toggle(move |play, _, cx| {
|
|
let at = toggled.read(cx).0;
|
|
set(&toggled, (at, play), cx)
|
|
})
|
|
.on_seek(move |share, _, cx| {
|
|
let playing = sought.read(cx).1;
|
|
set(&sought, (length.mul_f32(share), playing), cx)
|
|
})
|
|
.on_regenerate(|_, _| log::info!("gallery: regenerate"))
|
|
.on_download(|_, _| log::info!("gallery: download")),
|
|
),
|
|
),
|
|
)
|
|
.child(
|
|
div().w(px(440.)).child(
|
|
AudioGenerationPlayer::new("gen-audio-making", "a cello under a high window, morning", "Orchid Audio", peaks(), Duration::from_secs(45))
|
|
.making(0.45)
|
|
.on_regenerate(|_, _| log::info!("gallery: regenerate"))
|
|
.on_download(|_, _| log::info!("gallery: download")),
|
|
),
|
|
),
|
|
)
|
|
}
|
|
|
|
fn voices() -> Vec<Voice> {
|
|
let voice = |key: &str, name: &str, about: &str, tags: &[&str]| Voice {
|
|
key: key.to_string().into(),
|
|
name: name.to_string().into(),
|
|
about: about.to_string().into(),
|
|
tags: tags
|
|
.iter()
|
|
.map(|tag| SharedString::from(tag.to_string()))
|
|
.collect(),
|
|
};
|
|
vec![
|
|
voice(
|
|
"iris",
|
|
"Iris",
|
|
"Warm and unhurried, for long reads",
|
|
&["English", "US"],
|
|
),
|
|
voice(
|
|
"theo",
|
|
"Theo",
|
|
"Bright and quick, for product tours",
|
|
&["English", "UK"],
|
|
),
|
|
voice(
|
|
"mei",
|
|
"Mei",
|
|
"Calm, with clear diction",
|
|
&["Mandarin", "English"],
|
|
),
|
|
voice(
|
|
"ines",
|
|
"Inés",
|
|
"Soft, close to the microphone",
|
|
&["Spanish"],
|
|
),
|
|
]
|
|
}
|
|
|
|
pub fn voice(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let chosen = keep("gen-voice", || SharedString::from("iris"), window, cx);
|
|
let sample = keep("gen-voice-sample", || None::<SharedString>, window, cx);
|
|
let (now, sounding) = (chosen.read(cx).clone(), sample.read(cx).clone());
|
|
let picker = TTSVoicePicker::new("gen-voices", voices())
|
|
.selected(now)
|
|
.on_select(move |key, _, cx| set(&chosen, key.clone(), cx))
|
|
.on_play(move |key, _, cx| set(&sample, key, cx));
|
|
let picker = match sounding {
|
|
Some(key) => picker.playing(key),
|
|
None => picker,
|
|
};
|
|
section(
|
|
"TTSVoicePicker",
|
|
"Voices to speak with: play a sample, then choose one by its row. The chosen voice takes the active wash and a check; tags drop below the name when narrow.",
|
|
cx,
|
|
)
|
|
.child(probe("gen-voices", div().w(px(460.)).child(picker)))
|
|
}
|
|
|
|
fn shots() -> Vec<Shot> {
|
|
let shot = |key: &str, prompt: &str, seconds: u64, outcome| Shot {
|
|
key: key.to_string().into(),
|
|
prompt: prompt.to_string().into(),
|
|
length: Duration::from_secs(seconds),
|
|
outcome,
|
|
};
|
|
vec![
|
|
shot(
|
|
"s1",
|
|
"a slow pan across a white atrium",
|
|
5,
|
|
Outcome::Done(asset!("atrium.jpg").into()),
|
|
),
|
|
shot(
|
|
"s2",
|
|
"light moves over the stair",
|
|
4,
|
|
Outcome::Done(asset!("atrium-stair.jpg").into()),
|
|
),
|
|
shot("s3", "dusk over the dunes", 6, Outcome::Pending(Some(0.35))),
|
|
shot(
|
|
"s4",
|
|
"grass bends in the wind",
|
|
4,
|
|
Outcome::Failed("timed out".into()),
|
|
),
|
|
]
|
|
}
|
|
|
|
pub fn video(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let list = keep("gen-shots", shots, window, cx);
|
|
let chosen = keep("gen-shot", || SharedString::from("s1"), window, cx);
|
|
let (now, pick) = (list.read(cx).clone(), chosen.read(cx).clone());
|
|
section(
|
|
"VideoGenerationTimeline",
|
|
"A video made shot by shot along its time: each shot as wide as it runs, its poster once made, a placeholder while it comes. Choose a shot to read it; Extend asks for the next.",
|
|
cx,
|
|
)
|
|
.child(
|
|
probe(
|
|
"gen-timeline",
|
|
div().w(px(560.)).child(
|
|
VideoGenerationTimeline::new("gen-timeline", now, Duration::from_millis(3_200))
|
|
.selected(pick)
|
|
.on_select(move |key, _, cx| set(&chosen, key.clone(), cx))
|
|
.on_extend(move |_, cx| {
|
|
let mut next = list.read(cx).clone();
|
|
let key = format!("s{}", next.len() + 1);
|
|
next.push(Shot {
|
|
key: key.into(),
|
|
prompt: "the camera rises over the dunes".into(),
|
|
length: Duration::from_secs(5),
|
|
outcome: Outcome::Pending(Some(0.0)),
|
|
});
|
|
set(&list, next, cx)
|
|
}),
|
|
),
|
|
),
|
|
)
|
|
}
|