feat: align clip selections and slip sources without moving boundaries
This commit is contained in:
parent
ea9789b188
commit
b0dbf6905f
7 changed files with 44 additions and 7 deletions
|
|
@ -19,3 +19,7 @@ Ctrl+C/Ctrl+V in the sequencer, or Edit/Copy clips and Paste clips at cursor, co
|
|||
## Phase 50
|
||||
|
||||
Edit/Repeat selected phrase asks for 1..1024 additional copies. The bounding span includes gaps and overlaps; copies remain on their original tracks. The entire repeat is one undo step. Clip/note capacity and timeline endpoints can limit the requested count.
|
||||
|
||||
## Phase 51
|
||||
|
||||
Edit/Align clip starts to cursor places every selected start at the cursor. Slip sources earlier/later changes source offsets by the snap step while retaining timeline positions and durations. Trimming must leave source material available; an invalid slip rejects the whole selection. No time stretching or destructive media edits are performed.
|
||||
|
|
|
|||
|
|
@ -1378,12 +1378,12 @@ Implemented: bounded phrase repeat uses the complete selected bounding span, inc
|
|||
Align selected clip starts to the cursor and slip source offsets without moving timeline boundaries.
|
||||
|
||||
### Tasks and acceptance criteria
|
||||
- [ ] Provide the control/model behavior and accessible GUI commands.
|
||||
- [ ] Validate bounds and failure rollback; preserve existing project compatibility and callback safety.
|
||||
- [ ] Test the new workflow and document scope and limitations.
|
||||
- [x] Provide the control/model behavior and accessible GUI commands.
|
||||
- [x] Validate bounds and failure rollback; preserve existing project compatibility and callback safety.
|
||||
- [x] Test the new workflow and document scope and limitations.
|
||||
|
||||
### Implementation status
|
||||
Planned.
|
||||
Implemented: selection-wide start alignment and source slip commands reuse atomic clip transactions. Clip start/length and source limits remain validated for audio and MIDI. Core late-failure rollback and focused GUI alignment/history tests pass.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,10 @@ extern "C" {
|
|||
* ID counters and optional count-element output. No media loading or DSP work.
|
||||
* MOVE/DUPLICATE amount is a signed tick displacement, bounded by MAX_TICK.
|
||||
* DUPLICATE retains tracks, source ranges, notes and media with fresh IDs.
|
||||
* ALIGN amount is an absolute timeline start; SLIP amount is a signed source
|
||||
* offset change (audio or MIDI), retaining timeline start/length.
|
||||
* out_ids receives resulting IDs (removed IDs for DELETE). May be NULL. */
|
||||
typedef enum { XTC_CLIPS_MOVE, XTC_CLIPS_DUPLICATE, XTC_CLIPS_DELETE } xtc_clip_operation;
|
||||
typedef enum { XTC_CLIPS_MOVE, XTC_CLIPS_DUPLICATE, XTC_CLIPS_DELETE, XTC_CLIPS_ALIGN, XTC_CLIPS_SLIP } xtc_clip_operation;
|
||||
xtc_result xtc_project_transform_clips(xtc_project *project,const uint64_t *ids,size_t count,
|
||||
xtc_clip_operation operation,int64_t amount,uint64_t *out_ids);
|
||||
/* Repeat the bounding phrase span, including gaps, 1..1024 additional copies.
|
||||
|
|
|
|||
|
|
@ -322,6 +322,9 @@ MainWindow::MainWindow(bool prompt_unsaved,const char *configuration)
|
|||
Fl_Group::current(parent);
|
||||
menu->add("Edit/Nudge selection left",0,[](Fl_Widget *,void *c){static_cast<SequencerWidget *>(c)->nudge_selection(-1);},sequencer_);
|
||||
menu->add("Edit/Nudge selection right",0,[](Fl_Widget *,void *c){static_cast<SequencerWidget *>(c)->nudge_selection(1);},sequencer_);
|
||||
menu->add("Edit/Align clip starts to cursor",0,[](Fl_Widget *,void *c){auto *s=static_cast<SequencerWidget *>(c);s->transform_selection(XTC_CLIPS_ALIGN,static_cast<int64_t>(s->position()));},sequencer_);
|
||||
menu->add("Edit/Slip sources earlier",0,[](Fl_Widget *,void *c){auto *s=static_cast<SequencerWidget *>(c);s->transform_selection(XTC_CLIPS_SLIP,-static_cast<int64_t>(s->grid()));},sequencer_);
|
||||
menu->add("Edit/Slip sources later",0,[](Fl_Widget *,void *c){auto *s=static_cast<SequencerWidget *>(c);s->transform_selection(XTC_CLIPS_SLIP,static_cast<int64_t>(s->grid()));},sequencer_);
|
||||
menu->add("Edit/Repeat selected phrase...",0,[](Fl_Widget *,void *c){static_cast<SequencerWidget *>(c)->repeat_dialog();},sequencer_);
|
||||
menu->add("Edit/Copy clips",0,[](Fl_Widget *,void *c){static_cast<SequencerWidget *>(c)->copy_selection();},sequencer_);
|
||||
menu->add("Edit/Paste clips at cursor",0,[](Fl_Widget *,void *c){static_cast<SequencerWidget *>(c)->paste_selection();},sequencer_);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ xtc_result xtc_project_transform_clips(xtc_project *p,const uint64_t *ids,size_t
|
|||
xtc_project *copy=NULL;size_t i;uint64_t output[XTC_PROJECT_MAX_CLIPS];
|
||||
xtc_result r=selection_valid(p,ids,count);
|
||||
if(r!=XTC_OK)return r;
|
||||
if(operation<XTC_CLIPS_MOVE || operation>XTC_CLIPS_DELETE ||
|
||||
if(operation<XTC_CLIPS_MOVE || operation>XTC_CLIPS_SLIP ||
|
||||
amount<-(int64_t)XTC_PROJECT_MAX_TICK || amount>(int64_t)XTC_PROJECT_MAX_TICK)return XTC_ERR_INVALID_ARGUMENT;
|
||||
r=xtc_project_clone(p,©);
|
||||
for(i=0;r==XTC_OK && i<count;++i) {
|
||||
|
|
@ -31,6 +31,12 @@ xtc_result xtc_project_transform_clips(xtc_project *p,const uint64_t *ids,size_t
|
|||
r=xtc_project_find_clip(copy,ids[i],&c);if(r!=XTC_OK)break;
|
||||
start=(int64_t)c.start_tick+amount;output[i]=ids[i];
|
||||
if(operation==XTC_CLIPS_DELETE)r=xtc_project_remove_clip(copy,ids[i]);
|
||||
else if(operation==XTC_CLIPS_ALIGN) {
|
||||
r=amount<0 ? XTC_ERR_INVALID_ARGUMENT : xtc_project_edit_clip(copy,ids[i],c.track_id,(uint64_t)amount,c.length_tick,c.offset_tick);
|
||||
} else if(operation==XTC_CLIPS_SLIP) {
|
||||
int64_t offset=(int64_t)c.offset_tick+amount;
|
||||
r=offset<0 ? XTC_ERR_INVALID_ARGUMENT : xtc_project_edit_clip(copy,ids[i],c.track_id,c.start_tick,c.length_tick,(uint64_t)offset);
|
||||
}
|
||||
else if(start<0 || start>(int64_t)XTC_PROJECT_MAX_TICK)r=XTC_ERR_INVALID_ARGUMENT;
|
||||
else if(operation==XTC_CLIPS_DUPLICATE)r=xtc_project_duplicate_clip(copy,ids[i],c.track_id,(uint64_t)start,output+i);
|
||||
else r=xtc_project_edit_clip(copy,ids[i],c.track_id,(uint64_t)start,c.length_tick,c.offset_tick);
|
||||
|
|
|
|||
|
|
@ -1326,6 +1326,11 @@ int main(int argc, char **argv)
|
|||
CHECK(s->paste_selection()==XTC_OK && s->selected_clips().size()==2);
|
||||
xtc_project_get_counts(s->project(),&tc,&cc);CHECK(cc==4);
|
||||
CHECK(s->repeat_selection(2)==XTC_OK);xtc_project_get_counts(s->project(),&tc,&cc);CHECK(cc==8);
|
||||
CHECK(window.undo()==XTC_OK);xtc_project_get_counts(s->project(),&tc,&cc);CHECK(cc==4);
|
||||
CHECK(window.undo(true)==XTC_OK);xtc_project_get_counts(s->project(),&tc,&cc);CHECK(cc==8);
|
||||
s->select_all_clips();CHECK(s->transform_selection(XTC_CLIPS_ALIGN,3840)==XTC_OK);
|
||||
CHECK(xtc_project_find_clip(s->project(),first,&moved)==XTC_OK && moved.start_tick==3840);
|
||||
CHECK(s->transform_selection(XTC_CLIPS_SLIP,0)==XTC_OK);
|
||||
window.do_callback();
|
||||
}
|
||||
if (failures != 0) return 1;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,23 @@
|
|||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static void test_align_slip(void)
|
||||
{
|
||||
xtc_project *p=NULL;uint64_t t,ids[2];xtc_clip_info c;
|
||||
assert(xtc_project_create(&p)==XTC_OK && xtc_project_add_track(p,"Track",&t)==XTC_OK);
|
||||
assert(xtc_project_add_clip(p,t,"A",0,1920,&ids[0])==XTC_OK);
|
||||
assert(xtc_project_add_clip(p,t,"B",960,960,&ids[1])==XTC_OK);
|
||||
assert(xtc_project_edit_clip(p,ids[0],t,0,480,240)==XTC_OK);
|
||||
assert(xtc_project_edit_clip(p,ids[1],t,960,480,240)==XTC_OK);
|
||||
assert(xtc_project_transform_clips(p,ids,2,XTC_CLIPS_SLIP,240,NULL)==XTC_OK);
|
||||
assert(xtc_project_find_clip(p,ids[0],&c)==XTC_OK && c.offset_tick==480 && c.start_tick==0);
|
||||
/* First clip permits this slip, second does not: no partial publication. */
|
||||
assert(xtc_project_transform_clips(p,ids,2,XTC_CLIPS_SLIP,240,NULL)==XTC_ERR_INVALID_ARGUMENT);
|
||||
assert(xtc_project_find_clip(p,ids[0],&c)==XTC_OK && c.offset_tick==480);
|
||||
assert(xtc_project_transform_clips(p,ids,2,XTC_CLIPS_ALIGN,3840,NULL)==XTC_OK);
|
||||
assert(xtc_project_find_clip(p,ids[1],&c)==XTC_OK && c.start_tick==3840 && c.offset_tick==480);
|
||||
xtc_project_destroy(p);
|
||||
}
|
||||
static void test_repeat(void)
|
||||
{
|
||||
xtc_project *p=NULL;uint64_t t,ids[2];xtc_clip_info c;size_t tracks,clips;
|
||||
|
|
@ -18,7 +35,7 @@ static void test_repeat(void)
|
|||
}
|
||||
int main(void)
|
||||
{
|
||||
test_repeat();
|
||||
test_repeat();test_align_slip();
|
||||
xtc_project *p=NULL;uint64_t track,a,b,n,ids[2],out[2]={999,999};xtc_clip_info c;
|
||||
size_t tracks,clips,notes;
|
||||
assert(xtc_project_create(&p)==XTC_OK);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue