#include "project_internal.h" #include "xtc/xtc_editing.h" #include "xtc/xtc_audio.h" #include #include static xtc_result selection_valid(const xtc_project *p,const uint64_t *ids,size_t count) { size_t i,j;xtc_clip_info c; if(!p || !ids || !count || count>XTC_PROJECT_MAX_CLIPS)return XTC_ERR_INVALID_ARGUMENT; for(i=0;igraph;*p=*copy;copy->graph=old; } xtc_result xtc_project_transform_clips(xtc_project *p,const uint64_t *ids,size_t count, xtc_clip_operation operation,int64_t amount,uint64_t *out_ids) { 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(operationXTC_CLIPS_FADE || 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 && ic.length_tick/2 ? XTC_ERR_INVALID_ARGUMENT : xtc_project_set_clip_audio(copy,ids[i],c.gain,(uint64_t)amount,(uint64_t)amount); } 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); } if(r==XTC_OK){publish(p,copy);if(out_ids)memcpy(out_ids,output,count*sizeof(*output));} xtc_project_destroy(copy);return r; } struct xtc_clipboard {xtc_project *source;size_t count,first_track;uint64_t begin,ids[XTC_PROJECT_MAX_CLIPS];}; void xtc_clipboard_destroy(xtc_clipboard *b) { if(b){xtc_project_destroy(b->source);free(b);} } size_t xtc_clipboard_count(const xtc_clipboard *b){return b ? b->count : 0;} xtc_result xtc_clipboard_create(const xtc_project *p,const uint64_t *ids,size_t count,xtc_clipboard **out) { xtc_clipboard *b;size_t i,j;xtc_result r; if(!out)return XTC_ERR_INVALID_ARGUMENT; r=selection_valid(p,ids,count);if(r!=XTC_OK)return r; b=calloc(1,sizeof(*b));if(!b)return XTC_ERR_OUT_OF_MEMORY; r=xtc_project_clone(p,&b->source); if(r!=XTC_OK){free(b);return r;} b->count=count;b->begin=XTC_PROJECT_MAX_TICK;b->first_track=p->track_count; memcpy(b->ids,ids,count*sizeof(*ids)); for(i=0;ibegin)b->begin=c.start_tick; for(j=0;jtrack_count;++j)if(p->tracks[j].id==c.track_id && jfirst_track)b->first_track=j; } *out=b;return XTC_OK; } xtc_result xtc_clipboard_paste(const xtc_clipboard *b,xtc_project *p,uint64_t track,uint64_t start,uint64_t *out_ids) { xtc_project *copy=NULL;size_t base,i,j,k;uint64_t output[XTC_PROJECT_MAX_CLIPS];xtc_result r; if(!b || !p || start>XTC_PROJECT_MAX_TICK)return XTC_ERR_INVALID_ARGUMENT; for(base=0;basetrack_count;++base)if(p->tracks[base].id==track)break; if(base==p->track_count)return XTC_ERR_NOT_FOUND; r=xtc_project_clone(p,©); for(i=0;r==XTC_OK && icount;++i) { xtc_clip_info c;uint64_t id,target;xtc_project_find_clip(b->source,b->ids[i],&c); for(j=0;jsource->track_count;++j)if(b->source->tracks[j].id==c.track_id)break; k=base+j-b->first_track; if(k>=copy->track_count){r=XTC_ERR_NOT_FOUND;break;} if(copy->tracks[k].is_bus){r=XTC_ERR_INVALID_STATE;break;} if(c.start_tick-b->begin>XTC_PROJECT_MAX_TICK-start){r=XTC_ERR_INVALID_ARGUMENT;break;} target=start+c.start_tick-b->begin; r=xtc_project_add_clip(copy,copy->tracks[k].id,c.name,target,c.length_tick,&id); if(r!=XTC_OK)break; c.id=id;c.track_id=copy->tracks[k].id;c.start_tick=target;copy->clips[copy->clip_count-1]=c;output[i]=id; if(c.midi)r=xtc_project_ensure_midi_route(copy); for(j=0;r==XTC_OK && jsource->note_count;++j)if(b->source->notes[j].clip_id==b->ids[i]) { xtc_note_info n=b->source->notes[j];uint64_t note; r=xtc_project_add_note(copy,id,n.start_tick,n.length_tick,n.pitch,n.velocity,n.channel,¬e); } } if(r==XTC_OK){publish(p,copy);if(out_ids)memcpy(out_ids,output,b->count*sizeof(*output));} xtc_project_destroy(copy);return r; } xtc_result xtc_project_repeat_clips(xtc_project *p,const uint64_t *ids,size_t count,unsigned int copies) { xtc_project *copy=NULL;size_t i;unsigned int k;uint64_t begin=XTC_PROJECT_MAX_TICK,end=0,span; xtc_result r=selection_valid(p,ids,count);if(r!=XTC_OK)return r; if(!copies || copies>XTC_PROJECT_MAX_CLIPS)return XTC_ERR_INVALID_ARGUMENT; if(count>(XTC_PROJECT_MAX_CLIPS-p->clip_count)/copies)return XTC_ERR_CAPACITY; for(i=0;iend)end=c.start_tick+c.length_tick; } span=end-begin; if(span>(XTC_PROJECT_MAX_TICK-end)/copies)return XTC_ERR_INVALID_ARGUMENT; r=xtc_project_clone(p,©); for(k=1;r==XTC_OK && k<=copies;++k)for(i=0;r==XTC_OK && igrid,best=0,distance=UINT64_MAX;int offset; for(offset=-2;offset<=2;++offset) { int64_t index=(int64_t)base+offset;uint64_t candidate,d; if(index<0)continue; candidate=(uint64_t)index*edit->grid; if(index%2)candidate+=edit->grid*edit->swing/200; d=start>candidate ? start-candidate : candidate-start; if(dbest)){distance=d;best=candidate;} } return (start*(100-edit->strength)+best*edit->strength+50)/100; } xtc_result xtc_project_transform_midi(xtc_project *p,const uint64_t *ids,size_t count,const xtc_midi_transform *edit) { xtc_project *copy=NULL;size_t i,j,matched=0;xtc_result r=selection_valid(p,ids,count); if(r!=XTC_OK)return r; if(!edit || edit->typetype>XTC_MIDI_TRANSFORM_VELOCITY)return XTC_ERR_INVALID_ARGUMENT; if(edit->type==XTC_MIDI_TRANSFORM_SWING && (!edit->grid || edit->grid>3840 || edit->swing>100 || edit->strength>100))return XTC_ERR_INVALID_ARGUMENT; if(edit->type==XTC_MIDI_TRANSFORM_GATE && (!edit->gate_percent || edit->gate_percent>400))return XTC_ERR_INVALID_ARGUMENT; if(edit->type==XTC_MIDI_TRANSFORM_VELOCITY && (edit->velocity_percent>400 || edit->velocity_offset < -127 || edit->velocity_offset>127))return XTC_ERR_INVALID_ARGUMENT; if(edit->type==XTC_MIDI_TRANSFORM_SCALE && (edit->root>11 || edit->scale>2))return XTC_ERR_INVALID_ARGUMENT; for(i=0;inote_count;++i) { xtc_note_info *n=copy->notes+i;xtc_clip_info c; for(j=0;jclip_id==ids[j])break; if(j==count)continue; ++matched;xtc_project_find_clip(copy,n->clip_id,&c); if(edit->type==XTC_MIDI_TRANSFORM_SWING)n->start_tick=swung_start(n->start_tick,edit); else if(edit->type==XTC_MIDI_TRANSFORM_GATE) { n->length_tick=(n->length_tick*edit->gate_percent+50)/100; if(!n->length_tick)n->length_tick=1; } else if(edit->type==XTC_MIDI_TRANSFORM_LEGATO) { uint64_t next=c.source_length_tick; for(j=0;jnote_count;++j) { const xtc_note_info *other=p->notes+j; if(other->clip_id==n->clip_id && other->channel==n->channel && other->start_tick>n->start_tick && other->start_tickstart_tick; } n->length_tick=next-n->start_tick; } else if(edit->type==XTC_MIDI_TRANSFORM_SCALE) { static const unsigned int masks[]={4095,2741,1453}; unsigned int pitch,best=n->pitch,distance=128; for(pitch=0;pitch<128;++pitch)if(masks[edit->scale] & (1U<<((pitch+12-edit->root)%12))) { unsigned int d=pitch>n->pitch ? pitch-n->pitch : n->pitch-pitch; if(dpitch=best; } else if(edit->type==XTC_MIDI_TRANSFORM_VELOCITY) { int velocity=(int)((n->velocity*edit->velocity_percent+50)/100)+edit->velocity_offset; n->velocity=(unsigned int)(velocity<1 ? 1 : velocity>127 ? 127 : velocity); } if(n->start_tick>=c.source_length_tick || n->length_tick>c.source_length_tick-n->start_tick)r=XTC_ERR_INVALID_ARGUMENT; } if(r==XTC_OK && !matched)r=XTC_ERR_NOT_FOUND; if(r==XTC_OK)publish(p,copy); xtc_project_destroy(copy);return r; } struct xtc_mix_snapshot {size_t count;xtc_track_info tracks[XTC_PROJECT_MAX_TRACKS];}; void xtc_mix_snapshot_destroy(xtc_mix_snapshot *snapshot){free(snapshot);} xtc_result xtc_mix_snapshot_create(const xtc_project *p,xtc_mix_snapshot **out) { xtc_mix_snapshot *snapshot; if(!p || !out)return XTC_ERR_INVALID_ARGUMENT; snapshot=calloc(1,sizeof(*snapshot));if(!snapshot)return XTC_ERR_OUT_OF_MEMORY; snapshot->count=p->track_count;memcpy(snapshot->tracks,p->tracks,p->track_count*sizeof(*p->tracks)); *out=snapshot;return XTC_OK; } xtc_result xtc_mix_snapshot_recall(const xtc_mix_snapshot *snapshot,xtc_project *p) { xtc_project *copy=NULL;size_t i;xtc_result r; if(!snapshot || !p)return XTC_ERR_INVALID_ARGUMENT; if(snapshot->count!=p->track_count)return XTC_ERR_INVALID_STATE; r=xtc_project_clone(p,©); for(i=0;r==XTC_OK && icount;++i) { const xtc_track_info *track=snapshot->tracks+i; r=xtc_project_set_mix(copy,track->id,track->gain,track->pan,track->mute,track->solo); } if(r==XTC_OK)publish(p,copy); xtc_project_destroy(copy);return r; } xtc_result xtc_project_duplicate_track(xtc_project *p,uint64_t track,const char *name,uint64_t *out) { xtc_project *copy=NULL;size_t i,index;uint64_t id=0;xtc_result r; if(!p || !out || !xtc_project_valid_name(name))return XTC_ERR_INVALID_ARGUMENT; for(index=0;indextrack_count;++index)if(p->tracks[index].id==track)break; if(index==p->track_count)return XTC_ERR_NOT_FOUND; if(p->tracks[index].is_bus)return XTC_ERR_INVALID_STATE; r=xtc_project_clone(p,©);if(r!=XTC_OK)return r; r=xtc_project_add_track(copy,name,&id); if(r==XTC_OK){xtc_track_info *target=copy->tracks+copy->track_count-1;*target=p->tracks[index];target->id=id;memcpy(target->name,name,strlen(name)+1);copy->instruments[copy->track_count-1]=p->instruments[index];} for(i=0;r==XTC_OK && iclip_count;++i)if(p->clips[i].track_id==track){uint64_t clip;r=xtc_project_duplicate_clip(copy,p->clips[i].id,id,p->clips[i].start_tick,&clip);} for(i=0;r==XTC_OK && iautomation_count;++i)if(p->automation[i].track_id==track){xtc_automation_point point=p->automation[i];point.track_id=id;r=xtc_project_set_automation(copy,&point);} if(r==XTC_OK)r=xtc_project_move_track(copy,id,index+1); if(r==XTC_OK){publish(p,copy);*out=id;} xtc_project_destroy(copy);return r; }