diff --git a/hi/mkv.c b/hi/mkv.c index 7bfd23d..12a5d6b 100644 --- a/hi/mkv.c +++ b/hi/mkv.c @@ -294,14 +294,14 @@ static int muxmkv_perform(CHiPubNode *pubn) { struct Internal *this = (void*) pubn; - if(pubn->sinks[0].data.linked.to) { + if(pubn->sinks[0].linked.to) { CHiBSFrames *vp9bs = CHi_Crawl(&pubn->sinks[0])->data.bitstream; if(vp9bs) { this->videoBacklog = CHi_BS_Combine(this->videoBacklog, vp9bs); } } - if(pubn->sinks[1].data.linked.to) { + if(pubn->sinks[1].linked.to) { CHiBSFrames *opus = CHi_Crawl(&pubn->sinks[1])->data.bitstream; if(opus) { this->audioBacklog = CHi_BS_Combine(this->audioBacklog, opus); @@ -550,12 +550,12 @@ static int muxmkv_start(CHiPubNode *pubn) { int trackNum = 1; - if(pubn->sinks[0].data.linked.to) { + if(pubn->sinks[0].linked.to) { this->videoBacklog = CHi_BS_Empty(); this->videoTrack = trackNum++; } - if(pubn->sinks[1].data.linked.to) { + if(pubn->sinks[1].linked.to) { this->audioBacklog = CHi_BS_Empty(); this->audioTrack = trackNum++; } diff --git a/hi/node.c b/hi/node.c index fcac92e..07dcab3 100644 --- a/hi/node.c +++ b/hi/node.c @@ -19,55 +19,7 @@ #include"linearity.h" -static size_t bisect(const void *key, const void *base, size_t nmemb, size_t size, ssize_t(*compar)(const void*, const void*)) { - size_t low = 0, high = nmemb; - - while(low < high) { - size_t middle = (low + high) / 2; - if(compar((const void*) ((uintptr_t) base + size * middle), key) < 0) { - low = middle + 1; - } else { - high = middle; - } - } - - return low; -} - -static ssize_t float_compar(const void *A, const void *B) { - float a = *(float*) A; - float b = *(float*) B; - return (a > b) - (a < b); -} - -static int adjacencycmp(const void *a, const void *b) { - size_t v = (uintptr_t) ((CHiAdjacency*) a)[0] - (uintptr_t) ((CHiAdjacency*) b)[0]; - return v ? v : (uintptr_t) ((CHiAdjacency*) a)[1] - (uintptr_t) ((CHiAdjacency*) b)[1]; -} - -static void adjacency_add(CHiPubNode *source, CHiPubNode *sink) { - CHiNodeGraph *ng = source->ng; - - if(ng->adjacencyCount == ng->adjacencyCapacity) { - ng->adjacencies = realloc(ng->adjacencies, sizeof(CHiAdjacency) * (ng->adjacencyCapacity *= 2)); - } - - ng->adjacencies[ng->adjacencyCount][0] = source; - ng->adjacencies[ng->adjacencyCount][1] = sink; - ng->adjacencyCount++; - - qsort(ng->adjacencies, ng->adjacencyCount, sizeof(CHiAdjacency), adjacencycmp); -} - -static void adjacency_remove(CHiPubNode *source, CHiPubNode *sink) { - CHiNodeGraph *ng = source->ng; - - CHiAdjacency *adj = bsearch(&(CHiAdjacency) {source, sink}, ng->adjacencies, ng->adjacencyCount, sizeof(CHiAdjacency), adjacencycmp); - if(adj) { - memmove(adj, adj + 1, sizeof(CHiAdjacency) * (ng->adjacencyCount - (adj - ng->adjacencies) - 1)); - ng->adjacencyCount--; - } -} +#include"node_internal.h" CUTIVIS CHiNodeGraph *CHi_NewNodeGraph() { static int inited = 0; @@ -125,9 +77,9 @@ CUTIVIS CHiNodeGraph *CHi_NodeGraphReset(CHiNodeGraph *ng) { } CUTIVIS CHiValue *CHi_Crawl(CHiValue *v) { - while(v->type == CUTIHI_VAL_LINKED || v->type == CUTIHI_VAL_KEYED) { - if(v->type == CUTIHI_VAL_LINKED) { - v = &v->data.linked.to->sources[v->data.linked.idx]; + while(v->linked.to || v->type == CUTIHI_VAL_KEYED) { + if(v->linked.to) { + v = &v->linked.to->sources[v->linked.idx]; } else if(v->type == CUTIHI_VAL_KEYED) { v = &v->data.keyed->current; } @@ -163,8 +115,8 @@ static int dfs_visit(size_t *resultCount, CHiPubNode ***result, CHiPubNode *n) { n->_dfsmark = 1; for(size_t s = 0; s < n->sinkCount; s++) { - if(n->sinks[s].type == CUTIHI_VAL_LINKED) { - if(!dfs_visit(resultCount, result, n->sinks[s].data.linked.to)) { + if(n->sinks[s].linked.to) { + if(!dfs_visit(resultCount, result, n->sinks[s].linked.to)) { return 0; } } @@ -209,28 +161,34 @@ CUTIVIS int CHi_ConfigureSink(CHiPubNode *n, size_t i, CHiValue v) { return 1; } - if(v.type == CUTIHI_VAL_LINKED && n == v.data.linked.to) return 0; + if(v.linked.to && n == v.linked.to) return 0; CHiValue old = n->sinks[i]; - if(old.type == CUTIHI_VAL_LINKED) { - adjacency_remove(old.data.linked.to, n); + if(old.linked.to) { + adjacency_remove(old.linked.to, n); + } + + if(v.linked.to) { + // Overwrite only the link-related things to keep the old values in case the link is removed + n->sinks[i].linked = v.linked; + } else { + n->sinks[i] = v; } // Check if viable - n->sinks[i] = v; if(n->ng && !topological_sort(n->ng)) { n->sinks[i] = old; - if(old.type == CUTIHI_VAL_LINKED) { - adjacency_add(old.data.linked.to, n); + if(old.linked.to) { + adjacency_add(old.linked.to, n); } return 0; } - if(v.type == CUTIHI_VAL_LINKED) { - adjacency_add(v.data.linked.to, n); + if(v.linked.to) { + adjacency_add(v.linked.to, n); } CHi_MakeDirty(n->ng, n); @@ -351,22 +309,6 @@ CUTIVIS void CHi_SetDuration(CHiNodeGraph *ng, float d) { ng->duration = d; } -CUTIVIS int CHi_Hysteresis(CHiPubNode *root) { - if(root->ng->compilationStatus != CUTIHI_COMP_READY) return 0; - - for(size_t s = 0; s < root->sinkCount; s++) { - if(root->sinks[s].type == CUTIHI_VAL_LINKED) { - CHi_Hysteresis(root->sinks[s].data.linked.to); - } - } - - //if(!root->clean) { - root->Perform(root); - //} - - return 1; -} - static bool error_changes(CHiPubNode *n) { for(int e = 0; e < CUTIHI_MAX_ERRORS; e++) { if(n->errors.active[e] != n->errors.activeLast[e]) { @@ -384,9 +326,7 @@ static void save_errors(CHiPubNode *n) { } } -static void perform_step(CHiNodeGraph *ng) { - pthread_mutex_lock(&ng->mut); - +CUTIVIS int CHi_Hysteresis(CHiNodeGraph *ng) { for(size_t nIdx = 0; nIdx < ng->count; nIdx++) { save_errors(ng->nodes[nIdx]); } @@ -394,14 +334,25 @@ static void perform_step(CHiNodeGraph *ng) { for(size_t nIdx = 0; nIdx < ng->count; nIdx++) { CHiPubNode *n = ng->nodes[nIdx]; - n->Perform(n); - - if(error_changes(n)) { - if(ng->eventOnError) - ng->eventOnError(ng, n); + // The existence of n->Start implies the node has external side-effects and should not be executed + if(ng->compilationStatus == CUTIHI_COMP_RUNNING || n->Start == NULL) { + n->Perform(n); + + if(error_changes(n)) { + if(ng->eventOnError) + ng->eventOnError(ng, n); + } } } + return 1; +} + +static void perform_step(CHiNodeGraph *ng) { + pthread_mutex_lock(&ng->mut); + + CHi_Hysteresis(ng); + if(ng->eventOnFrameComplete) { ng->eventOnFrameComplete(ng); } @@ -917,49 +868,6 @@ CUTIVIS CHiPubNode *CHi_Modulate() { return n; } -static void update_keyed_values(CHiNodeGraph *ng) { - for(size_t kfsIdx = 0; kfsIdx < ng->keyframesList.count; kfsIdx++) { - CHiKeyframes *kfs = ng->keyframesList.keyframes[kfsIdx]; - - kfs->current.type = kfs->type; - - float now = ng->time; - - size_t idx = bisect(&now, kfs->times, kfs->count, sizeof(now), float_compar); - - if(idx == 0) { - kfs->current.data = kfs->values[idx]; - - if(kfs->current.type == CUTIHI_VAL_VEC4 && kfs->extrapolationMode == CUTIHI_EXTRAPOLATION_CONSTANT) { - kfs->current.data.vec4[0] += (now - kfs->times[0]) * kfs->extrapolationParameter[0]; - kfs->current.data.vec4[1] += (now - kfs->times[0]) * kfs->extrapolationParameter[1]; - kfs->current.data.vec4[2] += (now - kfs->times[0]) * kfs->extrapolationParameter[2]; - kfs->current.data.vec4[3] += (now - kfs->times[0]) * kfs->extrapolationParameter[3]; - } - } else if(idx == kfs->count) { - kfs->current.data = kfs->values[idx - 1]; - - if(kfs->current.type == CUTIHI_VAL_VEC4 && kfs->extrapolationMode == CUTIHI_EXTRAPOLATION_CONSTANT) { - kfs->current.data.vec4[0] += (now - kfs->times[kfs->count - 1]) * kfs->extrapolationParameter[0]; - kfs->current.data.vec4[1] += (now - kfs->times[kfs->count - 1]) * kfs->extrapolationParameter[1]; - kfs->current.data.vec4[2] += (now - kfs->times[kfs->count - 1]) * kfs->extrapolationParameter[2]; - kfs->current.data.vec4[3] += (now - kfs->times[kfs->count - 1]) * kfs->extrapolationParameter[3]; - } - } else { - if(kfs->type == CUTIHI_VAL_VEC4) { - float alpha = (now - kfs->times[idx - 1]) / (kfs->times[idx] - kfs->times[idx - 1]); - - kfs->current.data.vec4[0] = kfs->values[idx - 1].vec4[0] + (kfs->values[idx].vec4[0] - kfs->values[idx - 1].vec4[0]) * alpha; - kfs->current.data.vec4[1] = kfs->values[idx - 1].vec4[1] + (kfs->values[idx].vec4[1] - kfs->values[idx - 1].vec4[1]) * alpha; - kfs->current.data.vec4[2] = kfs->values[idx - 1].vec4[2] + (kfs->values[idx].vec4[2] - kfs->values[idx - 1].vec4[2]) * alpha; - kfs->current.data.vec4[3] = kfs->values[idx - 1].vec4[3] + (kfs->values[idx].vec4[3] - kfs->values[idx - 1].vec4[3]) * alpha; - } else { - kfs->current.data = kfs->values[idx - 1]; - } - } - } -} - static int time_perform(CHiPubNode *node) { node->sources->type = CUTIHI_VAL_VEC4; node->sources->data.vec4[0] = node->ng->time; @@ -1300,281 +1208,55 @@ CUTIVIS CHiPubNode *CHi_ChromaKey() { return n; } -static void save_chival(CHiNodeGraph *ng, CHiSaveWriter writer, CHiValType type, CHiValueRaw data, void *ud) { - if(type == CUTIHI_VAL_TEXT) { - size_t len = strlen(data.text); - writer(ud, &(uint32_t) {len}, sizeof(uint32_t)); - writer(ud, data.text, len); - } else if(type == CUTIHI_VAL_VEC4) { - writer(ud, data.vec4, sizeof(data.vec4)); - } else if(type == CUTIHI_VAL_LINKED) { - size_t index; - for(index = 0; index < ng->count; index++) { - if(ng->nodes[index] == data.linked.to) { - break; - } - } - assert(index < ng->count); - - writer(ud, &(uint64_t) {index}, sizeof(uint64_t)); - writer(ud, &(uint16_t) {data.linked.idx}, sizeof(uint16_t)); - } else if(type == CUTIHI_VAL_KEYED) { - size_t index; - for(index = 0; index < ng->keyframesList.count; index++) { - if(ng->keyframesList.keyframes[index] == data.keyed) { - break; - } - } - assert(index < ng->count); - - writer(ud, &(uint64_t) {index}, sizeof(uint64_t)); - } -} - -static void load_chival(CHiNodeGraph *ng, CHiLoadReader reader, CHiValType type, CHiValueRaw *data, void *ud) { - if(type == CUTIHI_VAL_TEXT) { - uint32_t len; - reader(ud, &len, sizeof(len)); - - data->text = malloc(len + 1); - - reader(ud, data->text, len); - - data->text[len] = 0; - } else if(type == CUTIHI_VAL_VEC4) { - reader(ud, data->vec4, sizeof(data->vec4)); - } else if(type == CUTIHI_VAL_LINKED) { - uint64_t index; - reader(ud, &index, sizeof(index)); - - data->linked.to = ng->nodes[index]; - - uint16_t idx; - reader(ud, &idx, sizeof(idx)); - - data->linked.idx = idx; - } else if(type == CUTIHI_VAL_KEYED) { - uint64_t index; - reader(ud, &index, sizeof(index)); - - data->keyed = ng->keyframesList.keyframes[index]; - } -} - -CUTIVIS int CHi_NodeGraphSave(CHiNodeGraph *ng, CHiSaveWriter writer, void *ud) { - writer(ud, "\x71\x74\xCE\xA0", 4); - - writer(ud, &(float) {ng->duration}, sizeof(float)); - writer(ud, &(float) {ng->time}, sizeof(float)); - - writer(ud, &(uint64_t) {ng->keyframesList.count}, sizeof(uint64_t)); - - for(size_t i = 0; i < ng->keyframesList.count; i++) { - CHiKeyframes *kfs = ng->keyframesList.keyframes[i]; - - writer(ud, &(uint16_t) {kfs->type}, sizeof(uint16_t)); - - writer(ud, &(uint64_t) {kfs->count}, sizeof(uint64_t)); - - writer(ud, kfs->times, sizeof(*kfs->times) * kfs->count); - - for(size_t k = 0; k < kfs->count; k++) { - save_chival(ng, writer, kfs->type, kfs->values[k], ud); - } - - writer(ud, &(uint16_t) {kfs->extrapolationMode}, sizeof(uint16_t)); - writer(ud, kfs->extrapolationParameter, sizeof(kfs->extrapolationParameter)); - } - - writer(ud, &(uint64_t) {ng->count}, sizeof(uint64_t)); - - for(size_t i = 0; i < ng->count; i++) { - CHiPubNode *node = ng->nodes[i]; - - writer(ud, &(uint64_t) {node->type}, sizeof(uint64_t)); - } - - for(size_t i = 0; i < ng->count; i++) { - CHiPubNode *node = ng->nodes[i]; - - if(node->Save) { - node->Save(node, ud, writer); - } - - writer(ud, &(uint16_t) {node->sinkCount}, sizeof(uint16_t)); - - for(size_t sink = 0; sink < node->sinkCount; sink++) { - writer(ud, &(uint16_t) {node->sinks[sink].type}, sizeof(uint16_t)); - - save_chival(ng, writer, node->sinks[sink].type, node->sinks[sink].data, ud); - } - } - - return 0; -} - -CUTIVIS int CHi_NodeGraphLoad(CHiNodeGraph *ng, CHiLoadReader reader, void *ud) { - { - char magic[4]; - reader(ud, magic, sizeof(magic)); - if(memcmp(magic, "\x71\x74\xCE\xA0", 4)) { - return 1; - } - } - - CHi_NodeGraphReset(ng); - - reader(ud, &ng->duration, sizeof(float)); - reader(ud, &ng->time, sizeof(float)); - - { - uint64_t count; - reader(ud, &count, sizeof(count)); - ng->keyframesList.count = count; - } - - for(size_t i = 0; i < ng->keyframesList.count; i++) { - CHiKeyframes *kfs = ng->keyframesList.keyframes[i] = calloc(1, sizeof(*kfs)); - - { - uint16_t type; - reader(ud, &type, sizeof(type)); - kfs->type = type; - } - - { - uint64_t count; - reader(ud, &count, sizeof(count)); - kfs->count = count; - } - - kfs->times = calloc(kfs->count, sizeof(*kfs->times)); - - reader(ud, kfs->times, kfs->count * sizeof(*kfs->times)); - - for(size_t k = 0; k < kfs->count; k++) { - load_chival(ng, reader, kfs->type, &kfs->values[k], ud); - } - - { - uint16_t extrap; - reader(ud, &extrap, sizeof(extrap)); - kfs->extrapolationMode = extrap; - } - - reader(ud, kfs->extrapolationParameter, sizeof(kfs->extrapolationParameter)); - } - - { - uint64_t count; - reader(ud, &count, sizeof(count)); - ng->count = count; - } - - ng->capacity = ng->count < 8 ? 8 : ng->count; - - ng->nodes = calloc(ng->capacity, sizeof(*ng->nodes)); - - for(size_t i = 0; i < ng->count; i++) { - uint64_t type; - - reader(ud, &type, sizeof(type)); - - CHiPubNode *n = NULL; - - if(type == CUTIHI_T('CPre','view')) { - n = CHi_Preview(); - } else if(type == CUTIHI_T('CMix','er ')) { - n = CHi_Mixer(); - } else if(type == CUTIHI_T('CTex','t ')) { - n = CHi_Text(); - } else if(type == CUTIHI_T('CTim','e ')) { - n = CHi_Time(); - } else if(type == CUTIHI_T('CMod','ulat')) { - n = CHi_Modulate(); - } else if(type == CUTIHI_T('CCns','tCol')) { - n = CHi_ConstantSample(); - } else if(type == CUTIHI_T('CEmb','ed ')) { - n = CHi_Embed(); - } else if(type == CUTIHI_T('CIma','ge ')) { - n = CHi_Image(); - } else if(type == CUTIHI_T('CWin','dow ')) { - n = CHi_Window(); - } else if(type == CUTIHI_T('CInA','udio')) { - n = CHi_Microphone(); - } else if(type == CUTIHI_T('CExp','Wave')) { - n = CHi_ExportWav(); - } else if(type == CUTIHI_T('CMov','ie ')) { - n = CHi_Movie(); - } else if(type == CUTIHI_T('CEnc','GVP8')) { - n = CHi_EncodeVP8(); - } else if(type == CUTIHI_T('CEnc','GVP9')) { - n = CHi_EncodeVP9(); - } else if(type == CUTIHI_T('CExp','Webm')) { - n = CHi_MuxWebm(); - } else if(type == CUTIHI_T('CKey','hook')) { - n = CHi_Keyhook(); - } else if(type == CUTIHI_T('CKey','hook')) { - n = CHi_Keyhook(); - } else if(type == CUTIHI_T('CEnc','Opus')) { - n = CHi_EncodeOpus(); - } else if(type == CUTIHI_T('CWeb','Cam ')) { - n = CHi_Camera(); - } else if(type == CUTIHI_T('CCmp','nScl')) { - n = CHi_ComponentScale(); - } else if(type == CUTIHI_T('CEnc','H264')) { - n = CHi_EncodeH264(); - } else if(type == CUTIHI_T('CStr','RTMP')) { - n = CHi_StreamRTMP(); - } else if(type == CUTIHI_T('CEnc','AACL')) { - n = CHi_EncodeAAC(); - } else if(type == CUTIHI_T('CExp','Mkv ')) { - n = CHi_MuxMatroska(); - } - - n->ng = ng; - - if(!n) { - CHi_NodeGraphReset(ng); - puts("Error: Unknown node type!"); - return 1; - } - - ng->nodes[i] = n; - } - - for(size_t i = 0; i < ng->count; i++) { - CHiPubNode *n = ng->nodes[i]; - - { - uint16_t u16; - reader(ud, &u16, sizeof(u16)); - n->sinkCount = u16; - } - - n->sinks = calloc(n->sinkCount, sizeof(*n->sinks)); - - for(size_t s = 0; s < n->sinkCount; s++) { - { - uint16_t u16; - reader(ud, &u16, sizeof(u16)); - n->sinks[s].type = u16; - } - - load_chival(ng, reader, n->sinks[s].type, &n->sinks[s].data, ud); - - if(n->sinks[s].type == CUTIHI_VAL_LINKED) { - adjacency_add(n->sinks[s].data.linked.to, n); - } - } - } - - update_keyed_values(ng); - - return 0; -} - CUTIVIS bool CHi_Node_Active(CHiPubNode *pubn) { float now = CHi_Time_Get(pubn->ng); return pubn->lifespan.start <= now && (pubn->lifespan.end == 0 || now < pubn->lifespan.end); } + +CUTIVIS void CHi_Node_Destroy(CHiPubNode *pubn) { + for(size_t ni = 0; ni < pubn->ng->count; ni++) { + for(size_t si = 0; si < pubn->ng->nodes[ni]->sinkCount; si++) { + if(pubn->ng->nodes[ni]->sinks[si].linked.to == pubn) { + adjacency_remove(pubn, pubn->ng->nodes[ni]); + + pubn->ng->nodes[ni]->sinks[si].linked.to = NULL; + } + } + } + + for(size_t si = 0; si < pubn->sinkCount; si++) { + if(pubn->sinks[si].linked.to) { + adjacency_remove(pubn->sinks[si].linked.to, pubn); + } + } + + size_t ni; + for(ni = 0; ni < pubn->ng->count; ni++) { + if(pubn->ng->nodes[ni] == pubn) { + break; + } + } + + assert(ni < pubn->ng->count); + + if(pubn->Destroy) { + pubn->Destroy(pubn); + } else { + free(pubn); + } + + memmove(&pubn->ng->nodes[ni], &pubn->ng->nodes[ni + 1], sizeof(*pubn->ng->nodes) * (pubn->ng->count - ni - 1)); + + pubn->ng->count--; +} + +CUTIVIS void CHi_AddError(CHiPubNode *node, const char *err, size_t sink) { + for(size_t i = 0; i < CUTIHI_MAX_ERRORS; i++) { + if(!node->errors.active[i]) { + node->errors.active[i] = true; + strncpy(node->errors.code[i], "invalid type", CUTIHI_ERR_SIZE); + node->errors.sink[i] = sink; + break; + } + } +} diff --git a/hi/node.h b/hi/node.h index 0fb2145..33f5726 100644 --- a/hi/node.h +++ b/hi/node.h @@ -21,12 +21,18 @@ extern "C" { #define CUTIHI_T(a, b) ((((uint64_t) b) << 32) | a #endif +struct EBMLWriter; +struct CHiPubNode; + typedef size_t(*CHiSaveWriter)(void *ud, const void *data, size_t len); +typedef void(*CHiSaveCustomNodeData)(void *ud, struct EBMLWriter*, struct CHiPubNode*); + typedef size_t(*CHiLoadReader)(void *ud, void *data, size_t len); +typedef void(*CHiLoadCustomNodeData)(void *ud, uint64_t nodeIdx, uint64_t elId, const void *buf, size_t length); typedef enum { CUTIHI_VAL_NONE = 0, - CUTIHI_VAL_LINKED = 1, +// CUTIHI_VAL_LINKED = 1, CUTIHI_VAL_KEYED = 2, CUTIHI_VAL_SAMPLE = 3, CUTIHI_VAL_TEXT = 4, @@ -47,10 +53,6 @@ struct CHiPubNode; struct CHiKeyframes; typedef union { - struct { - struct CHiPubNode *to; - uint8_t idx; - } linked; struct CHiImage *sample; char *text; float vec4[4]; @@ -61,6 +63,10 @@ typedef union { typedef struct { CHiValType type; + struct { + struct CHiPubNode *to; + uint8_t idx; + } linked; CHiValueRaw data; } CHiValue; @@ -167,8 +173,8 @@ typedef struct CHiNodeGraph { CUTIVIS CHiNodeGraph *CHi_NewNodeGraph(); CUTIVIS CHiNodeGraph *CHi_NodeGraphReset(CHiNodeGraph *ng); -CUTIVIS int CHi_NodeGraphSave(CHiNodeGraph *ng, CHiSaveWriter writer, void *ud); -CUTIVIS int CHi_NodeGraphLoad(CHiNodeGraph *ng, CHiLoadReader reader, void *ud); +CUTIVIS int CHi_NodeGraphSave(CHiNodeGraph *ng, CHiSaveWriter, CHiSaveCustomNodeData, void *ud); +CUTIVIS int CHi_NodeGraphLoad(CHiNodeGraph *ng, CHiLoadReader, CHiLoadCustomNodeData, void *ud); CUTIVIS void CHi_RegisterNode(CHiNodeGraph*, CHiPubNode*); CUTIVIS void CHi_MakeDirty(CHiNodeGraph*, CHiPubNode*); @@ -181,7 +187,7 @@ CUTIVIS size_t CHi_GetClosestKeyframe(CHiNodeGraph *ng, CHiKeyframes *kfs, float CUTIVIS void CHi_SetExtrapolationMode(CHiNodeGraph *ng, CHiPubNode *n, size_t sinkIdx, CHiExtrapolationMode mode, float* params); CUTIVIS void CHi_DeleteKeyframe(CHiNodeGraph *ng, CHiKeyframes *kfs, size_t idx); -CUTIVIS int CHi_Hysteresis(CHiPubNode*); +CUTIVIS int CHi_Hysteresis(CHiNodeGraph*); CUTIVIS void CHi_SetDuration(CHiNodeGraph *ng, float); CUTIVIS void CHi_BeginCompilation(CHiNodeGraph *ng); diff --git a/hi/node_internal.h b/hi/node_internal.h new file mode 100644 index 0000000..86797eb --- /dev/null +++ b/hi/node_internal.h @@ -0,0 +1,96 @@ +#pragma once + +#include"node.h" + +static inline size_t bisect(const void *key, const void *base, size_t nmemb, size_t size, ssize_t(*compar)(const void*, const void*)) { + size_t low = 0, high = nmemb; + + while(low < high) { + size_t middle = (low + high) / 2; + if(compar((const void*) ((uintptr_t) base + size * middle), key) < 0) { + low = middle + 1; + } else { + high = middle; + } + } + + return low; +} + +static inline ssize_t float_compar(const void *A, const void *B) { + float a = *(float*) A; + float b = *(float*) B; + return (a > b) - (a < b); +} + +static inline int adjacencycmp(const void *a, const void *b) { + size_t v = (uintptr_t) ((CHiAdjacency*) a)[0] - (uintptr_t) ((CHiAdjacency*) b)[0]; + return v ? v : (uintptr_t) ((CHiAdjacency*) a)[1] - (uintptr_t) ((CHiAdjacency*) b)[1]; +} + +static inline void adjacency_add(CHiPubNode *source, CHiPubNode *sink) { + CHiNodeGraph *ng = source->ng; + + if(ng->adjacencyCount == ng->adjacencyCapacity) { + ng->adjacencies = realloc(ng->adjacencies, sizeof(CHiAdjacency) * (ng->adjacencyCapacity *= 2)); + } + + ng->adjacencies[ng->adjacencyCount][0] = source; + ng->adjacencies[ng->adjacencyCount][1] = sink; + ng->adjacencyCount++; + + qsort(ng->adjacencies, ng->adjacencyCount, sizeof(CHiAdjacency), adjacencycmp); +} + +static inline void adjacency_remove(CHiPubNode *source, CHiPubNode *sink) { + CHiNodeGraph *ng = source->ng; + + CHiAdjacency *adj = bsearch(&(CHiAdjacency) {source, sink}, ng->adjacencies, ng->adjacencyCount, sizeof(CHiAdjacency), adjacencycmp); + if(adj) { + memmove(adj, adj + 1, sizeof(CHiAdjacency) * (ng->adjacencyCount - (adj - ng->adjacencies) - 1)); + ng->adjacencyCount--; + } +} + +static inline void update_keyed_values(CHiNodeGraph *ng) { + for(size_t kfsIdx = 0; kfsIdx < ng->keyframesList.count; kfsIdx++) { + CHiKeyframes *kfs = ng->keyframesList.keyframes[kfsIdx]; + + kfs->current.type = kfs->type; + + float now = ng->time; + + size_t idx = bisect(&now, kfs->times, kfs->count, sizeof(now), float_compar); + + if(idx == 0) { + kfs->current.data = kfs->values[idx]; + + if(kfs->current.type == CUTIHI_VAL_VEC4 && kfs->extrapolationMode == CUTIHI_EXTRAPOLATION_CONSTANT) { + kfs->current.data.vec4[0] += (now - kfs->times[0]) * kfs->extrapolationParameter[0]; + kfs->current.data.vec4[1] += (now - kfs->times[0]) * kfs->extrapolationParameter[1]; + kfs->current.data.vec4[2] += (now - kfs->times[0]) * kfs->extrapolationParameter[2]; + kfs->current.data.vec4[3] += (now - kfs->times[0]) * kfs->extrapolationParameter[3]; + } + } else if(idx == kfs->count) { + kfs->current.data = kfs->values[idx - 1]; + + if(kfs->current.type == CUTIHI_VAL_VEC4 && kfs->extrapolationMode == CUTIHI_EXTRAPOLATION_CONSTANT) { + kfs->current.data.vec4[0] += (now - kfs->times[kfs->count - 1]) * kfs->extrapolationParameter[0]; + kfs->current.data.vec4[1] += (now - kfs->times[kfs->count - 1]) * kfs->extrapolationParameter[1]; + kfs->current.data.vec4[2] += (now - kfs->times[kfs->count - 1]) * kfs->extrapolationParameter[2]; + kfs->current.data.vec4[3] += (now - kfs->times[kfs->count - 1]) * kfs->extrapolationParameter[3]; + } + } else { + if(kfs->type == CUTIHI_VAL_VEC4) { + float alpha = (now - kfs->times[idx - 1]) / (kfs->times[idx] - kfs->times[idx - 1]); + + kfs->current.data.vec4[0] = kfs->values[idx - 1].vec4[0] + (kfs->values[idx].vec4[0] - kfs->values[idx - 1].vec4[0]) * alpha; + kfs->current.data.vec4[1] = kfs->values[idx - 1].vec4[1] + (kfs->values[idx].vec4[1] - kfs->values[idx - 1].vec4[1]) * alpha; + kfs->current.data.vec4[2] = kfs->values[idx - 1].vec4[2] + (kfs->values[idx].vec4[2] - kfs->values[idx - 1].vec4[2]) * alpha; + kfs->current.data.vec4[3] = kfs->values[idx - 1].vec4[3] + (kfs->values[idx].vec4[3] - kfs->values[idx - 1].vec4[3]) * alpha; + } else { + kfs->current.data = kfs->values[idx - 1]; + } + } + } +} diff --git a/hi/serialize.c b/hi/serialize.c new file mode 100644 index 0000000..782acb9 --- /dev/null +++ b/hi/serialize.c @@ -0,0 +1,382 @@ +#include"node.h" + +#include +#include +#include +#include +#include"node_internal.h" +#include + +#define ID_PROJECT 0x10000000 +#define ID_DURATION 0x4000 +#define ID_TIME 0x4001 +#define ID_KEYFRAMES 0x200000 +#define ID_CHIVAL 0x80 +#define ID_CHIVAL_TYPE 0x81 +#define ID_LINK 0x82 +#define ID_DATA 0x83 +#define ID_EXTRAPOLATION_MODE 0x4002 +#define ID_EXTRAPOLATION_PARAM 0x4003 +#define ID_NODE 0x84 +#define ID_NODE_TYPE 0x85 +#define ID_LIFESPAN_START 0x4004 +#define ID_LIFESPAN_END 0x4005 + +static void put_chival(CHiNodeGraph *ng, EBMLWriter *ebml, CHiValue val) { + ebml_writer_push(ebml, ID_CHIVAL); + ebml_writer_put(ebml, ID_CHIVAL_TYPE, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = val.type}); + + if(val.linked.to) { + size_t index; + for(index = 0; index < ng->count; index++) { + if(ng->nodes[index] == val.linked.to) { + break; + } + } + assert(index < ng->count); + + ebml_writer_put(ebml, ID_LINK, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = (index << 16) | (val.linked.idx)}); + } + + if(val.type == CUTIHI_VAL_TEXT) { + ebml_writer_put(ebml, ID_DATA, EBML_BINARY, (EBMLPrimitive) {.binary = {.ptr = val.data.text, .length = strlen(val.data.text)}}); + } else if(val.type == CUTIHI_VAL_VEC4) { + ebml_writer_put(ebml, ID_DATA, EBML_BINARY, (EBMLPrimitive) {.binary = {.ptr = val.data.vec4, .length = sizeof(val.data.vec4)}}); + } else if(val.type == CUTIHI_VAL_KEYED) { + size_t index; + for(index = 0; index < ng->keyframesList.count; index++) { + if(ng->keyframesList.keyframes[index] == val.data.keyed) { + break; + } + } + assert(index < ng->count); + + ebml_writer_put(ebml, ID_DATA, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = index}); + } + ebml_writer_pop(ebml); +} + +static size_t ebml_write_callback(EBMLWriter *ebml, const void *buf, size_t length) { + CHiSaveWriter writer = ((void**) ebml->ud)[0]; + void *ud = ((void**) ebml->ud)[1]; + return writer(ud, buf, length); +} +static void *ebml_alloc_callback(EBMLWriter *ebml, void *buf, size_t length) { + return realloc(buf, length); +} + +CUTIVIS int CHi_NodeGraphSave(CHiNodeGraph *ng, CHiSaveWriter writer, CHiSaveCustomNodeData customNodeData, void *ud) { + EBMLWriter ebml; + ebml_writer_init(&ebml); + + void *ebmlud[2] = {writer, ud}; + + ebml.ud = ebmlud; + ebml.eventWrite = ebml_write_callback; + ebml.eventAlloc = ebml_alloc_callback; + + ebml_writer_push(&ebml, 0x1A45DFA3); + ebml_writer_put(&ebml, 0x4286, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = 1}); + ebml_writer_put(&ebml, 0x4282, EBML_STRING, (EBMLPrimitive) {.string = "cuticle"}); + ebml_writer_pop(&ebml); + + ebml_writer_push(&ebml, ID_PROJECT); + if(ng->duration != -1) { + ebml_writer_put(&ebml, ID_DURATION, EBML_FLOAT4, (EBMLPrimitive) {.flt4 = ng->duration}); + } + ebml_writer_put(&ebml, ID_TIME, EBML_FLOAT4, (EBMLPrimitive) {.flt4 = ng->time}); + + for(size_t i = 0; i < ng->keyframesList.count; i++) { + CHiKeyframes *kfs = ng->keyframesList.keyframes[i]; + + ebml_writer_push(&ebml, ID_KEYFRAMES); + //ebml_writer_put(&ebml, ID_CHIVAL_TYPE, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = kfs->type}); + + for(size_t k = 0; k < kfs->count; k++) { + ebml_writer_put(&ebml, ID_TIME, EBML_FLOAT4, (EBMLPrimitive) {.flt4 = kfs->times[k]}); + put_chival(ng, &ebml, (CHiValue) {.type = kfs->type, .linked = {}, .data = kfs->values[k]}); + } + + ebml_writer_put(&ebml, ID_EXTRAPOLATION_MODE, EBML_UNSIGNED_INTEGER, (EBMLPrimitive) {.uInt = kfs->extrapolationMode}); + ebml_writer_put(&ebml, ID_EXTRAPOLATION_PARAM, EBML_BINARY, (EBMLPrimitive) {.binary = {.ptr = &kfs->extrapolationParameter, sizeof(kfs->extrapolationParameter)}}); + ebml_writer_pop(&ebml); + } + + for(size_t i = 0; i < ng->count; i++) { + CHiPubNode *node = ng->nodes[i]; + + ebml_writer_push(&ebml, ID_NODE); + ebml_writer_put(&ebml, ID_NODE_TYPE, EBML_BINARY, (EBMLPrimitive) {.binary = {.ptr = &node->type, sizeof(node->type)}}); + + if(node->lifespan.start != 0) { + ebml_writer_put(&ebml, ID_LIFESPAN_START, EBML_FLOAT4, (EBMLPrimitive) {.flt4 = node->lifespan.start}); + } + if(node->lifespan.end != 0) { + ebml_writer_put(&ebml, ID_LIFESPAN_END, EBML_FLOAT4, (EBMLPrimitive) {.flt4 = node->lifespan.end}); + } + + for(size_t sink = 0; sink < node->sinkCount; sink++) { + put_chival(ng, &ebml, node->sinks[sink]); + } + + if(customNodeData) { + customNodeData(ud, &ebml, node); + } + ebml_writer_pop(&ebml); + } + ebml_writer_pop(&ebml); + + while(!ebml_writer_flush(&ebml)); + + return 0; +} + +struct LoadState { + uint8_t *accum; + size_t accumSize; + + CHiNodeGraph *ng; + + void *ud; + CHiLoadCustomNodeData customNodeData; + + // Filled in by callbacks + CHiValue activeVal; + + // Filled in by callbacks + size_t activeNodeNextSinkIdx; + CHiPubNode *activeNode; +}; + +static EBMLElementType ebml_enter_callback(EBMLReader *ebml, uint64_t id, uint64_t length) { + struct LoadState *state = ebml->ud; + + if(id == ID_KEYFRAMES) { + state->ng->keyframesList.keyframes = realloc(state->ng->keyframesList.keyframes, sizeof(*state->ng->keyframesList.keyframes) * (++state->ng->keyframesList.count)); + state->ng->keyframesList.keyframes[state->ng->keyframesList.count - 1] = calloc(1, sizeof(CHiKeyframes)); + } else if(id == ID_CHIVAL) { + memset(&state->activeVal, 0, sizeof(state->activeVal)); + } + + return (id == ID_PROJECT || id == ID_KEYFRAMES || id == ID_CHIVAL || id == ID_NODE || id == 0x5000) ? EBML_TREE : EBML_BINARY; +} + +static void ebml_data_callback(EBMLReader *ebml, const uint8_t *data, size_t length) { + struct LoadState *state = ebml->ud; + + state->accum = realloc(state->accum, state->accumSize + length); + memcpy(state->accum + state->accumSize, data, length); + state->accumSize += length; +} + +static void ebml_exit_callback(EBMLReader *ebml) { + struct LoadState *state = ebml->ud; + + uint64_t parent = ebml->currentDepth >= 2 ? ebml->idStack[ebml->currentDepth - 2] : 0; + uint64_t id = ebml->idStack[ebml->currentDepth - 1]; + + if(id == ID_NODE) { + state->activeNode = NULL; + } else if(parent == 0x5000 && state->customNodeData) { + assert(state->activeNode == state->ng->nodes[state->ng->count - 1]); + + state->customNodeData(state->ud, state->ng->count - 1, id, state->accum, state->accumSize); + } else if(parent == ID_NODE) { + if(id == ID_NODE_TYPE) { + assert(state->accumSize <= 8); + + uint64_t type = 0; + memcpy((char*) &type + sizeof(type) - state->accumSize, state->accum, state->accumSize); + + CHiPubNode *n = NULL; + + if(type == CUTIHI_T('CPre','view')) { + n = CHi_Preview(); + } else if(type == CUTIHI_T('CMix','er ')) { + n = CHi_Mixer(); + } else if(type == CUTIHI_T('CTex','t ')) { + n = CHi_Text(); + } else if(type == CUTIHI_T('CTim','e ')) { + n = CHi_Time(); + } else if(type == CUTIHI_T('CMod','ulat')) { + n = CHi_Modulate(); + } else if(type == CUTIHI_T('CCns','tCol')) { + n = CHi_ConstantSample(); + } else if(type == CUTIHI_T('CEmb','ed ')) { + n = CHi_Embed(); + } else if(type == CUTIHI_T('CIma','ge ')) { + n = CHi_Image(); + } else if(type == CUTIHI_T('CWin','dow ')) { + n = CHi_Window(); + } else if(type == CUTIHI_T('CInA','udio')) { + n = CHi_Microphone(); + } else if(type == CUTIHI_T('CExp','Wave')) { + n = CHi_ExportWav(); + } else if(type == CUTIHI_T('CMov','ie ')) { + n = CHi_Movie(); + } else if(type == CUTIHI_T('CEnc','GVP8')) { + n = CHi_EncodeVP8(); + } else if(type == CUTIHI_T('CEnc','GVP9')) { + n = CHi_EncodeVP9(); + } else if(type == CUTIHI_T('CExp','Webm')) { + n = CHi_MuxWebm(); + } else if(type == CUTIHI_T('CKey','hook')) { + n = CHi_Keyhook(); + } else if(type == CUTIHI_T('CKey','hook')) { + n = CHi_Keyhook(); + } else if(type == CUTIHI_T('CEnc','Opus')) { + n = CHi_EncodeOpus(); + } else if(type == CUTIHI_T('CWeb','Cam ')) { + n = CHi_Camera(); + } else if(type == CUTIHI_T('CCmp','nScl')) { + n = CHi_ComponentScale(); + } else if(type == CUTIHI_T('CEnc','H264')) { + n = CHi_EncodeH264(); + } else if(type == CUTIHI_T('CStr','RTMP')) { + n = CHi_StreamRTMP(); + } else if(type == CUTIHI_T('CEnc','AACL')) { + n = CHi_EncodeAAC(); + } else if(type == CUTIHI_T('CExp','Mkv ')) { + n = CHi_MuxMatroska(); + } + + n->ng = state->ng; + + if(!n) { + CHi_NodeGraphReset(state->ng); + assert(0 && "Unknown node type."); + } + + state->ng->nodes = realloc(state->ng->nodes, sizeof(*state->ng->nodes) * (++state->ng->count)); + state->ng->nodes[state->ng->count - 1] = n; + + state->activeNodeNextSinkIdx = 0; + state->activeNode = n; + } else if(id == ID_LIFESPAN_START) { + state->activeNode->lifespan.start = *(float*) state->accum; + } else if(id == ID_LIFESPAN_END) { + state->activeNode->lifespan.end = *(float*) state->accum; + } else if(id == ID_CHIVAL) { + if(state->activeNodeNextSinkIdx >= state->activeNode->sinkCount) { + state->activeNode->sinks = realloc(state->activeNode->sinks, sizeof(*state->activeNode->sinks) * (state->activeNode->sinkCount = (state->activeNodeNextSinkIdx + 1))); + } + state->activeNode->sinks[state->activeNodeNextSinkIdx++] = state->activeVal; + } + } else if(parent == ID_CHIVAL) { + if(id == ID_CHIVAL_TYPE) { + state->activeVal.type = *(uint8_t*) state->accum; + } else if(id == ID_LINK) { + assert(state->accumSize <= 8); + + uint64_t data = 0; + memcpy((char*) &data + sizeof(data) - state->accumSize, state->accum, state->accumSize); + data = htobe64(data); + + uint16_t srcIdx = data & 0xFFFF; + uint64_t nodeIdx = data >> 16; + + // linked.to is supposed to be a pointer but not all nodes are + // available at this stage, so we'll fix it up after the EBML + // parsing finishes + state->activeVal.linked.to = (void*) (nodeIdx + 1); + state->activeVal.linked.idx = srcIdx; + } else if(id == ID_DATA) { + if(state->activeVal.type == CUTIHI_VAL_TEXT) { + state->activeVal.data.text = calloc(1, state->accumSize + 1); + memcpy(state->activeVal.data.text, state->accum, state->accumSize); + } else if(state->activeVal.type == CUTIHI_VAL_VEC4) { + assert(state->accumSize <= 16 && state->accumSize % 4 == 0); + + memcpy(state->activeVal.data.vec4, state->accum, state->accumSize); + } else if(state->activeVal.type == CUTIHI_VAL_KEYED) { + assert(state->accumSize <= 8); + + uint64_t idx = 0; + memcpy((char*) &idx + sizeof(idx) - state->accumSize, state->accum, state->accumSize); + idx = htobe64(idx); + + // keyed is supposed to be a pointer but keyframes may not + // be available at this stage, so we'll fix it up after the + // EBML parsing finishes + state->activeVal.data.keyed = (void*) idx; + } else { + assert(0 && "Cannot load CHiValue data."); + } + } + } else if(parent == ID_KEYFRAMES) { + CHiKeyframes *kfs = state->ng->keyframesList.keyframes[state->ng->keyframesList.count - 1]; + if(id == ID_TIME) { + kfs->times = realloc(kfs->times, sizeof(*kfs->times) * (++kfs->count)); + kfs->values = realloc(kfs->values, sizeof(*kfs->values) * kfs->count); + + kfs->times[kfs->count - 1] = *(float*) state->accum; + } else if(id == ID_CHIVAL) { + assert(state->activeVal.linked.to == NULL); + + kfs->values[kfs->count - 1] = state->activeVal.data; + } else if(id == ID_EXTRAPOLATION_MODE) { + kfs->extrapolationMode = *(uint8_t*) state->accum; + } else if(id == ID_EXTRAPOLATION_PARAM) { + assert(state->accumSize == 16); + + memcpy(&kfs->extrapolationParameter, state->accum, 16); + } + } + + state->accumSize = 0; +} + +CUTIVIS int CHi_NodeGraphLoad(CHiNodeGraph *ng, CHiLoadReader reader, CHiLoadCustomNodeData customNodeData, void *ud) { + CHi_NodeGraphReset(ng); + + ng->time = 0; + ng->duration = -1; + + EBMLReader ebml; + ebml_reader_init(&ebml); + + struct LoadState state = {.ng = ng, .ud = ud, .customNodeData = customNodeData}; + ebml.ud = &state; + + ebml.eventEnterElement = ebml_enter_callback; + ebml.eventDataChunk = ebml_data_callback; + ebml.eventExitElement = ebml_exit_callback; + + size_t i = 0; + uint8_t buf[4096]; + while(1) { + size_t readBytes = reader(ud, buf + i, sizeof(buf) - i); + + i += readBytes; + + size_t fedBytes = ebml_reader_feed(&ebml, buf, i); + + memmove(buf, buf + fedBytes, sizeof(buf) - fedBytes); + + i -= fedBytes; + + if(readBytes == 0 && fedBytes == 0) { + break; + } + } + + // Fix-up links/adjacencies + for(size_t i = 0; i < ng->count; i++) { + CHiPubNode *n = ng->nodes[i]; + for(size_t s = 0; s < n->sinkCount; s++) { + if(n->sinks[s].linked.to) { + n->sinks[s].linked.to = ng->nodes[(uintptr_t) n->sinks[s].linked.to - 1]; + + adjacency_add(n->sinks[s].linked.to, n); + } + + if(n->sinks[s].type == CUTIHI_VAL_KEYED) { + n->sinks[s].data.keyed = ng->keyframesList.keyframes[(uintptr_t) n->sinks[s].data.keyed]; + } + } + } + + update_keyed_values(ng); + + return 0; +} diff --git a/hi/webmenc.cpp b/hi/webmenc.cpp index e36a6aa..27c3668 100644 --- a/hi/webmenc.cpp +++ b/hi/webmenc.cpp @@ -43,7 +43,7 @@ static int muxwebm_perform(CHiPubNode *pubn) { CHiMuxWebmNode *alln = (CHiMuxWebmNode*) pubn; - if(pubn->sinks[1].data.linked.to) { + if(pubn->sinks[1].linked.to) { CHiBSFrames *opus = CHi_Crawl(&pubn->sinks[1])->data.bitstream; for(size_t i = 0; i < opus->count; i++) { alln->audioBacklog.push(opus->data[i]); @@ -57,7 +57,7 @@ static int muxwebm_perform(CHiPubNode *pubn) { } } - while(pubn->sinks[1].data.linked.to && alln->audioBacklog.size() > 0 && alln->videoBacklog.size() > 0 && alln->audioBacklog.front().timestamp <= alln->videoBacklog.front().timestamp) { + while(pubn->sinks[1].linked.to && alln->audioBacklog.size() > 0 && alln->videoBacklog.size() > 0 && alln->audioBacklog.front().timestamp <= alln->videoBacklog.front().timestamp) { Frame frame; frame.Init((const uint8_t*) alln->audioBacklog.front().ptr, alln->audioBacklog.front().sz); frame.set_track_number(alln->audioTrack); @@ -68,7 +68,7 @@ static int muxwebm_perform(CHiPubNode *pubn) { alln->audioBacklog.pop(); } - if(pubn->sinks[1].data.linked.to == NULL || (alln->audioBacklog.size() > 0 && alln->videoBacklog.size() > 0 && alln->audioBacklog.front().timestamp >= alln->videoBacklog.front().timestamp)) { + if(pubn->sinks[1].linked.to == NULL || (alln->audioBacklog.size() > 0 && alln->videoBacklog.size() > 0 && alln->audioBacklog.front().timestamp >= alln->videoBacklog.front().timestamp)) { Frame frame; if(!frame.Init((const uint8_t*) alln->videoBacklog.front().ptr, alln->videoBacklog.front().sz)) puts("INIT FAIL"); frame.set_track_number(alln->videoTrack); @@ -122,7 +122,7 @@ CUTIVIS int CHi_MuxWebm_Start(CHiPubNode *pubn) { alln->seg.GetSegmentInfo()->set_writing_app("Cuticle"); /* Hack into first frame to get resolution */ - CHiPubNode *evp9 = pubn->sinks[0].data.linked.to; + CHiPubNode *evp9 = pubn->sinks[0].linked.to; CHiImage *firstFrame = (CHiImage*) CHi_Crawl(&evp9->sinks[0])->data.sample; alln->videoTrack = alln->seg.AddVideoTrack(firstFrame->width, firstFrame->height, 0); @@ -137,7 +137,7 @@ CUTIVIS int CHi_MuxWebm_Start(CHiPubNode *pubn) { track->SetColour(colourspace); alln->seg.CuesTrack(alln->videoTrack); - if(pubn->sinks[1].data.linked.to) { + if(pubn->sinks[1].linked.to) { struct __attribute__((packed)) { uint32_t magic1; uint32_t magic2; diff --git a/ui/frame.cpp b/ui/frame.cpp index 83eaa80..7eed5a0 100644 --- a/ui/frame.cpp +++ b/ui/frame.cpp @@ -22,13 +22,26 @@ #include"timeline.h" #include #include +#include #define SSE_MATHFUN_WITH_CODE #include #include -static Frame *globaldis; +extern Frame *globaldis; + +// The Preview node is actually a NOOP +// Must inject our own function when either loading or creating projects +static int INJECTED_PREVIEW_FUNC(CHiPubNode *preview) { + CHiValue *val = CHi_Crawl(&preview->sinks[0]); + + if(val->type == CUTIHI_VAL_SAMPLE && val->data.sample) { + globaldis->viewer->SetImage(val->data.sample); + } + + return 1; +}; std::string node_name_from_id(uint64_t id) { static std::unordered_map NODE_ID_NAMES = { @@ -224,76 +237,40 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem wxFileDialog save{this, "Save", "", "", "Cuticle Project (*.ctc)|*.ctc", wxFD_SAVE | wxFD_OVERWRITE_PROMPT}; if(save.ShowModal() == wxID_OK) { - FILE *f = fopen(save.GetPath().mb_str(), "wb"); + struct UD { + FILE *f; + Frame *frame; + } ud; - CHi_NodeGraphSave(graph->backendNG, +[](void *ud, const void *data, size_t len){ - auto f = (FILE*) ud; - - return fwrite(data, 1, len, f); - }, f); - - for(size_t n = 0; n < graph->backendNG->count; n++) { - GrNode *gn = graph->get_graphical(graph->backendNG->nodes[n]); - - int32_t pos[2] = {gn->GetPosition().x, gn->GetPosition().y}; - fwrite(pos, sizeof(pos), 1, f); + wxString path = save.GetPath(); + if(!path.EndsWith(".ctc")) { + path += ".ctc"; } - fclose(f); + ud.f = fopen(path.mb_str(), "wb"); + ud.frame = this; + + CHi_NodeGraphSave(graph->backendNG, +[](void *udPtr, const void *data, size_t len){ + auto &ud = *(UD*) udPtr; + return fwrite(data, 1, len, ud.f); + }, +[](void *udPtr, EBMLWriter *ebml, CHiPubNode *node){ + auto &ud = *(UD*) udPtr; + + GrNode *gn = ud.frame->graph->get_graphical(node); + + int32_t pos[2] = {gn->GetPosition().x, gn->GetPosition().y}; + ebml_writer_push(ebml, 0x5000); + ebml_writer_put(ebml, 0x5001, EBML_BINARY, (EBMLPrimitive) {.binary = {.ptr = (uint8_t*) &pos, sizeof(pos)}}); + ebml_writer_pop(ebml); + }, &ud); + + fclose(ud.f); } } else if(ev.GetId() == wxID_OPEN) { wxFileDialog load{this, "Load", "", "", "Cuticle Project (*.ctc)|*.ctc", wxFD_OPEN | wxFD_FILE_MUST_EXIST}; if(load.ShowModal() == wxID_OK) { - FILE *f = fopen(load.GetPath().mb_str(), "rb"); - - if(CHi_NodeGraphLoad(graph->backendNG, +[](void *ud, void *data, size_t len){ - auto f = (FILE*) ud; - - return fread(data, 1, len, f); - }, f) == 0) { - - for(GrNode *gnode : graph->gnodes) { - gnode->Destroy(); - } - - graph->gnodes.clear(); - graph->links.clear(); - - for(size_t n = 0; n < graph->backendNG->count; n++) { - GrNode *gnode = new GrNode(graph); - - int32_t pos[2]; - fread(pos, sizeof(pos), 1, f); - - gnode->SetPosition({pos[0], pos[1]}); - - gnode->logical = graph->backendNG->nodes[n]; - - ShapeGrNode(gnode); - - graph->gnodes.push_back(gnode); - } - - for(size_t n = 0; n < graph->backendNG->count; n++) { - CHiPubNode *node = graph->backendNG->nodes[n]; - - for(size_t s = 0; s < node->sinkCount; s++) { - if(node->sinks[s].type == CUTIHI_VAL_LINKED) { - graph->links.push_back(NodeGraph::Link{graph->gnodes[n], s, *std::find_if(graph->gnodes.begin(), graph->gnodes.end(), [=](GrNode *gn){ return gn->logical == node->sinks[s].data.linked.to; }), node->sinks[s].data.linked.idx}); - } - } - } - - toolbar.duration->SetSeconds(std::max(0.f, graph->backendNG->duration)); - - if(graph->backendNG->duration <= 0) { - toolbar.durationEnable->SetValue(false); - toolbar.duration->Enable(false); - } - } - - fclose(f); + LoadProject(load.GetPath().utf8_string()); } } }); @@ -347,27 +324,6 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem tlba->AddControl(toolbar.duration); tlba->AddControl(toolbar.btnPerform); - graph->backendNG->eventOnStopComplete = +[](CHiNodeGraph *ng){ - wxTheApp->CallAfter([ng](){ - wxButton *btn = ((Frame*) ng->ud)->toolbar.btnPerform; - btn->Enable(); - btn->SetLabel("Compile"); - - auto tlba = ((Frame*) ng->ud)->tlba; - tlba->EnableTool(wxID_SAVE, true); - tlba->EnableTool(wxID_OPEN, true); - }); - }; - - graph->backendNG->eventOnError = +[](CHiNodeGraph *ng, CHiPubNode *n) { - wxTheApp->CallAfter([ng, n](){ - auto frame = (Frame*) ng->ud; - - GrNode *gn = frame->graph->get_graphical(n); - gn->Refresh(); - }); - }; - tlba->Realize(); aui.SetFlags(wxAUI_MGR_LIVE_RESIZE | wxAUI_MGR_DEFAULT); @@ -375,6 +331,7 @@ Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystem Centre(); + LoadProject(""); graph->CreatePreviewNode(); } @@ -382,6 +339,101 @@ Frame::~Frame() { aui.UnInit(); } +void Frame::LoadProject(std::string filename) { + struct UD { + FILE *f; + Frame *frame; + std::unordered_map nodePositions; + } ud = {}; + + if(filename != "") { + ud.f = fopen(filename.c_str(), "rb"); + } + ud.frame = this; + + if(filename == "" || CHi_NodeGraphLoad(graph->backendNG, +[](void *udPtr, void *data, size_t len){ + auto &ud = *(UD*) udPtr; + return fread(data, 1, len, ud.f); + }, +[](void *udPtr, uint64_t nodeIdx, uint64_t elId, const void *data, size_t len){ + auto &ud = *(UD*) udPtr; + if(elId == 0x5001) { + auto pos = (const int32_t*) data; + ud.nodePositions.emplace(nodeIdx, wxPoint{pos[0], pos[1]}); + } + }, &ud) == 0) { + + for(GrNode *gnode : graph->gnodes) { + gnode->Destroy(); + } + + graph->gnodes.clear(); + graph->links.clear(); + + for(size_t n = 0; n < graph->backendNG->count; n++) { + GrNode *gnode = new GrNode(graph); + + gnode->SetPosition(ud.nodePositions[n]); + + gnode->logical = graph->backendNG->nodes[n]; + + ShapeGrNode(gnode); + + if(gnode->logical->type == CUTIHI_T('CPre', 'view')) { + gnode->logical->Perform = INJECTED_PREVIEW_FUNC; + } + + graph->gnodes.push_back(gnode); + } + + for(size_t n = 0; n < graph->backendNG->count; n++) { + CHiPubNode *node = graph->backendNG->nodes[n]; + + for(size_t s = 0; s < node->sinkCount; s++) { + if(node->sinks[s].linked.to) { + graph->links.push_back(NodeGraph::Link{graph->gnodes[n], s, *std::find_if(graph->gnodes.begin(), graph->gnodes.end(), [=](GrNode *gn){ return gn->logical == node->sinks[s].linked.to; }), node->sinks[s].linked.idx}); + } + } + } + + toolbar.duration->SetSeconds(std::max(0.f, graph->backendNG->duration)); + + if(graph->backendNG->duration <= 0) { + toolbar.durationEnable->SetValue(false); + toolbar.duration->Enable(false); + } + + timeline->ResetRows(); + + graph->backendNG->eventOnStopComplete = +[](CHiNodeGraph *ng){ + wxTheApp->CallAfter([ng](){ + wxButton *btn = ((Frame*) ng->ud)->toolbar.btnPerform; + btn->Enable(); + btn->SetLabel("Compile"); + + auto tlba = ((Frame*) ng->ud)->tlba; + tlba->EnableTool(wxID_SAVE, true); + tlba->EnableTool(wxID_OPEN, true); + }); + }; + + graph->backendNG->eventOnError = +[](CHiNodeGraph *ng, CHiPubNode *n) { + wxTheApp->CallAfter([ng, n](){ + auto frame = (Frame*) ng->ud; + + GrNode *gn = frame->graph->get_graphical(n); + gn->Refresh(); + }); + }; + } + + if(ud.f) { + fclose(ud.f); + } + + // Force everything in the graph to be computed so the preview immediately shows up + CHi_Hysteresis(graph->backendNG); +} + bool GrNode::MouseOverPort(wxPoint point, bool &source, int &i) { if(point.y < 26 || point.x < 0 || point.x > GetSize().x) return false; @@ -400,7 +452,7 @@ void GrNode::MakeKeyframe(int sinkIdx) { CHi_MakeKeyframe(ng->backendNG, this->logical, sinkIdx); - ((Frame*) ng->GetParent())->timeline->Refresh(); + globaldis->timeline->ResetRows(); } static bool has_errors(CHiPubNode *pn) { @@ -1048,7 +1100,7 @@ NodeGraph::NodeGraph(Frame *f) : wxPanel(f, wxID_ANY) { after(); - ((Frame*) GetParent())->timeline->ResetRows(); + globaldis->timeline->ResetRows(); } pthread_mutex_unlock(&backendNG->mut); @@ -1114,17 +1166,16 @@ NodeGraph::~NodeGraph() { void NodeGraph::Alinken(Link l) { pthread_mutex_lock(&backendNG->mut); - CHiValue newv; - newv.type = CUTIHI_VAL_LINKED; - newv.data.linked.to = l.output->logical; - newv.data.linked.idx = l.o; + CHiValue newv = {}; + newv.linked.to = l.output->logical; + newv.linked.idx = l.o; int err = CHi_ConfigureSink(l.input->logical, l.i, newv); pthread_mutex_unlock(&backendNG->mut); if(!err) { - ((Frame*) GetParent())->stba->SetStatusText("Uh oh! Hur-hur, there's a WAACKY cycle! Can't do, sorry friend."); + globaldis->stba->SetStatusText("Uh oh! Hur-hur, there's a WAACKY cycle! Can't do, sorry friend."); return; } @@ -1161,7 +1212,7 @@ void NodeGraph::Dirtify(GrNode *g) { } }*/ - CHi_Hysteresis(gnodes[0]->logical); + CHi_Hysteresis(backendNG); } bool operator==(const NodeGraph::Link &l, const NodeGraph::Link &r) { @@ -1197,17 +1248,23 @@ void NodeGraph::CreatePreviewNode() { v->SetPosition(wxPoint{GetSize().x - v->GetSize().x - 8, 8}); - // The Preview node is actually a NOOP - // Must inject our own function in Perform - v->logical->Perform = +[](CHiPubNode *preview){ - CHiValue *val = CHi_Crawl(&preview->sinks[0]); - - if(val->type == CUTIHI_VAL_SAMPLE && val->data.sample) { - globaldis->viewer->SetImage(val->data.sample); + v->logical->Perform = INJECTED_PREVIEW_FUNC; +} + +void NodeGraph::DestroyNode(GrNode *gn) { + for(auto it = links.begin(); it != links.end();) { + if(it->input == gn || it->output == gn) { + it = links.erase(it); + } else { + it++; } - - return 1; - }; + } + + gnodes.erase(std::find(gnodes.begin(), gnodes.end(), gn)); + + wxTheApp->CallAfter([=](){ + gn->Destroy(); + }); } GrNode *NodeGraph::get_graphical(CHiPubNode *n) { diff --git a/ui/frame.h b/ui/frame.h index edbdba0..23b7788 100644 --- a/ui/frame.h +++ b/ui/frame.h @@ -43,6 +43,8 @@ struct Frame : wxFrame { Frame(); virtual ~Frame(); + + void LoadProject(std::string filename); }; struct GrNode : wxPanel { @@ -136,6 +138,8 @@ struct NodeGraph : wxPanel { void CreatePreviewNode(); + void DestroyNode(GrNode*); + GrNode *get_graphical(CHiPubNode*); }; diff --git a/ui/main.cpp b/ui/main.cpp index 63e5aab..5e1942f 100644 --- a/ui/main.cpp +++ b/ui/main.cpp @@ -2,6 +2,8 @@ #include"frame.h" +Frame *globaldis; + struct App : wxApp { virtual bool OnInit() { (new Frame())->Show(true); @@ -9,4 +11,4 @@ struct App : wxApp { } }; -wxIMPLEMENT_APP(App); \ No newline at end of file +wxIMPLEMENT_APP(App); diff --git a/ui/timeline.cpp b/ui/timeline.cpp index 2844721..f8d7990 100644 --- a/ui/timeline.cpp +++ b/ui/timeline.cpp @@ -10,6 +10,8 @@ #include"frame.h" +extern Frame *globaldis; + static wxBitmap bmpKf; static wxBitmap bmpKfExtrap; @@ -38,7 +40,7 @@ bool Timeline::MouseOverKF(wxPoint p, CHiKeyframes* &kfs, size_t &kfIdxRet) { return false; } - auto f = (Frame*) GetParent(); + auto f = globaldis; kfs = row->kfs; @@ -59,7 +61,7 @@ bool Timeline::MouseOverKF(wxPoint p, CHiKeyframes* &kfs, size_t &kfIdxRet) { void Timeline::ResetRows() { rows.clear(); - auto frame = (Frame*) GetParent(); + auto frame = globaldis; auto kfsList = &frame->graph->backendNG->keyframesList; @@ -92,13 +94,15 @@ void Timeline::ResetRows() { y += row.h; } + + Refresh(); } float Timeline::SnapTime(float t) { float minDist = FLT_MAX; float newT = t; - auto f = (Frame*) GetParent(); + auto f = globaldis; std::vector times; for(Timeline::Row &row : rows) { @@ -149,7 +153,7 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) { Bind(wxEVT_LEFT_DOWN, [=](wxMouseEvent &ev){ Timeline::Row *row = GetRow(ev.GetPosition().y); - auto f = (Frame*) GetParent(); + auto f = globaldis; float t = (ev.GetX() + camX - ZERO_TIME_BASE) / (float) scale; if(ev.ControlDown()) { @@ -196,14 +200,14 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) { t = SnapTime(t); } - row->gn->logical->lifespan.end = t; + row->gn->logical->lifespan.end = t < 0 ? 0 : t; Refresh(); } }); Bind(wxEVT_MOTION, [=](wxMouseEvent &ev){ - auto f = (Frame*) GetParent(); + auto f = globaldis; if(HasCapture()) { if(captureMode == Timeline::CaptureMode::CAM) { @@ -319,7 +323,7 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) { menu.Bind(wxEVT_MENU, [=](wxCommandEvent &ev){ if(ev.GetId() == idDel) { - auto f = (Frame*) GetParent(); + auto f = globaldis; CHi_DeleteKeyframe(f->graph->backendNG, kfs, kfIdx); @@ -337,7 +341,7 @@ Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) { } void Timeline::Paint(wxPaintEvent &ev) { - auto frame = (Frame*) GetParent(); + auto frame = globaldis; wxPaintDC dc{this}; @@ -405,6 +409,10 @@ void Timeline::Paint(wxPaintEvent &ev) { if(end == 0) { end = gn->logical->ng->duration; + if(end == -1) { + // Hack :) + end = 3600 * 10; + } } start *= scale;