This commit is contained in:
Brandon Thomas committed 2026-10-13 05:24:58 -04:00
commit 4ebdc35b04
3 files changed
+45 -32

No files matched your search

+36 -24
View File
@@ -453,7 +453,7 @@ fn nice_step(spp: f64, sr: f64, min_px: f64) -> i64 {
fn draw_rulers(app: &mut SoundApp, ui: &mut Ui, area: Rect, tl: Rect, rulers: &[String]) {
let t = Tokens::current();
let s = app.engine.session().clone();
let s = app.engine.session_arc();
ui.painter().rect_filled(area, 0.0, t.ruler_bg);
let view = Range::new(sample_at(&s, tl, tl.min.x), sample_at(&s, tl, tl.max.x));
let sr = s.sample_rate;
@@ -538,9 +538,7 @@ fn draw_rulers(app: &mut SoundApp, ui: &mut Ui, area: Rect, tl: Rect, rulers: &[
} else {
soundcraft_time::FrameRate::Fps25
};
let mut s2 = s.clone();
s2.frame_rate = alt;
timebase_ruler(ui, &painter, &s2, tl, row, view, TimeFormat::Timecode, i + 1 == rulers.len());
timebase_ruler(ui, &painter, &s, tl, row, view, TimeFormat::Timecode, alt, i + 1 == rulers.len());
}
"markers" | "markers2" | "markers3" | "markers4" | "markers5" | "chords" => {
let lane: u8 = match id.as_str() {
@@ -595,7 +593,7 @@ fn draw_rulers(app: &mut SoundApp, ui: &mut Ui, area: Rect, tl: Rect, rulers: &[
}
_ => {
if let Some(f) = fmt {
timebase_ruler(ui, &painter, &s, tl, row, view, f, i + 1 == rulers.len());
timebase_ruler(ui, &painter, &s, tl, row, view, f, s.frame_rate, i + 1 == rulers.len());
// Clicking a timebase ruler locates.
let resp = ui.interact(row, ui.id().with(("ruler", id.as_str())), Sense::click_and_drag());
if (resp.clicked() || resp.dragged())
@@ -617,7 +615,17 @@ fn draw_rulers(app: &mut SoundApp, ui: &mut Ui, area: Rect, tl: Rect, rulers: &[
ui.painter().line_segment([pos2(area.min.x, area.max.y - 1.0), pos2(area.max.x, area.max.y - 1.0)], Stroke::new(1.0, t.border));
}
fn timebase_ruler(_ui: &Ui, painter: &egui::Painter, s: &Session, tl: Rect, row: Rect, view: Range, f: TimeFormat, bottom: bool) {
fn timebase_ruler(
_ui: &Ui,
painter: &egui::Painter,
s: &Session,
tl: Rect,
row: Rect,
view: Range,
f: TimeFormat,
rate: soundcraft_time::FrameRate,
bottom: bool,
) {
let t = Tokens::current();
let sr = s.sample_rate;
let spp = s.edit.zoom.samples_per_px;
@@ -627,7 +635,7 @@ fn timebase_ruler(_ui: &Ui, painter: &egui::Painter, s: &Session, tl: Rect, row:
// Bars every n so labels are ≥ 60 px apart.
let bar_px = {
let b = GridValue::Note { value: NoteValue::Bar, dotted: false, triplet: false };
b.step_samples(view.start.max(0), sr, &s.tempo, s.frame_rate) as f64 / spp
b.step_samples(view.start.max(0), sr, &s.tempo, rate) as f64 / spp
};
let every = [1i64, 2, 4, 8, 16, 32, 64, 128].into_iter().find(|n| bar_px * *n as f64 >= 60.0).unwrap_or(256);
let first = s.tempo.bar_beat_at(view.start.max(0), sr).bar;
@@ -696,9 +704,13 @@ fn timebase_ruler(_ui: &Ui, painter: &egui::Painter, s: &Session, tl: Rect, row:
TimeFormat::BarsBeats => s.tempo.bar_beat_at(at, sr).bar.to_string(),
TimeFormat::MinSecs => {
let secs = sr.seconds(at);
if secs.fract().abs() < 1e-9 { format!("{}:{:02}", (secs / 60.0) as i64, (secs as i64) % 60) } else { s.format_position(at, f) }
if secs.fract().abs() < 1e-9 {
format!("{}:{:02}", (secs / 60.0) as i64, (secs as i64) % 60)
} else {
format_position(at, f, sr, &s.tempo, rate, s.timecode_start, s.feet_frames_start())
}
}
_ => s.format_position(at, f),
_ => format_position(at, f, sr, &s.tempo, rate, s.timecode_start, s.feet_frames_start()),
};
painter.text(pos2(x + 3.0, row.center().y), Align2::LEFT_CENTER, label, regular(11.0), t.ruler_text);
} else {
@@ -759,28 +771,28 @@ fn views_button(app: &mut SoundApp, ui: &mut Ui, rulers: Rect) {
fn track_row(app: &mut SoundApp, ui: &mut Ui, id: TrackId, row: Rect, tl: Rect, cols: &[Column]) {
let t = Tokens::current();
let s = app.engine.session();
let Some(track) = s.track(id).cloned() else { return };
let s = app.engine.session_arc();
let Some(track) = s.track(id) else { return };
let selected = s.edit.selected_tracks.contains(&id);
let head = Rect::from_min_max(row.min, pos2(row.min.x + HEADER_W, row.max.y));
let (head_bg, lane_bg) = crate::theme::track_backgrounds(track.color, selected, app.ui.track_tint);
let mut x = head.max.x;
for &(col, _, _, w) in cols {
header_column(app, ui, &track, col, Rect::from_min_size(pos2(x, row.min.y), vec2(w, row.height())), head_bg);
header_column(app, ui, track, col, Rect::from_min_size(pos2(x, row.min.y), vec2(w, row.height())), head_bg);
x += w;
}
let main_h = track.height.points();
let lane = lane_rect(&track, row, tl);
let lane = lane_rect(track, row, tl);
ui.painter().rect_filled(head, 0.0, head_bg);
ui.painter().rect_filled(Rect::from_min_size(head.min, vec2(STRIP_W, head.height())), 0.0, rgb(track.color));
ui.painter().line_segment([pos2(head.min.x, row.max.y + 0.5), pos2(tl.max.x, row.max.y + 0.5)], Stroke::new(1.0, t.border));
ui.painter().hline(head.min.x + STRIP_W..=head.max.x, head.min.y + 0.5, Stroke::new(1.0, t.edge_light));
track_header(app, ui, &track, head, selected, head_bg);
track_header(app, ui, track, head, selected, head_bg);
// Lane.
ui.painter().vline(tl.min.x - 0.5, row.y_range(), Stroke::new(1.0, t.border));
let painter = ui.painter().with_clip_rect(lane.intersect(ui.clip_rect()));
painter.rect_filled(lane, 0.0, lane_bg);
let s = app.engine.session().clone();
let s = app.engine.session_arc();
// Shown grid lines go over the clips; otherwise the bar lines stay behind them.
if !s.edit.grid_lines {
grid_lines(&painter, &s, tl, lane);
@@ -791,19 +803,19 @@ fn track_row(app: &mut SoundApp, ui: &mut Ui, id: TrackId, row: Rect, tl: Rect,
if !clip.range().overlaps(&view) {
continue;
}
draw_clip(app, &painter, &s, &track, clip, tl, lane, auto_view.is_some());
draw_clip(app, &painter, &s, track, clip, tl, lane, auto_view.is_some());
}
draw_live_take(app, &painter, &s, &track, tl, lane);
crate::video_track::draw_lane(app, &painter, &s, &track, tl, lane);
draw_live_take(app, &painter, &s, track, tl, lane);
crate::video_track::draw_lane(app, &painter, &s, track, tl, lane);
if s.edit.grid_lines {
grid_lines(&painter, &s, tl, lane);
}
if let Some(p) = &auto_view {
draw_automation(&painter, &s, &track, p, tl, lane);
draw_automation(&painter, &s, track, p, tl, lane);
}
if track.kind == TrackKind::Master || track.kind == TrackKind::Aux || track.kind == TrackKind::Vca {
let p = auto_view.clone().unwrap_or(AutoParam::Volume);
draw_automation(&painter, &s, &track, &p, tl, lane);
draw_automation(&painter, &s, track, &p, tl, lane);
}
// Selection highlight on this track.
let sel = s.edit.selection;
@@ -814,16 +826,16 @@ fn track_row(app: &mut SoundApp, ui: &mut Ui, id: TrackId, row: Rect, tl: Rect,
painter.rect_filled(Rect::from_min_max(pos2(x0, lane.min.y), pos2(x1, lane.max.y)), 0.0, t.selection);
}
}
lane_interaction(app, ui, &track, lane, tl);
lane_interaction(app, ui, track, lane, tl);
if track.view == "playlists" {
playlist_lanes(app, ui, &track, row, main_h, tl);
playlist_lanes(app, ui, track, row, main_h, tl);
}
}
/// Alternate playlists as lanes under the main one; clicking a clip promotes it (comping).
fn playlist_lanes(app: &mut SoundApp, ui: &mut Ui, track: &Track, row: Rect, main_h: f32, tl: Rect) {
let t = Tokens::current();
let s = app.engine.session().clone();
let s = app.engine.session_arc();
let mut y = row.min.y + main_h;
for (i, pl) in track.playlists.iter().enumerate() {
if i == track.active_playlist {
@@ -1781,7 +1793,7 @@ fn lane_interaction(app: &mut SoundApp, ui: &mut Ui, track: &Track, lane: Rect,
if resp.dnd_hover_payload::<crate::DragSource>().is_some() {
ui.painter().rect_stroke(lane, 0.0, Stroke::new(2.0, Tokens::current().accent), StrokeKind::Inside);
}
let s = app.engine.session().clone();
let s = app.engine.session_arc();
let tool = s.edit.tool;
let mods = ui.input(|i| i.modifiers);
let Some(p) = resp.interact_pointer_pos().or_else(|| resp.hover_pos()) else { return };
+1 -1
View File
@@ -118,7 +118,7 @@ pub fn show(app: &mut SoundApp, ui: &mut Ui) {
p["clips"] = json!([cid.0]);
let _ = app.run(id, p);
}
let s = app.engine.session().clone();
let s = app.engine.session_arc();
let sr = s.sample_rate;
let base_tick = s.tempo.samples_to_ticks(clip.start, sr);
let len_ticks = (s.tempo.samples_to_ticks(clip.end(), sr) - base_tick).max(1);
+8 -7
View File
@@ -122,7 +122,8 @@ pub(crate) fn route_popup(app: &mut SoundApp, resp: &egui::Response, id: TrackId
fn strip(app: &mut SoundApp, ui: &mut Ui, id: TrackId, snap: Option<&MeterSnapshot>) {
let t = Tokens::current();
let Some(track) = app.engine.session().track(id).cloned() else { return };
let session = app.engine.session_arc();
let Some(track) = session.track(id) else { return };
let narrow = app.ui.narrow_mix;
let w = strip_width(narrow);
let h = ui.available_height().max(560.0);
@@ -166,8 +167,8 @@ fn strip(app: &mut SoundApp, ui: &mut Ui, id: TrackId, snap: Option<&MeterSnapsh
let sr = Rect::from_min_size(pos2(x0 + 2.0, sec.min.y + 16.0), vec2(inner_w - 4.0, 15.0));
let slot = track.mixer.inserts.iter().position(|i| i.as_ref().is_some_and(|i| plugin_info(&i.plugin).is_some_and(|p| p.is_instrument)));
match slot {
_ if track.kind == TrackKind::Instrument => instrument_slot(app, ui, &track, sr),
Some(k) => insert_slot(app, ui, &track, k, sr),
_ if track.kind == TrackKind::Instrument => instrument_slot(app, ui, track, sr),
Some(k) => insert_slot(app, ui, track, k, sr),
None if track.kind == TrackKind::Midi => {
ui.painter().text(sr.center(), Align2::CENTER_CENTER, "MIDI in: all", regular(9.0), t.text_dim);
}
@@ -202,9 +203,9 @@ fn strip(app: &mut SoundApp, ui: &mut Ui, id: TrackId, snap: Option<&MeterSnapsh
for k in 0..5 {
let sr = Rect::from_min_size(pos2(x0 + 2.0, sec.min.y + 15.0 + k as f32 * 17.0), vec2(inner_w - 4.0, 15.0));
if is_send {
send_slot(app, ui, &track, off + k, sr);
send_slot(app, ui, track, off + k, sr);
} else {
insert_slot(app, ui, &track, off + k, sr);
insert_slot(app, ui, track, off + k, sr);
}
}
y = sec.max.y + 4.0;
@@ -255,7 +256,7 @@ fn strip(app: &mut SoundApp, ui: &mut Ui, id: TrackId, snap: Option<&MeterSnapsh
let in_r = Rect::from_min_size(pos2(x0 + 2.0, sec.min.y + 15.0), vec2(inner_w - 4.0, 16.0));
let out_r = Rect::from_min_size(pos2(x0 + 2.0, sec.min.y + 33.0), vec2(inner_w - 4.0, 16.0));
let mut c = ui.new_child(egui::UiBuilder::new().id_salt(("mix-input", id.0)).max_rect(in_r));
let resp = selector_box(&mut c, in_r.width(), in_r.height(), &input_name(app, &track), t.text);
let resp = selector_box(&mut c, in_r.width(), in_r.height(), &input_name(app, track), t.text);
route_popup(app, &resp, id, true);
let mut c = ui.new_child(egui::UiBuilder::new().id_salt(("mix-output", id.0)).max_rect(out_r));
let resp = selector_box(&mut c, out_r.width(), out_r.height(), &route_name(app, &track.mixer.output), t.text);
@@ -298,7 +299,7 @@ fn strip(app: &mut SoundApp, ui: &mut Ui, id: TrackId, snap: Option<&MeterSnapsh
let _ = selector_box(&mut c, gr.width(), gr.height(), &gname, t.text_dim);
y += 22.0;
// Pan: a surround panner when the output is multichannel, else the stereo knobs.
let out_fmt = output_format(app, &track);
let out_fmt = output_format(app, track);
let surround_out =
out_fmt.channels() > 2 && !out_fmt.is_ambisonic() && !matches!(track.kind, TrackKind::Master | TrackKind::Vca) && track.channels() <= 2;
if surround_out {