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
|
## 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.
|
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.
|
Align selected clip starts to the cursor and slip source offsets without moving timeline boundaries.
|
||||||
|
|
||||||
### Tasks and acceptance criteria
|
### Tasks and acceptance criteria
|
||||||
- [ ] Provide the control/model behavior and accessible GUI commands.
|
- [x] Provide the control/model behavior and accessible GUI commands.
|
||||||
- [ ] Validate bounds and failure rollback; preserve existing project compatibility and callback safety.
|
- [x] Validate bounds and failure rollback; preserve existing project compatibility and callback safety.
|
||||||
- [ ] Test the new workflow and document scope and limitations.
|
- [x] Test the new workflow and document scope and limitations.
|
||||||
|
|
||||||
### Implementation status
|
### 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.
|
* 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.
|
* MOVE/DUPLICATE amount is a signed tick displacement, bounded by MAX_TICK.
|
||||||
* DUPLICATE retains tracks, source ranges, notes and media with fresh IDs.
|
* 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. */
|
* 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_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);
|
xtc_clip_operation operation,int64_t amount,uint64_t *out_ids);
|
||||||
/* Repeat the bounding phrase span, including gaps, 1..1024 additional copies.
|
/* 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);
|
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 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/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/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/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_);
|
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_project *copy=NULL;size_t i;uint64_t output[XTC_PROJECT_MAX_CLIPS];
|
||||||
xtc_result r=selection_valid(p,ids,count);
|
xtc_result r=selection_valid(p,ids,count);
|
||||||
if(r!=XTC_OK)return r;
|
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;
|
amount<-(int64_t)XTC_PROJECT_MAX_TICK || amount>(int64_t)XTC_PROJECT_MAX_TICK)return XTC_ERR_INVALID_ARGUMENT;
|
||||||
r=xtc_project_clone(p,©);
|
r=xtc_project_clone(p,©);
|
||||||
for(i=0;r==XTC_OK && i<count;++i) {
|
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;
|
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];
|
start=(int64_t)c.start_tick+amount;output[i]=ids[i];
|
||||||
if(operation==XTC_CLIPS_DELETE)r=xtc_project_remove_clip(copy,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(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 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);
|
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);
|
CHECK(s->paste_selection()==XTC_OK && s->selected_clips().size()==2);
|
||||||
xtc_project_get_counts(s->project(),&tc,&cc);CHECK(cc==4);
|
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(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();
|
window.do_callback();
|
||||||
}
|
}
|
||||||
if (failures != 0) return 1;
|
if (failures != 0) return 1;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,23 @@
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdio.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)
|
static void test_repeat(void)
|
||||||
{
|
{
|
||||||
xtc_project *p=NULL;uint64_t t,ids[2];xtc_clip_info c;size_t tracks,clips;
|
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)
|
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;
|
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;
|
size_t tracks,clips,notes;
|
||||||
assert(xtc_project_create(&p)==XTC_OK);
|
assert(xtc_project_create(&p)==XTC_OK);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue