Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
d162910
Meters: Peak Hold and Clip Indication can be set to 3 Seconds, Infini…
dotCooCoo Oct 24, 2026
f912393
Clip > Unloop... opens the Unloop dialog: Remove or Flatten
dotCooCoo Oct 24, 2026
7b7e63a
Clip > Unloop... runs the command when no selected clip is looped
dotCooCoo Oct 24, 2026
afc93c1
Strip Silence: the sliders move a tenth as far while Ctrl or Command …
dotCooCoo Oct 25, 2026
a9ef108
Plugins: parameter sliders move a tenth as far while Ctrl or Command …
dotCooCoo Oct 25, 2026
5c42c6c
Plugins: Alt-click returns a parameter to its default value
dotCooCoo Oct 25, 2026
a90f968
Merge main into feat/plugin-parameter-default-reset
dotCooCoo Oct 25, 2026
6287acf
Plugins: FineSlider::default_value describes what a held Alt does
dotCooCoo Oct 25, 2026
82e3ce0
Merge main into feat/strip-silence-fine-sliders
dotCooCoo Oct 25, 2026
9774947
Merge main into feat/strip-silence-fine-sliders
dotCooCoo Oct 25, 2026
eaf0634
Merge commit '9774947' into feat/plugin-parameter-fine-sliders
dotCooCoo Oct 25, 2026
df1d064
Merge main into feat/plugin-parameter-fine-sliders
dotCooCoo Oct 25, 2026
8cec3da
Merge main into feat/plugin-parameter-fine-sliders
dotCooCoo Oct 25, 2026
92a5ce9
Dialogs: Enter on a focused Cancel or Don't Save no longer runs OK
dotCooCoo Oct 25, 2026
812282e
Merge main into feat/meter-peak-hold-clip-indication
dotCooCoo Oct 25, 2026
8a8fdca
Merge main into feat/unloop-dialog
dotCooCoo Oct 25, 2026
bf62aa6
Sends: Ctrl or Command gives fine adjustment on a send's level, its s…
dotCooCoo Oct 25, 2026
5f1335b
Merge main into feat/plugin-parameter-default-reset
dotCooCoo Oct 25, 2026
84e591e
Zoom Toggle: Alt+Shift+E and Alt-click cancel it and keep the current…
dotCooCoo Oct 26, 2026
cb8014f
Merge main into feat/plugin-parameter-fine-sliders
dotCooCoo Oct 26, 2026
e142cb2
Merge #1056: Strip Silence: the sliders move a tenth as far while Ctr…
dotCooCoo Oct 26, 2026
e5542cf
Merge #1059: Plugins: parameter sliders move a tenth as far while Ctr…
dotCooCoo Oct 26, 2026
ec19224
Merge #1081: Plugins: Alt-click returns a parameter to its default value
dotCooCoo Oct 26, 2026
6bef8f0
Merge #1098: Sends: Ctrl or Command gives fine adjustment on a send's…
dotCooCoo Oct 26, 2026
ab8f0a4
Merge #1036: Clip > Unloop... opens the Unloop dialog: Remove or Flatten
dotCooCoo Oct 26, 2026
c11e94a
Merge #1022: Meters: Peak Hold and Clip Indication can be set to 3 Se…
dotCooCoo Oct 26, 2026
18ca57d
Merge #1101: Zoom Toggle: Alt+Shift+E and Alt-click cancel it and kee…
dotCooCoo Oct 26, 2026
6c7491e
Merge main into feat/send-fine-adjustment
dotCooCoo Oct 26, 2026
cf4fe7a
Merge main into feat/send-fine-adjustment
dotCooCoo Oct 26, 2026
a447742
Merge #1098: Sends: Ctrl or Command gives fine adjustment on a send's…
dotCooCoo Oct 26, 2026
5b604a5
Merge main and add default reset and fine adjustment to Clip Effects …
dotCooCoo Oct 27, 2026
8c3bcc2
Merge main and preserve plugin, send, metering, and zoom controls
dotCooCoo Oct 27, 2026
ca3c984
Merge the updated plugin parameter reset branch
dotCooCoo Oct 27, 2026
8176230
Read send destinations only while the menu is open
dotCooCoo Oct 27, 2026
1125a5f
Add fine dragging and default reset to all AudioSuite sliders
dotCooCoo Oct 27, 2026
1161d0c
Add fine dragging and default reset to all AudioSuite sliders
dotCooCoo Oct 27, 2026
8734559
Read send destinations only while the menu is open
dotCooCoo Oct 27, 2026
817561e
Add fine dragging and default reset to all AudioSuite sliders
dotCooCoo Oct 27, 2026
ce2a691
Merge main into feat/meter-peak-hold-clip-indication
dotCooCoo Oct 28, 2026
97123fa
Merge main into feat/unloop-dialog
dotCooCoo Oct 28, 2026
4534c0c
Tests: follow main's PathBuf pickers and FileFormat in the focused-bu…
dotCooCoo Oct 28, 2026
1480399
Merge main into feat/strip-silence-fine-sliders
dotCooCoo Oct 28, 2026
f554274
Merge main into feat/plugin-parameter-fine-sliders
dotCooCoo Oct 28, 2026
1ffc164
Merge commit 'refs/check/mw-soundcraft-main' into feat/zoom-toggle-ca…
dotCooCoo Oct 28, 2026
dcdcca4
Zoom Toggle: a cancel with Zoom Toggle off keeps the scroll to restore
dotCooCoo Oct 28, 2026
3869492
Tests: pressing Ctrl during a value-box drag keeps the value
dotCooCoo Oct 28, 2026
245523a
Bounce Mix: Cancel starts no bounce; fine adjust on more AudioSuite s…
dotCooCoo Oct 28, 2026
633726f
Dialogs: Enter presses the default button after the dialog opens
dotCooCoo Oct 28, 2026
3005888
Tests: give the Enter-on-Cancel bounce test its own scratch folder
dotCooCoo Oct 28, 2026
e83295d
Dialogs: a replaced dialog skips the opening Enter, and a held Enter …
dotCooCoo Oct 28, 2026
69d336b
Merge commit 'refs/check/bf-sc-main' into feat/strip-silence-fine-sli…
dotCooCoo Oct 28, 2026
167adfb
Merge #1056: Strip Silence: the sliders move a tenth as far while Ctr…
dotCooCoo Oct 28, 2026
7e52d63
Merge #1059: Plugins: parameter sliders move a tenth as far while Ctr…
dotCooCoo Oct 28, 2026
bed1aaa
Merge #1036: Clip > Unloop... opens the Unloop dialog: Remove or Flatten
dotCooCoo Oct 28, 2026
dcfb124
Merge #1022: Meters: Peak Hold and Clip Indication can be set to 3 Se…
dotCooCoo Oct 28, 2026
460e7b0
Merge #1101: Zoom Toggle: Alt+Shift+E and Alt-click cancel it and kee…
dotCooCoo Oct 28, 2026
1ed2c67
Meters: import MeterHold in app/audio.rs after the explicit imports o…
dotCooCoo Oct 28, 2026
fb05978
Merge #1059: Plugins: parameter sliders move a tenth as far while Ctr…
dotCooCoo Oct 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

**Stage: pre-alpha** · next: alpha, ~5% and ~110–180 h away (the alpha gate's three failing workflows)

> **Last reviewed:** 2026-10-24 · **Last updated:** 2026-10-25 · **Change:** trivial (side panels in dock columns, Auto Pan stereo balance, Clip List Auto Rename, Strip Silence rectangles, Strip Silence Rename, Strip Silence Extract and Separate, Clip Looping dialog, AudioSuite Vari-Fi speed ramps, Auto Pan plugin, Subharmonic plugin, Ring Modulator plugin, Float32 file headroom) · **Target:** Avid Pro Tools Ultimate 2026.4.1
> **Last reviewed:** 2026-10-24 · **Last updated:** 2026-10-27 · **Change:** trivial (side panels in dock columns, Cancel Zoom Toggle, Auto Pan stereo balance, fine adjustment of sends, Alt-click returns a plugin parameter to its default, fine adjustment on plugin parameter sliders, Strip Silence fine adjustment, Unloop dialog, Peak Hold and Clip Indication options, Clip List Auto Rename, Strip Silence rectangles, Strip Silence Rename, Strip Silence Extract and Separate, Clip Looping dialog, AudioSuite Vari-Fi speed ramps, Auto Pan plugin, Subharmonic plugin, Ring Modulator plugin, Float32 file headroom) · **Target:** Avid Pro Tools Ultimate 2026.4.1

SoundCraft is a clean-room, pure-Rust digital audio workstation that aims at full parity with
Avid Pro Tools, and then beyond it: faster, open, scriptable and agent-controllable. This page is
Expand Down Expand Up @@ -123,7 +123,14 @@ mutated and truncated movies. Field reports from the first public week are track
## Progress log

- **2026-10-25:** Tracks and Groups and the Clip List sit in dock columns that run the Edit window's full height at its left and right edges, with the MIDI Editor under the session view between them; Mix docks Tracks and Groups at its left edge. A panel dragged onto a dock group joins it as a tab, stacks above or below it, or makes a new column; one dragged to an empty window edge makes a dock there. A list with no saved place reopens in its dock, and makes the dock again when it is gone. Preferences and window configurations saved with the earlier split-frame layouts load with the new default layouts and keep their other settings. No readiness percentage change.
- **2026-10-25:** Alt+Shift+E (`view.zoom_toggle_cancel`) and an Alt-click on the Zoom Toggle button cancel Zoom Toggle and keep the current view: the zoom, the scroll and the track heights stay as they are, and the button is no longer lit. The next Zoom Toggle zooms in from that view and goes back to it. No readiness percentage change.
- **2026-10-25:** Auto Pan moves the balance of a stereo track: the left channel's level falls as the position moves right and the right channel's as it moves left, at constant power, and each channel stays on its own side. At the center the track passes unchanged, so a wide or out-of-phase stereo image keeps its width, and the envelope follower follows the louder channel. No readiness percentage change.
- **2026-10-25:** A vertical drag on a send's level moves it as finely with Ctrl (Command on a Mac) held as with Shift, as a fader's Ctrl-drag does. The Level and Pan sliders of a send's options menu, the Send window's pan knob and the tracks' pan knobs move a tenth as far for the same drag while Ctrl (Command on a Mac) is held. No readiness percentage change.
- **2026-10-24:** An Alt-click (Option-click on a Mac) on a parameter in the plugin window, the Clip Effects window or an AudioSuite plugin's window returns it to its default value, on a slider, a slider's value box or a choice list, as an Alt-click returns a fader to 0 dB. In the plugin window and the Clip Effects window the reset is one undo step. While Alt is held, a drag on a slider leaves the value where it is. No readiness percentage change.
- **2026-10-24:** The parameter sliders of the plugin window, the Clip Effects window and AudioSuite plugins move a tenth as far for the same drag while Ctrl (Command on a Mac) is held, as the Strip Silence sliders do. Logarithmic sliders move a tenth as far along the slider, and whole-number sliders stay on whole numbers. No readiness percentage change.
- **2026-10-24:** The Strip Silence window's four sliders move a tenth as far for the same drag while Ctrl (Command on a Mac) is held, on the slider and on its value box. Pressing or releasing the key during a drag leaves the value where it is. The Threshold and Clip Start Pad boxes show one decimal place. No readiness percentage change.
- **2026-10-24:** Clip › Unloop… opens the Unloop dialog. Remove deletes the loop iterations and keeps the source clip, and Flatten keeps each loop iteration as its own clip. No readiness percentage change.
- **2026-10-24:** Setup › Metering… sets Peak Hold and Clip Indication to 3 Seconds, Infinite or None, as `ui.peak_hold` and `ui.clip_indication`, kept with the preferences. Meters held a peak for 2 s and kept a clip lit until cleared; the defaults are now 3 Seconds and Infinite. Track › Clear All Clip Indicators also clears held peaks. No readiness percentage change.
- **2026-10-24:** Clip List › Auto Rename (`clip.auto_rename`) sets the names that editing gives the clips it separates from the selected clips: a name, a starting number, a number of places (zeros before the number) and a suffix. The piece after each cut takes the next name, the piece before it keeps its name, and all the pieces count up one number sequence. Pieces that Strip Silence names with its own Rename fields, or removes, take no Auto Rename name. Copied clips keep their scheme when they are pasted, saved sessions hold the schemes, and Import Session Data brings them along. No readiness percentage change.
- **2026-10-24:** While the Strip Silence window is open, the Edit window draws a rectangle around each range that Strip would keep on the selected tracks, padded by Clip Start Pad and Clip End Pad, and the rectangles follow the sliders as they move. They are removed when the window closes. `edit.strip_silence_preview` returns the same ranges without changing the session. No readiness percentage change.
- **2026-10-24:** The Strip Silence window has a Rename button. It opens Rename Selected Clips, where a name, a starting number, a count of zeros and a suffix set the names of the clips that Strip, Extract and Separate leave in the selection (for example SFX023.Reel1, SFX024.Reel1). The app keeps the scheme for later uses of Strip Silence until the app quits, or until Clear and then OK empty it. No readiness percentage change.
Expand Down Expand Up @@ -177,7 +184,14 @@ mutated and truncated movies. Field reports from the first public week are track
| Date | Change | Summary |
|---|---|---|
| 2026-10-25 | trivial | Progress log: side panels in dock columns; readiness estimates unchanged |
| 2026-10-25 | trivial | Progress log: Cancel Zoom Toggle; readiness estimates unchanged |
| 2026-10-25 | trivial | Progress log: Auto Pan stereo balance; readiness estimates unchanged |
| 2026-10-25 | trivial | Progress log: fine adjustment of sends; readiness estimates unchanged |
| 2026-10-24 | trivial | Progress log: Alt-click returns a plugin parameter to its default; readiness estimates unchanged |
| 2026-10-24 | trivial | Progress log: fine adjustment on plugin parameter sliders; readiness estimates unchanged |
| 2026-10-24 | trivial | Progress log: Strip Silence fine adjustment; readiness estimates unchanged |
| 2026-10-24 | trivial | Progress log: Unloop dialog; readiness estimates unchanged |
| 2026-10-24 | trivial | Progress log: Peak Hold and Clip Indication options; readiness estimates unchanged |
| 2026-10-24 | trivial | Progress log: Clip List Auto Rename; readiness estimates unchanged |
| 2026-10-24 | trivial | Progress log: Strip Silence rectangles; readiness estimates unchanged |
| 2026-10-24 | trivial | Progress log: Strip Silence Rename; readiness estimates unchanged |
Expand Down
1 change: 1 addition & 0 deletions crates/engine/src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ mod panic_probe {
}
}

pub use clip_more::is_looped;
pub use file::bounce_job;

pub(crate) use edit::remember_selection;
Expand Down
5 changes: 5 additions & 0 deletions crates/engine/src/cmd/clip_more.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ fn loop_chain(s: &Session, t: TrackId, id: ClipId) -> Vec<ClipId> {
chain
}

/// Whether a clip belongs to a loop that `clip.unloop` changes: two or more back-to-back copies.
pub fn is_looped(s: &Session, id: ClipId) -> bool {
s.find_clip(id).is_some_and(|(t, _)| loop_chain(s, t, id).len() >= 2)
}

fn unloop(e: &mut Engine, p: &Value) -> Result<Value> {
let id = "clip.unloop";
let remove = match str_param(p, "mode") {
Expand Down
32 changes: 30 additions & 2 deletions crates/engine/src/cmd/zoom_toggle.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! Zoom Toggle: zoom in on the edit selection, then back out to the view before.
//! Zoom Toggle: zoom in on the edit selection, then back out to the view before. Cancel Zoom Toggle turns it off
//! and keeps the current view.
use super::{CommandSpec, always, f64_or};
use soundcraft_model::{TrackHeight, ZoomToggle};
use soundcraft_time::MAX_POSITION;
Expand All @@ -7,7 +8,16 @@ use crate::{Engine, Result, cmd};
use serde_json::{Value, json};

pub fn specs() -> Vec<CommandSpec> {
vec![cmd!(noundo "view.zoom_toggle", "Zoom Toggle", [], None, "{width_px?: 1200, height_px?: 600}", always, toggle)]
vec![
cmd!(noundo "view.zoom_toggle", "Zoom Toggle", [], None, "{width_px?: 1200, height_px?: 600}", always, toggle),
cmd!(noundo "view.zoom_toggle_cancel", "Cancel Zoom Toggle", [], Some("Alt+Shift+E"), "{}", always, cancel),
]
}

/// Forget the view Zoom Toggle would go back to, leaving the zoom, scroll and track heights as they are.
fn cancel(e: &mut Engine, _: &Value) -> Result<Value> {
let was_on = e.session_mut().edit.zoom_toggle.take().is_some();
Ok(json!({"on": false, "canceled": was_on}))
}

fn toggle(e: &mut Engine, p: &Value) -> Result<Value> {
Expand Down Expand Up @@ -73,6 +83,24 @@ mod tests {
assert_eq!(view(&e), (500.0, 1000, vec![Medium, Fit(299), Medium]));
}

#[test]
fn cancel_keeps_the_zoomed_view_and_the_next_toggle_zooms_in_again() {
let mut e = engine(json!({"tracks": ["Audio 1", "Audio 3"], "start": 48_000, "end": 96_000}));
e.execute("view.zoom_toggle", &json!({"width_px": 1000.0, "height_px": 500.0})).unwrap();
let zoomed = view(&e);
assert_eq!(e.execute("view.zoom_toggle_cancel", &json!({})).unwrap(), json!({"on": false, "canceled": true}));
assert_eq!(e.session().edit.zoom_toggle, None);
assert_eq!(view(&e), zoomed, "cancel keeps the view Zoom Toggle zoomed in to");
assert_eq!(e.execute("view.zoom_toggle_cancel", &json!({})).unwrap(), json!({"on": false, "canceled": false}));
assert_eq!(view(&e), zoomed);
e.execute("view.zoom_set", &json!({"samples_per_px": 200.0})).unwrap();
assert_eq!(e.execute("view.zoom_toggle", &json!({"width_px": 1000.0, "height_px": 500.0})).unwrap(), json!({"on": true}));
e.execute("view.zoom_toggle", &json!({})).unwrap();
assert_eq!(view(&e), (200.0, 48_000, vec![Fit(249), Medium, Fit(249)]), "the toggle goes back to the view it saw after the cancel");
let spec = crate::cmd::command_specs().iter().find(|c| c.id == "view.zoom_toggle_cancel").unwrap();
assert_eq!(spec.shortcut, Some("Alt+Shift+E"));
}

#[test]
fn hostile_sizes_and_stored_views_stay_in_range() {
let mut e = engine(json!({"tracks": ["Audio 1"], "start": 0, "end": MAX_POSITION}));
Expand Down
2 changes: 2 additions & 0 deletions crates/ui-egui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ mod input;
mod playback;
mod plugins;

#[cfg(test)]
pub(crate) use audio::feed_meter;
#[cfg(test)]
pub(crate) use playback::automation_step_due;
#[cfg(test)]
Expand Down
36 changes: 28 additions & 8 deletions crates/ui-egui/src/app/audio.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::{MeterDisplay, OutputTrouble, Player, SoundApp, Trouble};
use crate::{METER_HOLD_SECS, MeterHold};

impl SoundApp {
/// Whether the meters are live: while playing, and while armed or input-monitored tracks are heard.
Expand Down Expand Up @@ -125,29 +126,48 @@ impl SoundApp {
}
}

pub(super) fn feed_meter(d: &mut MeterDisplay, peak: [f32; 2], gr: f32, dt: f32) {
// Instant attack, ~26 dB/s release, 2 s peak hold.
/// Instant attack and ~26 dB/s release over `dt`; `holds` is the (Peak Hold, Clip Indication)
/// setting, timed against `now`, the frame time in egui seconds.
pub(crate) fn feed_meter(d: &mut MeterDisplay, peak: [f32; 2], gr: f32, dt: f32, now: f64, holds: (MeterHold, MeterHold)) {
let (peak_hold, clip_indication) = holds;
let fall = 10f32.powf(-26.0 * dt / 20.0);
let mut clipped = false;
for c in 0..2 {
let v = peak.get(c).copied().unwrap_or(0.0);
let lvl = d.level.get(c).copied().unwrap_or(0.0);
let nv = if v >= lvl { v } else { (lvl * fall).max(v) };
if let Some(x) = d.level.get_mut(c) {
*x = if nv < 1e-5 { 0.0 } else { nv };
}
if v >= d.hold.get(c).copied().unwrap_or(0.0) {
if v > 0.0 && v >= d.hold.get(c).copied().unwrap_or(0.0) {
if let Some(h) = d.hold.get_mut(c) {
*h = v;
}
d.hold_age = 0.0;
if let Some(t) = d.hold_at.get_mut(c) {
*t = now;
}
}
if v >= 1.0 {
d.clip = true;
clipped = true;
}
let hold_over = match peak_hold {
MeterHold::ThreeSeconds => now - d.hold_at.get(c).copied().unwrap_or(now) > METER_HOLD_SECS,
MeterHold::Infinite => false,
MeterHold::Off => true,
};
if hold_over && let (Some(h), Some(l)) = (d.hold.get_mut(c), d.level.get(c)) {
*h = *l;
}
}
d.hold_age += dt;
if d.hold_age > 2.0 {
d.hold = d.level;
if clipped {
d.clip = true;
d.clip_at = now;
} else {
d.clip &= match clip_indication {
MeterHold::ThreeSeconds => now - d.clip_at <= METER_HOLD_SECS,
MeterHold::Infinite => true,
MeterHold::Off => false,
};
}
d.gr = if gr > d.gr { gr } else { d.gr * fall };
}
8 changes: 7 additions & 1 deletion crates/ui-egui/src/app/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,14 @@ impl SoundApp {
// Re-selecting a scrolling mode resumes the playhead follow.
self.edit_layout.follow_hold = false;
}
if id == "view.zoom_toggle_cancel" && v["canceled"] == true {
self.edit_layout.forget_zoom_back();
}
if id == "track.clear_clip_indicators" {
self.meters.values_mut().chain([&mut self.main_meter]).for_each(|m| m.clip = false);
self.meters.values_mut().chain([&mut self.main_meter]).for_each(|m| {
m.clip = false;
m.hold = m.level;
});
}
Ok(v)
}
Expand Down
11 changes: 7 additions & 4 deletions crates/ui-egui/src/app/playback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,9 +364,10 @@ impl SoundApp {
self.last_meter_poll = now;
}
let snap = &self.meter_snapshot;
let holds = (self.ui.peak_hold, self.ui.clip_indication);
for (id, m) in &snap.tracks {
let d = self.meters.entry(*id).or_default();
feed_meter(d, m.peak, m.gain_reduction_db, dt);
feed_meter(d, m.peak, m.gain_reduction_db, dt, now, holds);
if m.plugin_failures.count != d.plugin_failures {
d.plugin_failures = m.plugin_failures.count;
if let Some(msg) = failed_plugin_status(self.engine.session(), *id, m.plugin_failures.last) {
Expand All @@ -375,7 +376,7 @@ impl SoundApp {
}
}
let main = snap.main.peak;
feed_meter(&mut self.main_meter, main, 0.0, dt);
feed_meter(&mut self.main_meter, main, 0.0, dt, now, holds);
if ended {
self.stop_play();
}
Expand All @@ -394,10 +395,12 @@ impl SoundApp {
}
}
if !self.meters_live() {
let holds = (self.ui.peak_hold, self.ui.clip_indication);
let now = self.uptime;
for d in self.meters.values_mut() {
feed_meter(d, [0.0, 0.0], 0.0, dt);
feed_meter(d, [0.0, 0.0], 0.0, dt, now, holds);
}
feed_meter(&mut self.main_meter, [0.0, 0.0], 0.0, dt);
feed_meter(&mut self.main_meter, [0.0, 0.0], 0.0, dt, now, holds);
}
}
}
Loading
Loading