172 lines
9.3 KiB
C
172 lines
9.3 KiB
C
#include "project_internal.h"
|
|
#include "xtc/xtc_editing.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
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;i<count;++i) {
|
|
if(xtc_project_find_clip(p,ids[i],&c)!=XTC_OK)return XTC_ERR_NOT_FOUND;
|
|
for(j=0;j<i;++j)if(ids[i]==ids[j])return XTC_ERR_INVALID_ARGUMENT;
|
|
}
|
|
return XTC_OK;
|
|
}
|
|
static void publish(xtc_project *p,xtc_project *copy)
|
|
{
|
|
xtc_graph *old=p->graph;*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(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) {
|
|
xtc_clip_info c;int64_t start;
|
|
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);
|
|
}
|
|
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;i<count;++i){xtc_clip_info c;xtc_project_find_clip(p,ids[i],&c);
|
|
if(c.start_tick<b->begin)b->begin=c.start_tick;
|
|
for(j=0;j<p->track_count;++j)if(p->tracks[j].id==c.track_id && j<b->first_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;base<p->track_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 && i<b->count;++i) {
|
|
xtc_clip_info c;uint64_t id,target;xtc_project_find_clip(b->source,b->ids[i],&c);
|
|
for(j=0;j<b->source->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 && j<b->source->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;i<count;++i){xtc_clip_info c;xtc_project_find_clip(p,ids[i],&c);
|
|
if(c.start_tick<begin)begin=c.start_tick;
|
|
if(c.start_tick+c.length_tick>end)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 && i<count;++i) {
|
|
xtc_clip_info c;uint64_t id;xtc_project_find_clip(p,ids[i],&c);
|
|
r=xtc_project_duplicate_clip(copy,ids[i],c.track_id,c.start_tick+span*k,&id);
|
|
}
|
|
if(r==XTC_OK)publish(p,copy);
|
|
xtc_project_destroy(copy);return r;
|
|
}
|
|
|
|
static uint64_t swung_start(uint64_t start,const xtc_midi_transform *edit)
|
|
{
|
|
uint64_t base=start/edit->grid,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(d<distance || (d==distance && candidate>best)){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->type<XTC_MIDI_TRANSFORM_SWING || edit->type>XTC_MIDI_TRANSFORM_SCALE)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_SCALE && (edit->root>11 || edit->scale>2))return XTC_ERR_INVALID_ARGUMENT;
|
|
for(i=0;i<count;++i){xtc_clip_info c;xtc_project_find_clip(p,ids[i],&c);if(!c.midi)return XTC_ERR_INVALID_STATE;}
|
|
r=xtc_project_clone(p,©);if(r!=XTC_OK)return r;
|
|
for(i=0;r==XTC_OK && i<copy->note_count;++i) {
|
|
xtc_note_info *n=copy->notes+i;xtc_clip_info c;
|
|
for(j=0;j<count;++j)if(n->clip_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;j<p->note_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_tick<next)next=other->start_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(d<distance){distance=d;best=pitch;}
|
|
}
|
|
n->pitch=best;
|
|
}
|
|
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;
|
|
}
|