316 lines
17 KiB
Rust
316 lines
17 KiB
Rust
use super::{decode_region_audio, Editor};
|
|
use crate::engine::EngineCommand;
|
|
use crate::history::EditCommand;
|
|
use crate::waveform::WaveformPeaks;
|
|
|
|
impl Editor {
|
|
pub(crate) fn perform_undo(&mut self) {
|
|
let cmd = match self.history.pop_undo() {
|
|
Some(c) => c,
|
|
None => return,
|
|
};
|
|
match cmd {
|
|
EditCommand::MoveRegion { track_index, region_id, old_start, new_start, old_start_sample, new_start_sample } => {
|
|
debug_log!("undo MoveRegion: region={} track={}", region_id, track_index);
|
|
if let Some(track) = self.tracks.get_mut(track_index) {
|
|
if let Some(region) = track.regions.iter_mut().find(|r| r.id == region_id) {
|
|
region.start_time = old_start;
|
|
region.start_sample = old_start_sample;
|
|
}
|
|
}
|
|
self.history.push_redo(EditCommand::MoveRegion {
|
|
track_index, region_id,
|
|
old_start: new_start,
|
|
new_start: old_start,
|
|
old_start_sample: new_start_sample,
|
|
new_start_sample: old_start_sample,
|
|
});
|
|
}
|
|
EditCommand::MoveRegionAcrossTracks { region_id, old_track, new_track, old_start, new_start, old_start_sample, new_start_sample } => {
|
|
debug_log!("undo cross-track move: region={} track {}->{}",
|
|
region_id, new_track, old_track);
|
|
if new_track < self.tracks.len() {
|
|
if let Some(pos) = self.tracks[new_track].regions.iter().position(|r| r.id == region_id) {
|
|
let mut region = self.tracks[new_track].regions.remove(pos);
|
|
region.start_time = old_start;
|
|
region.start_sample = old_start_sample;
|
|
|
|
if old_track < self.tracks.len() {
|
|
if let Some(ref engine) = self.engine {
|
|
engine.send(EngineCommand::UnloadRegionAudio { region_id });
|
|
if let Some(ref audio_file) = region.audio_file {
|
|
let abs_path = self.project_path.join(audio_file);
|
|
if let Some((audio_l, audio_r)) = decode_region_audio(&abs_path, self.project_config.sample_rate) {
|
|
engine.send(EngineCommand::LoadRegionAudio {
|
|
bus_name: self.tracks[old_track].bus_name.clone(),
|
|
region_id,
|
|
start_sample: old_start_sample,
|
|
audio_l,
|
|
audio_r,
|
|
fade_in_samples: region.fade_in_samples,
|
|
fade_out_samples: region.fade_out_samples,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
self.tracks[old_track].regions.push(region);
|
|
}
|
|
}
|
|
}
|
|
self.history.push_redo(EditCommand::MoveRegionAcrossTracks {
|
|
region_id,
|
|
old_track: new_track,
|
|
new_track: old_track,
|
|
old_start: new_start,
|
|
new_start: old_start,
|
|
old_start_sample: new_start_sample,
|
|
new_start_sample: old_start_sample,
|
|
});
|
|
}
|
|
EditCommand::DeleteRegion { track_index, region } => {
|
|
debug_log!("undo DeleteRegion: region={} track={}", region.id, track_index);
|
|
let region_clone = region.clone();
|
|
if track_index < self.tracks.len() {
|
|
if let Some(ref audio_file) = region.audio_file {
|
|
let abs_path = self.project_path.join(audio_file);
|
|
if let Some((audio_l, audio_r)) = decode_region_audio(&abs_path, self.project_config.sample_rate) {
|
|
let start = (region.start_sample as usize).min(audio_l.len());
|
|
let end = (start + region.length_samples as usize).min(audio_l.len());
|
|
let sl = &audio_l[start..end];
|
|
let sr = &audio_r[start.min(audio_r.len())..end.min(audio_r.len())];
|
|
self.waveform_cache.insert(
|
|
region.id,
|
|
WaveformPeaks::from_stereo(sl, sr),
|
|
);
|
|
if let Some(ref engine) = self.engine {
|
|
engine.send(EngineCommand::LoadRegionAudio {
|
|
bus_name: self.tracks[track_index].bus_name.clone(),
|
|
region_id: region.id,
|
|
start_sample: region.start_sample,
|
|
audio_l: sl.to_vec(),
|
|
audio_r: sr.to_vec(),
|
|
fade_in_samples: region.fade_in_samples,
|
|
fade_out_samples: region.fade_out_samples,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
self.tracks[track_index].regions.push(region);
|
|
}
|
|
self.history.push_redo(EditCommand::DeleteRegion {
|
|
track_index,
|
|
region: region_clone,
|
|
});
|
|
}
|
|
EditCommand::DeleteTrack { index, track } => {
|
|
debug_log!("undo DeleteTrack: index={}", index);
|
|
let track_clone = track.clone();
|
|
if index <= self.tracks.len() {
|
|
if let Some(ref engine) = self.engine {
|
|
engine.send(EngineCommand::CreateBus {
|
|
name: track.bus_name.clone(),
|
|
is_midi: track.track_type == crate::track::TrackType::Midi,
|
|
});
|
|
}
|
|
self.tracks.insert(index, track);
|
|
}
|
|
self.history.push_redo(EditCommand::DeleteTrack {
|
|
index,
|
|
track: track_clone,
|
|
});
|
|
}
|
|
EditCommand::CreateTrack { index } => {
|
|
debug_log!("undo CreateTrack: index={}", index);
|
|
if index < self.tracks.len() {
|
|
let removed = self.tracks.remove(index);
|
|
if let Some(ref engine) = self.engine {
|
|
engine.send(EngineCommand::RemoveBus {
|
|
name: removed.bus_name.clone(),
|
|
});
|
|
}
|
|
self.history.push_redo(EditCommand::CreateTrack { index });
|
|
}
|
|
}
|
|
EditCommand::DuplicateTrack { source_index, new_index } => {
|
|
debug_log!("undo DuplicateTrack: source={} new={}", source_index, new_index);
|
|
if new_index < self.tracks.len() {
|
|
let removed = self.tracks.remove(new_index);
|
|
if let Some(ref engine) = self.engine {
|
|
engine.send(EngineCommand::RemoveBus {
|
|
name: removed.bus_name.clone(),
|
|
});
|
|
}
|
|
}
|
|
self.history.push_redo(EditCommand::DuplicateTrack { source_index, new_index });
|
|
}
|
|
EditCommand::SplitRegion { track_index, original_id, original_region, left_id, right_id, split_sample } => {
|
|
debug_log!("undo SplitRegion: original={} track={}", original_id, track_index);
|
|
if let Some(track) = self.tracks.get_mut(track_index) {
|
|
track.regions.retain(|r| r.id != left_id && r.id != right_id);
|
|
self.waveform_cache.remove(&left_id);
|
|
self.waveform_cache.remove(&right_id);
|
|
if let Some(ref engine) = self.engine {
|
|
engine.send(EngineCommand::UnloadRegionAudio { region_id: left_id });
|
|
engine.send(EngineCommand::UnloadRegionAudio { region_id: right_id });
|
|
}
|
|
|
|
let orig_clone = original_region.clone();
|
|
if let Some(ref audio_file) = original_region.audio_file {
|
|
let abs_path = self.project_path.join(audio_file);
|
|
if let Ok(decoder) = crate::codec::XtcDecoder::open(&abs_path) {
|
|
if let Ok((audio_l, audio_r)) = decoder.decode_real(&abs_path) {
|
|
let start = original_region.start_sample as usize;
|
|
let end = (start + original_region.length_samples as usize).min(audio_l.len());
|
|
let sl = &audio_l[start.min(audio_l.len())..end];
|
|
let sr = &audio_r[start.min(audio_r.len())..end.min(audio_r.len())];
|
|
self.waveform_cache.insert(
|
|
original_id,
|
|
WaveformPeaks::from_stereo(sl, sr),
|
|
);
|
|
if let Some(ref engine) = self.engine {
|
|
engine.send(EngineCommand::LoadRegionAudio {
|
|
bus_name: track.bus_name.clone(),
|
|
region_id: original_id,
|
|
start_sample: original_region.start_sample,
|
|
audio_l: audio_l[start.min(audio_l.len())..end].to_vec(),
|
|
audio_r: audio_r[start.min(audio_r.len())..end.min(audio_r.len())].to_vec(),
|
|
fade_in_samples: original_region.fade_in_samples,
|
|
fade_out_samples: original_region.fade_out_samples,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
track.regions.push(original_region);
|
|
self.history.push_redo(EditCommand::SplitRegion {
|
|
track_index,
|
|
original_id,
|
|
original_region: orig_clone,
|
|
left_id,
|
|
right_id,
|
|
split_sample,
|
|
});
|
|
}
|
|
}
|
|
EditCommand::PasteRegions { entries } => {
|
|
debug_log!("undo PasteRegions: {} regions", entries.len());
|
|
let entries_clone = entries.clone();
|
|
for (track_index, region) in &entries {
|
|
if *track_index < self.tracks.len() {
|
|
self.tracks[*track_index].regions.retain(|r| r.id != region.id);
|
|
self.waveform_cache.remove(®ion.id);
|
|
if let Some(ref engine) = self.engine {
|
|
engine.send(EngineCommand::UnloadRegionAudio { region_id: region.id });
|
|
}
|
|
}
|
|
}
|
|
self.history.push_redo(EditCommand::PasteRegions { entries: entries_clone });
|
|
}
|
|
EditCommand::CutRegions { entries } => {
|
|
debug_log!("undo CutRegions: {} regions", entries.len());
|
|
let entries_clone = entries.clone();
|
|
for (track_index, region) in &entries {
|
|
if *track_index < self.tracks.len() {
|
|
if let Some(ref audio_file) = region.audio_file {
|
|
let abs_path = self.project_path.join(audio_file);
|
|
if let Ok(decoder) = crate::codec::XtcDecoder::open(&abs_path) {
|
|
if let Ok((audio_l, audio_r)) = decoder.decode_real(&abs_path) {
|
|
let start = (region.start_sample as usize).min(audio_l.len());
|
|
let end = (start + region.length_samples as usize).min(audio_l.len());
|
|
let sl = &audio_l[start..end];
|
|
let sr = &audio_r[start.min(audio_r.len())..end.min(audio_r.len())];
|
|
self.waveform_cache.insert(
|
|
region.id,
|
|
WaveformPeaks::from_stereo(sl, sr),
|
|
);
|
|
if let Some(ref engine) = self.engine {
|
|
engine.send(EngineCommand::LoadRegionAudio {
|
|
bus_name: self.tracks[*track_index].bus_name.clone(),
|
|
region_id: region.id,
|
|
start_sample: region.start_sample,
|
|
audio_l: sl.to_vec(),
|
|
audio_r: sr.to_vec(),
|
|
fade_in_samples: region.fade_in_samples,
|
|
fade_out_samples: region.fade_out_samples,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
self.tracks[*track_index].regions.push(region.clone());
|
|
}
|
|
}
|
|
self.history.push_redo(EditCommand::CutRegions { entries: entries_clone });
|
|
}
|
|
EditCommand::AudioQuantize { track_index, original_region, result_regions } => {
|
|
if track_index < self.tracks.len() {
|
|
for r in &result_regions {
|
|
self.tracks[track_index].regions.retain(|rr| rr.id != r.id);
|
|
self.waveform_cache.remove(&r.id);
|
|
if let Some(ref engine) = self.engine {
|
|
engine.send(EngineCommand::UnloadRegionAudio { region_id: r.id });
|
|
}
|
|
}
|
|
let orig = original_region.clone();
|
|
if let Some(ref audio_file) = orig.audio_file {
|
|
let abs_path = self.project_path.join(audio_file);
|
|
if let Some((audio_l, audio_r)) = decode_region_audio(&abs_path, self.project_config.sample_rate) {
|
|
let s = (orig.start_sample as usize).min(audio_l.len());
|
|
let e = (s + orig.length_samples as usize).min(audio_l.len());
|
|
let sl = &audio_l[s..e];
|
|
let sr = &audio_r[s.min(audio_r.len())..e.min(audio_r.len())];
|
|
self.waveform_cache.insert(orig.id, WaveformPeaks::from_stereo(sl, sr));
|
|
if let Some(ref engine) = self.engine {
|
|
engine.send(EngineCommand::LoadRegionAudio {
|
|
bus_name: self.tracks[track_index].bus_name.clone(),
|
|
region_id: orig.id,
|
|
start_sample: orig.start_sample,
|
|
audio_l: sl.to_vec(),
|
|
audio_r: sr.to_vec(),
|
|
fade_in_samples: orig.fade_in_samples,
|
|
fade_out_samples: orig.fade_out_samples,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
self.tracks[track_index].regions.push(orig);
|
|
}
|
|
self.history.push_redo(EditCommand::AudioQuantize {
|
|
track_index,
|
|
original_region,
|
|
result_regions,
|
|
});
|
|
}
|
|
EditCommand::SetTempo { old_tempo, new_tempo, old_tempo_map, new_tempo_map } => {
|
|
self.tempo = old_tempo;
|
|
self.project_config.tempo = old_tempo;
|
|
self.tempo_map = old_tempo_map.clone();
|
|
self.sync_tempo_to_engine();
|
|
self.history.push_redo(EditCommand::SetTempo {
|
|
old_tempo: new_tempo,
|
|
new_tempo: old_tempo,
|
|
old_tempo_map: new_tempo_map,
|
|
new_tempo_map: old_tempo_map,
|
|
});
|
|
}
|
|
EditCommand::SplitStems { track_indices } => {
|
|
for &idx in track_indices.iter().rev() {
|
|
if idx < self.tracks.len() {
|
|
let removed = self.tracks.remove(idx);
|
|
for r in &removed.regions {
|
|
self.waveform_cache.remove(&r.id);
|
|
if let Some(ref engine) = self.engine {
|
|
engine.send(EngineCommand::UnloadRegionAudio { region_id: r.id });
|
|
}
|
|
}
|
|
if let Some(ref engine) = self.engine {
|
|
engine.send(EngineCommand::RemoveBus { name: removed.bus_name.clone() });
|
|
}
|
|
}
|
|
}
|
|
self.history.push_redo(EditCommand::SplitStems { track_indices });
|
|
}
|
|
}
|
|
}
|
|
}
|