Files
ZacharyZhang-NY a4f101f7b3 T25d Generative: sound and motion
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.
2026-09-26 13:16:42 -04:00

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)
}),
),
),
)
}