feat: invert selected audio sample polarity without changing surrounding frames
This commit is contained in:
parent
9ae2e2a11c
commit
f528a169f8
7 changed files with 18 additions and 7 deletions
|
|
@ -25,3 +25,7 @@ Export selected frames writes a new mono float32 WAV of the half-open selection
|
|||
## Phase 72
|
||||
|
||||
Reverse selection changes only the chosen source frames in a new local snapshot. Undo / redo toggles the most recent processing step; selection and view are unchanged. Processing does not affect playback until Save full copy and relink. Close discards unsaved local processing. Sources are bounded to 16 MiB of float samples; preparing a new edit can temporarily retain three snapshots (48 MiB), then two (32 MiB).
|
||||
|
||||
## Phase 73
|
||||
|
||||
Invert polarity negates selected float samples; it is not time reversal. Over-range finite samples remain over-range; no implicit limiting is performed. Outside-selection frames are bit-preserved in the copied snapshot.
|
||||
|
|
|
|||
|
|
@ -1747,12 +1747,12 @@ Implemented: immutable whole-source snapshots reverse only selected frames; the
|
|||
Invert selected samples without altering frames outside the selection, with reversible local processing and no implicit clipping.
|
||||
|
||||
### Tasks and acceptance criteria
|
||||
- [ ] Implement bounded C99 analysis/processing and accessible FLTK controls.
|
||||
- [ ] Preserve source files, atomic error handling, history and callback safety.
|
||||
- [ ] Pass meaningful core and GUI checks; document exact behavior and limits.
|
||||
- [x] Implement bounded C99 analysis/processing and accessible FLTK controls.
|
||||
- [x] Preserve source files, atomic error handling, history and callback safety.
|
||||
- [x] Pass meaningful core and GUI checks; document exact behavior and limits.
|
||||
|
||||
### Implementation status
|
||||
Planned.
|
||||
Implemented: selected-range polarity inversion preserves source length and every surrounding sample, supports local undo/redo and full-copy adoption. Exact signs, two inversions restoring input and focused GUI processing/undo pass.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ void xtc_sample_destroy(xtc_sample *sample);
|
|||
xtc_result xtc_sample_info(const xtc_sample *sample,unsigned int *rate,size_t *frames);
|
||||
xtc_result xtc_sample_read(const xtc_sample *sample,size_t frame,float *out);
|
||||
xtc_result xtc_sample_analyze(const xtc_sample *sample,size_t begin,size_t end,xtc_sample_stats *out);
|
||||
typedef enum { XTC_SAMPLE_REVERSE } xtc_sample_operation;
|
||||
typedef enum { XTC_SAMPLE_REVERSE, XTC_SAMPLE_INVERT } xtc_sample_operation;
|
||||
/* Independent full-source result; only [begin,end) changes. Never mutates input.
|
||||
* amount is reserved/ignored for reversal. Failure preserves *out. */
|
||||
xtc_result xtc_sample_process(const xtc_sample *sample,size_t begin,size_t end,
|
||||
|
|
|
|||
|
|
@ -54,13 +54,13 @@ xtc_result xtc_sample_process(const xtc_sample *s,size_t begin,size_t end,
|
|||
xtc_sample_operation operation,double amount,xtc_sample **out)
|
||||
{
|
||||
xtc_sample *copy;size_t i;xtc_result r=XTC_OK;
|
||||
if(!range(s,begin,end)||!out||operation!=XTC_SAMPLE_REVERSE||!isfinite(amount))return XTC_ERR_INVALID_ARGUMENT;
|
||||
if(!range(s,begin,end)||!out||(operation<XTC_SAMPLE_REVERSE||operation>XTC_SAMPLE_INVERT)||!isfinite(amount))return XTC_ERR_INVALID_ARGUMENT;
|
||||
copy=calloc(1,sizeof(*copy));if(!copy)return XTC_ERR_OUT_OF_MEMORY;
|
||||
copy->wave=s->wave;copy->wave.samples=malloc(s->wave.frames*sizeof(float));
|
||||
if(!copy->wave.samples){free(copy);return XTC_ERR_OUT_OF_MEMORY;}
|
||||
for(i=0;i<s->wave.frames;++i)copy->wave.samples[i]=s->wave.samples[i];
|
||||
for(i=begin;i<end;++i){double value;
|
||||
switch(operation){case XTC_SAMPLE_REVERSE:value=s->wave.samples[end-1-(i-begin)];break;default:value=0;r=XTC_ERR_INVALID_ARGUMENT;break;}
|
||||
switch(operation){case XTC_SAMPLE_INVERT:value=-(double)s->wave.samples[i];break;case XTC_SAMPLE_REVERSE:value=s->wave.samples[end-1-(i-begin)];break;default:value=0;r=XTC_ERR_INVALID_ARGUMENT;break;}
|
||||
if(!isfinite(value)||fabs(value)>3.4028234663852886e38){r=XTC_ERR_CAPACITY;break;}
|
||||
copy->wave.samples[i]=(float)value;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ SampleEditor::SampleEditor(xtc_sample *sample):Fl_Double_Window(1100,650,"xtc
|
|||
auto *save=new Fl_Button(210,520,210,30,"Save full copy and relink...");save->callback([](Fl_Widget *,void *c){auto *e=static_cast<SampleEditor *>(c);const char *path=fl_input("NEW absolute WAV path (full source; project undo keeps the new file)","");if(path){auto r=e->save_copy(path,true);if(r!=XTC_OK)fl_alert("Copy not adopted: %s",xtc_result_string(r));}},this);
|
||||
auto *export_button=new Fl_Button(434,520,200,30,"Export selected frames...");export_button->callback([](Fl_Widget *,void *c){auto *e=static_cast<SampleEditor *>(c);const char *path=fl_input("NEW absolute WAV path (selection only)","");if(path){auto r=e->export_selection(path);if(r!=XTC_OK)fl_alert("Export failed: %s",xtc_result_string(r));}},this);
|
||||
auto *reverse=new Fl_Button(16,562,160,30,"Reverse selection");reverse->callback([](Fl_Widget *,void *c){auto r=static_cast<SampleEditor *>(c)->process(XTC_SAMPLE_REVERSE);if(r!=XTC_OK)fl_alert("Reversal failed: %s",xtc_result_string(r));},this);
|
||||
auto *invert=new Fl_Button(188,562,160,30,"Invert polarity");invert->callback([](Fl_Widget *,void *c){auto r=static_cast<SampleEditor *>(c)->process(XTC_SAMPLE_INVERT);if(r!=XTC_OK)fl_alert("Polarity edit failed: %s",xtc_result_string(r));},this);
|
||||
auto *undo=new Fl_Button(850,604,110,30,"Undo / redo");undo->callback([](Fl_Widget *,void *c){auto r=static_cast<SampleEditor *>(c)->undo_sample();if(r!=XTC_OK)fl_alert("No local sample edit to undo.");},this);
|
||||
auto *close=new Fl_Button(970,604,110,30,"Close");close->callback([](Fl_Widget *,void *c){static_cast<SampleEditor *>(c)->hide();},this);callback([](Fl_Widget *,void *c){static_cast<SampleEditor *>(c)->hide();},this);
|
||||
Fl_Group::end();xtc_ui::style(this);refresh();
|
||||
|
|
|
|||
|
|
@ -195,6 +195,7 @@ static int sample_editor_tests()
|
|||
MainWindow window(false,"");window.show();auto *s=timeline(&window);char path[4096];CHECK(realpath("examples/projects/original-pluck.wav",path));
|
||||
CHECK(window.import_audio(path)==XTC_OK);
|
||||
Fl::add_timeout(.1,[](void *){auto *e=dynamic_cast<SampleEditor *>(Fl::modal());CHECK(e!=nullptr);if(e){CHECK(e->select(10,100)==XTC_OK);float before=0,after=0;CHECK(xtc_sample_read(e->sample(),10,&before)==XTC_OK);
|
||||
CHECK(e->process(XTC_SAMPLE_INVERT)==XTC_OK);CHECK(e->undo_sample()==XTC_OK);
|
||||
CHECK(e->process(XTC_SAMPLE_REVERSE)==XTC_OK);CHECK(e->undo_sample()==XTC_OK);CHECK(xtc_sample_read(e->sample(),10,&after)==XTC_OK && before==after);CHECK(e->undo_sample()==XTC_OK);CHECK(e->select(100,10)==XTC_ERR_INVALID_ARGUMENT);CHECK(e->view(10,8)==XTC_OK);CHECK(e->view(e->frames(),1)==XTC_ERR_INVALID_ARGUMENT);e->hide();}},nullptr);
|
||||
CHECK(window.open_sample_editor()==XTC_OK);CHECK(s->selected_clip()!=UINT64_MAX);
|
||||
uint64_t id=s->selected_clip();CHECK(window.trim_sample_clip(id,48000,2400,12000)==XTC_OK);
|
||||
|
|
|
|||
|
|
@ -35,5 +35,10 @@ int main(void)
|
|||
assert(xtc_sample_process(q,1,7,XTC_SAMPLE_REVERSE,0,&twice)==XTC_OK);
|
||||
for(size_t j=0;j<8;++j){assert(xtc_sample_read(twice,j,&v)==XTC_OK && v==values[j]);}
|
||||
xtc_sample_destroy(q);xtc_sample_destroy(twice);}
|
||||
{xtc_sample *q=NULL,*twice=NULL;assert(xtc_sample_process(s,1,7,XTC_SAMPLE_INVERT,0,&q)==XTC_OK);
|
||||
for(size_t j=0;j<8;++j){assert(xtc_sample_read(q,j,&v)==XTC_OK && v==(j>=1&&j<7 ? -values[j] : values[j]));}
|
||||
assert(xtc_sample_process(q,1,7,XTC_SAMPLE_INVERT,0,&twice)==XTC_OK);
|
||||
for(size_t j=0;j<8;++j){assert(xtc_sample_read(twice,j,&v)==XTC_OK && v==values[j]);}
|
||||
xtc_sample_destroy(q);xtc_sample_destroy(twice);}
|
||||
xtc_sample_destroy(s);assert(unlink(path)==0 && rmdir(root)==0);puts("Sample editor core tests passed.");return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue