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.
This commit is contained in:
16 files changed
+1198
-19
No files matched your search
@@ -5,6 +5,7 @@ use crate::{forms, primitives::FocusNext, theme::Theme};
|
||||
mod canvas;
|
||||
mod results;
|
||||
mod settings;
|
||||
mod sound;
|
||||
|
||||
fn settle(cx: &mut VisualTestContext) {
|
||||
cx.run_until_parked();
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
use std::{cell::RefCell, rc::Rc, time::Duration};
|
||||
|
||||
use gpui::{
|
||||
Context, IntoElement, Modifiers, ParentElement, Render, Styled, TestAppContext, Window, div,
|
||||
point, px,
|
||||
};
|
||||
|
||||
use super::{press, settle, setup, tab};
|
||||
use crate::generative::{
|
||||
AudioGenerationPlayer, Outcome, Shot, TTSVoicePicker, VideoGenerationTimeline, Voice,
|
||||
};
|
||||
|
||||
type Heard = Rc<RefCell<Vec<String>>>;
|
||||
|
||||
/// A made sound a quarter played and one still being made, and what they heard.
|
||||
struct Players(Heard);
|
||||
|
||||
impl Render for Players {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
let (sought, made, making) = (self.0.clone(), self.0.clone(), self.0.clone());
|
||||
let length = Duration::from_secs(40);
|
||||
div()
|
||||
.w(px(420.0))
|
||||
.child(
|
||||
AudioGenerationPlayer::new("made", "rain", "Orchid", vec![0.5; 8], length)
|
||||
.played(Duration::from_secs(10), false)
|
||||
.on_toggle(|_, _, _| {})
|
||||
.on_seek(move |share, _, _| {
|
||||
sought.borrow_mut().push(format!("seek {share:.2}"))
|
||||
})
|
||||
.on_regenerate(move |_, _| made.borrow_mut().push("regenerate made".into())),
|
||||
)
|
||||
.child(
|
||||
AudioGenerationPlayer::new("making", "cello", "Orchid", vec![0.5; 8], length)
|
||||
.making(0.4)
|
||||
.on_regenerate(move |_, _| {
|
||||
making.borrow_mut().push("regenerate making".into())
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn the_waveform_seeks_with_keys_and_a_sound_being_made_waits(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let heard = Heard::default();
|
||||
let store = heard.clone();
|
||||
let (_, cx) = cx.add_window_view(|_, _| Players(store));
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
tab(1, cx);
|
||||
press("enter", cx);
|
||||
tab(2, cx);
|
||||
press("right", cx);
|
||||
tab(1, cx);
|
||||
press("enter", cx);
|
||||
let heard = heard.borrow().clone();
|
||||
assert_eq!(
|
||||
heard,
|
||||
["regenerate made", "seek 0.30", "regenerate made"],
|
||||
"Tab passes the waiting button"
|
||||
);
|
||||
}
|
||||
|
||||
/// Two voices, the first chosen, and what they heard.
|
||||
struct Voices(Heard);
|
||||
|
||||
impl Render for Voices {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
let (chosen, played) = (self.0.clone(), self.0.clone());
|
||||
let voice = |key: &str| Voice {
|
||||
key: key.to_string().into(),
|
||||
name: key.to_string().into(),
|
||||
about: "a voice".into(),
|
||||
tags: vec!["English".into()],
|
||||
};
|
||||
div().w(px(420.0)).child(
|
||||
TTSVoicePicker::new("voices", [voice("iris"), voice("theo")])
|
||||
.selected("iris")
|
||||
.on_select(move |key, _, _| chosen.borrow_mut().push(format!("choose {key}")))
|
||||
.on_play(move |key, _, _| played.borrow_mut().push(format!("play {key:?}"))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_sample_plays_without_choosing_its_voice(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let heard = Heard::default();
|
||||
let store = heard.clone();
|
||||
let (_, cx) = cx.add_window_view(|_, _| Voices(store));
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
tab(3, cx);
|
||||
press("enter", cx);
|
||||
cx.simulate_click(point(px(23.0), px(83.0)), Modifiers::none());
|
||||
settle(cx);
|
||||
assert_eq!(
|
||||
*heard.borrow(),
|
||||
["choose theo", "play Some(\"theo\")"],
|
||||
"a sample's press stays off its row"
|
||||
);
|
||||
}
|
||||
|
||||
/// Two shots, the first chosen, and what they heard.
|
||||
struct Timeline(Heard);
|
||||
|
||||
impl Render for Timeline {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
let (chosen, extended) = (self.0.clone(), self.0.clone());
|
||||
let shot = |key: &str, outcome| Shot {
|
||||
key: key.to_string().into(),
|
||||
prompt: "a pan".into(),
|
||||
length: Duration::from_secs(4),
|
||||
outcome,
|
||||
};
|
||||
div().w(px(420.0)).child(
|
||||
VideoGenerationTimeline::new(
|
||||
"timeline",
|
||||
[
|
||||
shot("a", Outcome::Done("missing.jpg".into())),
|
||||
shot("b", Outcome::Pending(None)),
|
||||
],
|
||||
Duration::ZERO,
|
||||
)
|
||||
.selected("a")
|
||||
.on_select(move |key, _, _| chosen.borrow_mut().push(format!("choose {key}")))
|
||||
.on_extend(move |_, _| extended.borrow_mut().push("extend".into())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_shot_is_chosen_once_and_extend_asks_for_the_next(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let heard = Heard::default();
|
||||
let store = heard.clone();
|
||||
let (_, cx) = cx.add_window_view(|_, _| Timeline(store));
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
for _ in 0..3 {
|
||||
tab(1, cx);
|
||||
press("enter", cx);
|
||||
}
|
||||
assert_eq!(*heard.borrow(), ["choose b", "extend"]);
|
||||
}
|
||||
|
||||
/// A voice list and a timeline shown to read, with nothing to take a choice.
|
||||
struct Shown;
|
||||
|
||||
impl Render for Shown {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
let voice = Voice {
|
||||
key: "iris".into(),
|
||||
name: "Iris".into(),
|
||||
about: "a voice".into(),
|
||||
tags: Vec::new(),
|
||||
};
|
||||
let shot = Shot {
|
||||
key: "a".into(),
|
||||
prompt: "a pan".into(),
|
||||
length: Duration::from_secs(4),
|
||||
outcome: Outcome::Pending(None),
|
||||
};
|
||||
div()
|
||||
.w(px(420.0))
|
||||
.child(TTSVoicePicker::new("voices", [voice]).selected("iris"))
|
||||
.child(VideoGenerationTimeline::new("timeline", [shot], Duration::ZERO).selected("a"))
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn lists_shown_to_read_take_no_focus(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let (_, cx) = cx.add_window_view(|_, _| Shown);
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
tab(1, cx);
|
||||
assert!(cx.update(|window, cx| window.focused(cx).is_none()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "a sound's peaks lie within 0 to 1")]
|
||||
fn a_peak_past_full_fails_loud() {
|
||||
let _ =
|
||||
AudioGenerationPlayer::new("sound", "rain", "Orchid", vec![1.5], Duration::from_secs(1));
|
||||
}
|
||||
Reference in new issue
Block a user