Move to Git

This commit is contained in:
mid 2024-06-26 20:47:26 +03:00
commit 18da1dabcd
34 changed files with 5999 additions and 0 deletions

24
Makefile Normal file
View File

@ -0,0 +1,24 @@
CXXFLAGS := -D_POSIX_C_SOURCE=200809L -Wno-narrowing -march=native -flto -Wall -fvisibility=hidden -fPIC -msse4 -I./ -I/usr/local/include/sail `pkg-config --cflags pango opus libv4l2` '-Wl,-rpath,$$ORIGIN' -Wno-multichar
LDFLAGS := -lwebm -lpng -lvpx -lsail -lsail-manip `pkg-config --libs pango opus libv4l2` -lportaudio -lXtst
ifneq ($(RELEASE),0)
CXXFLAGS := $(CXXFLAGS) -O0 -g3 -fsanitize=address
else
CXXFLAGS := $(CXXFLAGS) -O3 -fopenmp
endif
all:
gcc $(CXXFLAGS) -std=c99 -shared -c -o node.o hi/node.c $(LDFLAGS)
gcc $(CXXFLAGS) -std=c99 -shared -c -o window.o hi/window.c $(LDFLAGS)
gcc $(CXXFLAGS) -std=c99 -shared -c -o microphone.o hi/microphone.c $(LDFLAGS)
gcc $(CXXFLAGS) -std=c99 -shared -c -o mode.o hi/mode.c $(LDFLAGS)
gcc $(CXXFLAGS) -std=c99 -shared -c -o img.o hi/img.c $(LDFLAGS)
g++ $(CXXFLAGS) -std=c++11 -shared -c -o webmdec.o hi/webmdec.cpp $(LDFLAGS)
g++ $(CXXFLAGS) -std=c++11 -shared -c -o webmenc.o hi/webmenc.cpp $(LDFLAGS)
gcc $(CXXFLAGS) -std=c99 -shared -c -o opus.o hi/opus.c $(LDFLAGS)
gcc $(CXXFLAGS) -std=c99 -shared -c -o webcam.o hi/webcam.c $(LDFLAGS)
gcc $(CXXFLAGS) -std=c99 -shared -c -o scale.o hi/relay.c $(LDFLAGS)
gcc $(CXXFLAGS) -shared -o libcutihi.so -shared node.o webmdec.o webmenc.o window.o microphone.o mode.o img.o opus.o webcam.o scale.o $(LDFLAGS)
g++ $(CXXFLAGS) -std=c++11 `wx-config --cflags base,adv,core,aui` -o cuticle ui/main.cpp ui/frame.cpp ui/textctrl.cpp ui/timeline.cpp -L./ -lcutihi $(LDFLAGS) `wx-config --libs base,adv,core,aui`

26
hi/bs.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef _CUTI_BS_H
#define _CUTI_BS_H
#ifdef __cplusplus
extern "C" {
#endif
#define CUTIHI_BS_FLAG_KEY 1
typedef struct {
uint64_t timestamp;
uint32_t sz;
uint8_t flags;
void *ptr;
} CHiBSFrame;
typedef struct {
size_t count;
CHiBSFrame data[];
} CHiBSFrames;
#ifdef __cplusplus
}
#endif
#endif

3
hi/defs.h Normal file
View File

@ -0,0 +1,3 @@
#pragma once
#define CUTIVIS __attribute__((visibility("default")))

39
hi/img.c Normal file
View File

@ -0,0 +1,39 @@
#include"img.h"
#include<mm_malloc.h>
#include<string.h>
CUTIVIS CHiImage* CHi_Image_New(uint8_t bpc, uint8_t channels, uint16_t stride, uint16_t width, uint16_t height, void *data) {
CHiImage *img = malloc(sizeof(*img));
img->bpc = bpc;
img->channels = channels;
img->stride = stride;
img->width = width;
img->height = height;
if(data) img->data16 = data;
else img->data16 = _mm_malloc(bpc * stride * height, 16);
img->owned = !data;
return img;
}
CUTIVIS void CHi_Image_Free(CHiImage *img) {
if(img->owned) {
_mm_free(img->data16);
}
free(img);
}
CUTIVIS void CHi_Restride(const void *oldbuf_, void *newbuf_, uint16_t oldStride, uint16_t newStride, uint16_t rows) {
const uint8_t *oldbuf = oldbuf_;
uint8_t *newbuf = newbuf_;
if(oldStride == newStride && oldbuf == newbuf) {
return;
}
while(rows) {
uint16_t row = --rows;
memmove(&newbuf[newStride * row], &oldbuf[oldStride * row], oldStride);
}
}

29
hi/img.h Normal file
View File

@ -0,0 +1,29 @@
#pragma once
#include<stdint.h>
#include"defs.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct CHiImage {
uint8_t bpc;
uint8_t channels;
uint16_t stride;
uint16_t width;
uint16_t height;
union {
uint16_t *data16;
};
uint8_t owned;
} CHiImage;
CUTIVIS CHiImage* CHi_Image_New(uint8_t bpc, uint8_t channels, uint16_t stride, uint16_t width, uint16_t height, void *data);
CUTIVIS void CHi_Image_Free(CHiImage *img);
CUTIVIS void CHi_Restride(const void *oldbuf, void *newbuf, uint16_t oldStride, uint16_t newStride, uint16_t rows);
#ifdef __cplusplus
}
#endif

1523
hi/kumb.h Normal file

File diff suppressed because it is too large Load Diff

114
hi/linearity.h Normal file
View File

@ -0,0 +1,114 @@
#pragma once
#define SSE_MATHFUN_WITH_CODE
#include"kumb.h"
// exp2f4 and log2f4 by Jose Fonseca (MIT License)
#define EXP_POLY_DEGREE 3
#define POLY0(x, c0) _mm_set1_ps(c0)
#define POLY1(x, c0, c1) _mm_add_ps(_mm_mul_ps(POLY0(x, c1), x), _mm_set1_ps(c0))
#define POLY2(x, c0, c1, c2) _mm_add_ps(_mm_mul_ps(POLY1(x, c1, c2), x), _mm_set1_ps(c0))
#define POLY3(x, c0, c1, c2, c3) _mm_add_ps(_mm_mul_ps(POLY2(x, c1, c2, c3), x), _mm_set1_ps(c0))
#define POLY4(x, c0, c1, c2, c3, c4) _mm_add_ps(_mm_mul_ps(POLY3(x, c1, c2, c3, c4), x), _mm_set1_ps(c0))
#define POLY5(x, c0, c1, c2, c3, c4, c5) _mm_add_ps(_mm_mul_ps(POLY4(x, c1, c2, c3, c4, c5), x), _mm_set1_ps(c0))
static inline __m128 exp2f4(__m128 x)
{
__m128i ipart;
__m128 fpart, expipart, expfpart;
x = _mm_min_ps(x, _mm_set1_ps( 129.00000f));
x = _mm_max_ps(x, _mm_set1_ps(-126.99999f));
/* ipart = int(x - 0.5) */
ipart = _mm_cvtps_epi32(_mm_sub_ps(x, _mm_set1_ps(0.5f)));
/* fpart = x - ipart */
fpart = _mm_sub_ps(x, _mm_cvtepi32_ps(ipart));
/* expipart = (float) (1 << ipart) */
expipart = _mm_castsi128_ps(_mm_slli_epi32(_mm_add_epi32(ipart, _mm_set1_epi32(127)), 23));
/* minimax polynomial fit of 2**x, in range [-0.5, 0.5[ */
#if EXP_POLY_DEGREE == 5
expfpart = POLY5(fpart, 9.9999994e-1f, 6.9315308e-1f, 2.4015361e-1f, 5.5826318e-2f, 8.9893397e-3f, 1.8775767e-3f);
#elif EXP_POLY_DEGREE == 4
expfpart = POLY4(fpart, 1.0000026f, 6.9300383e-1f, 2.4144275e-1f, 5.2011464e-2f, 1.3534167e-2f);
#elif EXP_POLY_DEGREE == 3
expfpart = POLY3(fpart, 9.9992520e-1f, 6.9583356e-1f, 2.2606716e-1f, 7.8024521e-2f);
#elif EXP_POLY_DEGREE == 2
expfpart = POLY2(fpart, 1.0017247f, 6.5763628e-1f, 3.3718944e-1f);
#else
#error
#endif
return _mm_mul_ps(expipart, expfpart);
}
#define LOG_POLY_DEGREE 5
static inline __m128 log2f4(__m128 x)
{
__m128i exp = _mm_set1_epi32(0x7F800000);
__m128i mant = _mm_set1_epi32(0x007FFFFF);
__m128 one = _mm_set1_ps( 1.0f);
__m128i i = _mm_castps_si128(x);
__m128 e = _mm_cvtepi32_ps(_mm_sub_epi32(_mm_srli_epi32(_mm_and_si128(i, exp), 23), _mm_set1_epi32(127)));
__m128 m = _mm_or_ps(_mm_castsi128_ps(_mm_and_si128(i, mant)), one);
__m128 p;
/* Minimax polynomial fit of log2(x)/(x - 1), for x in range [1, 2[ */
#if LOG_POLY_DEGREE == 6
p = POLY5( m, 3.1157899f, -3.3241990f, 2.5988452f, -1.2315303f, 3.1821337e-1f, -3.4436006e-2f);
#elif LOG_POLY_DEGREE == 5
p = POLY4(m, 2.8882704548164776201f, -2.52074962577807006663f, 1.48116647521213171641f, -0.465725644288844778798f, 0.0596515482674574969533f);
#elif LOG_POLY_DEGREE == 4
p = POLY3(m, 2.61761038894603480148f, -1.75647175389045657003f, 0.688243882994381274313f, -0.107254423828329604454f);
#elif LOG_POLY_DEGREE == 3
p = POLY2(m, 2.28330284476918490682f, -1.04913055217340124191f, 0.204446009836232697516f);
#else
#error
#endif
/* This effectively increases the polynomial degree by one, but ensures that log2(1) == 0*/
p = _mm_mul_ps(p, _mm_sub_ps(m, one));
return _mm_add_ps(p, e);
}
__attribute__((optimize("O3"))) static inline __m128i apply_gamma_epi32(__m128i z, __m128 gamma) {
__m128 zf = _mm_cvtepi32_ps(z);
zf = _mm_mul_ps(zf, _mm_set1_ps(1.f / 65535));
zf = log2f4(zf);
zf = _mm_mul_ps(zf, gamma);
zf = exp2f4(zf);
zf = _mm_mul_ps(zf, _mm_set1_ps(65535));
z = _mm_cvtps_epi32(zf);
/* Sometimes overflow causes the top 16 bits to be non-zero (e.g. when log(0) makes NaN) */
/* Those must be masked out */
z = _mm_and_si128(z, _mm_set1_epi32(0x0000FFFF));
return z;
}
__attribute__((optimize("O3"))) static inline __m128i apply_gamma_epi16(__m128i z, __m128 gamma) {
__m128i low = apply_gamma_epi32(_mm_unpacklo_epi16(z, _mm_setzero_si128()), gamma);
__m128i high = apply_gamma_epi32(_mm_unpackhi_epi16(z, _mm_setzero_si128()), gamma);
low = _mm_hadd_epi16(low, low);
low = _mm_and_si128(low, _mm_set_epi32(0, 0, 0xFFFFFFFF, 0xFFFFFFFF));
high = _mm_hadd_epi16(high, high);
high = _mm_slli_si128(high, 8);
return _mm_or_si128(low, high);
}

19
hi/loopback.c Normal file
View File

@ -0,0 +1,19 @@
#include<alsa/asoundlib.h>
CUTIVIS CHiPubNode *CHi_Loopback() {
snd_pcm_t *handle;
snd_pcm_hw_params_t *params;
printf("GOO %i\n", snd_pcm_open(&handle, "default", SND_PCM_STREAM_CAPTURE, 0));
printf("GOO %i\n", snd_pcm_hw_params_malloc(&params));
printf("GOO %i\n", snd_pcm_hw_params_any(handle, params));
printf("GOO %i\n", snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED));
printf("GOO %i\n", snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE));
printf("GOO %i\n", snd_pcm_hw_params_set_rate_near(handle, params, &(int) {48000}));
printf("GOO %i\n", snd_pcm_hw_params_set_channels(handle, params, 1));
printf("GOO %i\n", snd_pcm_hw_params(handle, aprams));
snd_pcm_hw_params_free(params);
printf("GOO %i\n", snd_pcm_prepare(handle));
}

209
hi/microphone.c Normal file
View File

@ -0,0 +1,209 @@
#include"node.h"
#include<portaudio.h>
#include"img.h"
#include<stdlib.h>
#include<stdio.h>
#include<math.h>
#include<stdatomic.h>
#include<string.h>
#include"microphone.h"
#define pabufsize (48000*5)
typedef struct CHiMicrophoneNode {
CHiPubNode pub;
PaStream *paStream;
int16_t paBuffer[pabufsize];
atomic_size_t paBufferWriteIdx;
atomic_size_t paBufferReadIdx;
} CHiMicrophoneNode;
static int pacallback(const void *input_, void *output, unsigned long samples, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags flags, void *ud) {
CHiMicrophoneNode *node = ud;
const int16_t *input = input_;
size_t writeidx = node->paBufferWriteIdx;
while(writeidx + samples >= pabufsize) {
memcpy(node->paBuffer + writeidx, input, (pabufsize - writeidx) * sizeof(*node->paBuffer));
samples -= pabufsize - writeidx;
input += pabufsize - writeidx;
writeidx = 0;
}
memcpy(node->paBuffer + writeidx, input, samples * sizeof(*node->paBuffer));
writeidx = (writeidx + samples) % pabufsize;
node->paBufferWriteIdx = writeidx;
/*static size_t total = 0;
for(size_t i = 0; i < pabufsize; i++) {
paBuffer[paBufferWriteIdx] = sin(total++ * 440.0 / 24000 * 3.141592653) * 0.1;
paBufferWriteIdx = (paBufferWriteIdx + 1) % pabufsize;
}*/
return paContinue;
}
static int microphone_start(CHiPubNode *pubn) {
CHiMicrophoneNode *node = (void*) pubn;
PaStreamParameters params = {
.device = pubn->sinks[0].data.vec4[0],
.channelCount = 1,
.sampleFormat = paInt16,
.suggestedLatency = Pa_GetDeviceInfo(pubn->sinks[0].data.vec4[0])->defaultLowInputLatency,
};
Pa_OpenStream(&node->paStream, &params, NULL, 48000, 0, paNoFlag, pacallback, pubn);
Pa_StartStream(node->paStream);
return 1;
}
static int microphone_stop(CHiPubNode *pubn) {
CHiMicrophoneNode *node = (void*) pubn;
Pa_StopStream(node->paStream);
Pa_CloseStream(node->paStream);
return 1;
}
static int microphone_perform(CHiPubNode *pubn) {
CHiMicrophoneNode *node = (void*) pubn;
if(pubn->sources[0].data.sample) {
CHi_Image_Free(pubn->sources[0].data.sample);
}
size_t width = roundf(CHi_Time_GetDelta(pubn->ng) * 48000.f);
do {
}while((node->paBufferWriteIdx - node->paBufferReadIdx + pabufsize) % pabufsize < width); // Wait until available
CHiImage *ret = CHi_Image_New(2, 1, 2 * width, width, 1, NULL);
if(node->paBufferReadIdx + width >= pabufsize) {
memcpy(ret->data16, node->paBuffer + node->paBufferReadIdx, sizeof(*node->paBuffer) * (pabufsize - node->paBufferReadIdx));
memcpy(ret->data16 + pabufsize - node->paBufferReadIdx, node->paBuffer, sizeof(*node->paBuffer) * (width - pabufsize + node->paBufferReadIdx));
node->paBufferReadIdx = node->paBufferReadIdx + width - pabufsize;
} else {
memcpy(ret->data16, node->paBuffer + node->paBufferReadIdx, sizeof(*node->paBuffer) * width);
node->paBufferReadIdx = (node->paBufferReadIdx + width) % pabufsize;
}
pubn->sources[0].type = CUTIHI_VAL_SAMPLE;
pubn->sources[0].data.sample = ret;
pubn->clean = 0;
return 1;
}
CUTIVIS CHiPubNode *CHi_Microphone() {
static int inited = 0;
if(!inited) {
Pa_Initialize();
inited = 1;
}
CHiPubNode *n = calloc(1, sizeof(CHiMicrophoneNode));
n->type = CUTIHI_T('CInA','udio');
n->Start = microphone_start;
n->Perform = microphone_perform;
n->Stop = microphone_stop;
n->clean = 0;
n->sinkCount = 1;
n->sinks = calloc(sizeof(*n->sinks), n->sinkCount);
n->sourceCount = 1;
n->sources = calloc(sizeof(*n->sources), n->sourceCount);
return n;
}
struct CHiExportWavNode {
CHiPubNode pubn;
FILE *output;
};
CUTIVIS int CHi_ExportWav_Start(CHiPubNode *pubn) {
struct CHiExportWavNode *n = (struct CHiExportWavNode*) pubn;
n->output = fopen(CHi_Crawl(&pubn->sinks[0])->data.text, "wb");
struct __attribute__((packed)) {
uint32_t ckID;
uint32_t ckSize;
uint32_t waveID;
} header = {.ckID = 'FFIR', .ckSize = 0, .waveID = 'EVAW'};
fwrite(&header, sizeof(header), 1, n->output);
struct __attribute__((packed)) {
uint32_t ckID;
uint32_t ckSize;
uint16_t wFormatTag;
uint16_t nChannels;
uint32_t nSamplesPerSec;
uint32_t nAvgBytesPerSec;
uint16_t nBlockAlign;
uint16_t wBitsPerSample;
} chunk0 = {.ckID = ' tmf', .ckSize = 16, .wFormatTag = 1 /* float */, .nChannels = 1, .nSamplesPerSec = 48000, .nAvgBytesPerSec = 48000 * 2, .nBlockAlign = 4, .wBitsPerSample = 16};
fwrite(&chunk0, sizeof(chunk0), 1, n->output);
struct __attribute__((packed)) {
uint32_t ckID;
uint32_t ckSize;
} chunk1 = {.ckID = 'atad', .ckSize = 0};
fwrite(&chunk1, sizeof(chunk1), 1, n->output);
return 1;
}
static int exportwav_perform(CHiPubNode *pubn) {
struct CHiExportWavNode *n = (struct CHiExportWavNode*) pubn;
CHiImage *buf = CHi_Crawl(&pubn->sinks[1])->data.sample;
fwrite(buf->data16, 2, buf->width, n->output);
pubn->clean = 0;
return 1;
}
CUTIVIS int CHi_ExportWav_Stop(CHiPubNode *pubn) {
struct CHiExportWavNode *n = (struct CHiExportWavNode*) pubn;
/* Fill size info in headers. */
uint32_t sz = ftell(n->output) - 8;
fseek(n->output, 4, SEEK_SET);
fwrite(&sz, sizeof(sz), 1, n->output);
sz -= 36;
fseek(n->output, 32, SEEK_CUR);
fwrite(&sz, sizeof(sz), 1, n->output);
fclose(n->output);
return 1;
}
CUTIVIS CHiPubNode *CHi_ExportWav() {
struct CHiExportWavNode *n = malloc(sizeof(*n));
n->pubn.type = CUTIHI_T('CExp','Wave');
n->pubn.Start = CHi_ExportWav_Start;
n->pubn.Perform = exportwav_perform;
n->pubn.Stop = CHi_ExportWav_Stop;
n->pubn.clean = 0;
n->pubn.sinkCount = 2;
n->pubn.sinks = calloc(sizeof(*n->pubn.sinks), n->pubn.sinkCount);
n->pubn.sourceCount = 0;
n->pubn.sources = NULL;
return &n->pubn;
}
size_t CHi_Microphone_GetSourceCount() {
return Pa_GetDeviceCount();
}
const char *CHi_Microphone_GetSourceName(size_t i) {
return Pa_GetDeviceInfo(i)->name;
}
size_t CHi_Microphone_GetNextSource(size_t i) {
i++;
while(i < CHi_Microphone_GetSourceCount()) {
if(Pa_GetDeviceInfo(i)->maxInputChannels > 0) break;
i++;
}
return i;
}

18
hi/microphone.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef _CUTIHI_MICROPHONE_H
#define _CUTIHI_MICROPHONE_H
#include<stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
CUTIVIS size_t CHi_Microphone_GetSourceCount();
CUTIVIS const char *CHi_Microphone_GetSourceName(size_t);
CUTIVIS size_t CHi_Microphone_GetNextSource(size_t);
#ifdef __cplusplus
}
#endif
#endif

11
hi/mode.c Normal file
View File

@ -0,0 +1,11 @@
#include"mode.h"
static CHiMode moed;
CUTIVIS void CHi_SetMode(CHiMode mode) {
moed = mode;
}
CUTIVIS CHiMode CHi_GetMode() {
return moed;
}

19
hi/mode.h Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include"defs.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
CUTIHI_MODE_LIVE,
CUTIHI_MODE_OFFLINE
} CHiMode;
CUTIVIS void CHi_SetMode(CHiMode mode);
CUTIVIS CHiMode CHi_GetMode();
#ifdef __cplusplus
}
#endif

899
hi/node.c Normal file
View File

@ -0,0 +1,899 @@
#include"node.h"
#include<stdlib.h>
#include"img.h"
#include<sail/sail.h>
#include<sail-manip/sail-manip.h>
#include<assert.h>
#include<string.h>
#include<tmmintrin.h>
#include<smmintrin.h>
#include<pango/pango.h>
#include<pango/pangoft2.h>
#include<freetype/ftbitmap.h>
#include"mode.h"
#include<math.h>
#include<sched.h>
#include<limits.h>
#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--;
}
}
CUTIVIS CHiNodeGraph *CHi_NewNodeGraph() {
CHiNodeGraph *ret = calloc(1, sizeof(*ret));
ret->count = 0;
ret->nodes = malloc(sizeof(*ret->nodes) * (ret->capacity = 8));
ret->eventOnStopComplete = NULL;
ret->eventOnFrameComplete = NULL;
ret->compilationStatus = CUTIHI_COMP_READY;
ret->adjacencyCount = 0;
ret->adjacencyCapacity = 8;
ret->adjacencies = malloc(sizeof(CHiAdjacency) * ret->adjacencyCapacity);
return ret;
}
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];
} else if(v->type == CUTIHI_VAL_KEYED) {
v = &v->data.keyed->current;
}
}
return v;
}
CUTIVIS void CHi_RegisterNode(CHiNodeGraph* ng, CHiPubNode* n) {
if(ng->count == ng->capacity) {
ng->nodes = realloc(ng->nodes, sizeof(*ng->nodes) * (ng->capacity = ng->capacity * 3 / 2));
}
ng->nodes[ng->count++] = n;
n->ng = ng;
}
CUTIVIS void CHi_MakeDirty(CHiNodeGraph *ng, CHiPubNode *n) {
for(size_t i = 0; i < ng->count; i++) {
}
}
static int dfs_visit(size_t *resultCount, CHiPubNode ***result, CHiPubNode *n) {
if(n->_dfsmark == 2) return 1;
else if(n->_dfsmark == 1) return 0;
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)) {
return 0;
}
}
}
n->_dfsmark++;
(*result)[(*resultCount)++] = n;
return 1;
}
static int topological_sort(CHiNodeGraph *ng) {
size_t resultCount = 0;
CHiPubNode **result = malloc(sizeof(*result) * ng->capacity);
for(size_t i = 0; i < ng->count; i++) {
ng->nodes[i]->_dfsmark = 0;
}
for(size_t i = 0; i < ng->count; i++) {
if(!dfs_visit(&resultCount, &result, ng->nodes[i])) {
free(result);
return 0;
}
}
assert(resultCount == ng->count);
free(ng->nodes);
ng->nodes = result;
return 1;
}
CUTIVIS int CHi_ConfigureSink(CHiPubNode *n, size_t i, CHiValue v) {
if(n->sinks[i].type == CUTIHI_VAL_KEYED) {
n->sinks[i].data.keyed->current = v;
return 1;
}
if(v.type == CUTIHI_VAL_LINKED && n == v.data.linked.to) return 0;
CHiValue old = n->sinks[i];
if(old.type == CUTIHI_VAL_LINKED) {
adjacency_remove(old.data.linked.to, n);
}
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);
}
return 0;
}
if(v.type == CUTIHI_VAL_LINKED) {
adjacency_add(v.data.linked.to, n);
}
return 1;
}
CUTIVIS void CHi_MakeKeyframe(CHiNodeGraph *ng, CHiPubNode *n, size_t i) {
if(n->sinks[i].type != CUTIHI_VAL_KEYED) {
CHiKeyframes *kfs = calloc(1, sizeof(*kfs));
kfs->type = n->sinks[i].type;
kfs->count = 1;
kfs->times = malloc(sizeof(*kfs->times));
*kfs->times = ng->time;
kfs->values = malloc(sizeof(*kfs->values));
memcpy(kfs->values, &n->sinks[i].data, sizeof(CHiValueRaw));
memcpy(&kfs->current, &n->sinks[i], sizeof(CHiValueRaw));
kfs->node = n;
n->sinks[i].type = CUTIHI_VAL_KEYED;
n->sinks[i].data.keyed = kfs;
ng->keyframesList.keyframes = realloc(ng->keyframesList.keyframes, sizeof(*ng->keyframesList.keyframes) * (++ng->keyframesList.count));
ng->keyframesList.keyframes[ng->keyframesList.count - 1] = kfs;
} else {
CHiKeyframes *kfs = n->sinks[i].data.keyed;
float now = ng->time;
size_t idx = bisect(&now, kfs->times, kfs->count, sizeof(now), float_compar);
if(idx < kfs->count && kfs->times[idx] == now) {
kfs->values[idx] = kfs->current.data;
} else {
kfs->count++;
kfs->values = realloc(kfs->values, sizeof(*kfs->values) * kfs->count);
kfs->times = realloc(kfs->times, sizeof(*kfs->times) * kfs->count);
memmove(kfs->values + idx + 1, kfs->values + idx, sizeof(*kfs->values) * (kfs->count - idx - 1));
memmove(kfs->times + idx + 1, kfs->times + idx, sizeof(*kfs->times) * (kfs->count - idx - 1));
kfs->values[idx] = kfs->current.data;
kfs->times[idx] = now;
}
}
}
CUTIVIS size_t CHi_MoveKeyframe(CHiNodeGraph *ng, CHiKeyframes *kfs, size_t idx, float to) {
CHiValueRaw val = kfs->values[idx];
while(idx < kfs->count - 1 && to > kfs->times[idx + 1]) {
memcpy(&kfs->values[idx], &kfs->values[idx + 1], sizeof(*kfs->values));
memcpy(&kfs->times[idx], &kfs->times[idx + 1], sizeof(*kfs->times));
idx++;
}
while(idx > 0 && to < kfs->times[idx - 1]) {
memcpy(&kfs->values[idx], &kfs->values[idx - 1], sizeof(*kfs->values));
memcpy(&kfs->times[idx], &kfs->times[idx - 1], sizeof(*kfs->times));
idx--;
}
kfs->times[idx] = to;
kfs->values[idx] = val;
return idx;
}
CUTIVIS size_t CHi_MoveKeyframeBy(CHiNodeGraph *ng, CHiKeyframes *kfs, size_t idx, float dt) {
return CHi_MoveKeyframe(ng, kfs, idx, kfs->times[idx] + dt);
}
CUTIVIS void CHi_DeleteKeyframe(CHiNodeGraph *ng, CHiKeyframes *kfs, size_t idx) {
memmove(&kfs->times[idx], &kfs->times[idx + 1], (kfs->count - idx - 1) * sizeof(*kfs->times));
memmove(&kfs->values[idx], &kfs->values[idx + 1], (kfs->count - idx - 1) * sizeof(*kfs->values));
kfs->count--;
}
CUTIVIS size_t CHi_GetClosestKeyframe(CHiNodeGraph *ng, size_t kfsIdx, float t) {
CHiKeyframes *kfs = ng->keyframesList.keyframes[kfsIdx];
if(kfs->count == 1) {
return 0;
}
size_t idx = bisect(&t, kfs->times, kfs->count, sizeof(*kfs->times), float_compar);
if(idx == 0) {
return idx;
}
if(idx == kfs->count) {
return kfs->count - 1;
}
if(fabs(kfs->times[idx] - t) < fabs(kfs->times[idx - 1] - t)) {
return idx;
} else {
return idx - 1;
}
}
CUTIVIS void CHi_SetExtrapolationMode(CHiNodeGraph *ng, CHiPubNode *n, size_t sinkIdx, CHiExtrapolationMode mode, float* params) {
if(n->sinks[sinkIdx].type != CUTIHI_VAL_KEYED) {
return;
}
CHiKeyframes *kfs = n->sinks[sinkIdx].data.keyed;
kfs->extrapolationMode = mode;
memcpy(kfs->extrapolationParameter, params, sizeof(kfs->extrapolationParameter));
}
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);
}
}
root->Perform(root);
return 1;
}
bool timespec_less(const struct timespec l, const struct timespec r) {
if(l.tv_sec == r.tv_sec) {
return l.tv_nsec < r.tv_nsec;
} else {
return l.tv_sec < r.tv_sec;
}
}
struct timespec timespec_sub(const struct timespec l, const struct timespec r) {
struct timespec ret;
ret.tv_sec = l.tv_sec - r.tv_sec;
ret.tv_nsec = l.tv_nsec - r.tv_nsec;
if(ret.tv_nsec < 0) {
ret.tv_nsec += 1000000000L;
ret.tv_sec--;
}
return ret;
}
struct timespec timespec_addf(const struct timespec l, const float r) {
struct timespec ret;
ret.tv_sec = l.tv_sec + floorf(r);
ret.tv_nsec = l.tv_nsec + (r - floorf(r)) * 1000000000L;
if(ret.tv_nsec > 1000000000L) {
ret.tv_sec++;
ret.tv_nsec -= 1000000000L;
}
return ret;
}
struct timespec timespec_add(const struct timespec l, const struct timespec r) {
struct timespec ret;
ret.tv_sec = l.tv_sec + r.tv_sec;
ret.tv_nsec = l.tv_nsec + r.tv_nsec;
if(ret.tv_nsec > 1000000000L) {
ret.tv_nsec -= 1000000000L;
ret.tv_sec++;
}
return ret;
}
float timespecToFloat(const struct timespec t) {
return t.tv_sec + t.tv_nsec / 1000000000.f;
}
struct CompileCtx {
CHiNodeGraph *ng;
};
void *compile_thread(void *ctx_) {
struct CompileCtx *ctx = ctx_;
ctx->ng->time = ctx->ng->timedelta = 0;
puts("START");
for(size_t nIdx = 0; nIdx < ctx->ng->count; nIdx++) {
if(ctx->ng->nodes[nIdx]->Start) {
ctx->ng->nodes[nIdx]->Start(ctx->ng->nodes[nIdx]);
} else {
ctx->ng->nodes[nIdx]->Perform(ctx->ng->nodes[nIdx]);
}
}
if(CHi_GetMode() == CUTIHI_MODE_LIVE) {
struct timespec start;
clock_gettime(CLOCK_MONOTONIC, &start);
struct timespec finish = timespec_addf(start, ctx->ng->duration);
for(size_t frm = 0; ctx->ng->compilationStatus != CUTIHI_COMP_KILL_YOURSELF; frm++) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
if(ctx->ng->duration != -1 && timespec_less(finish, now)) {
break;
}
struct timespec end = timespec_addf(now, 0.033333333333333333333333);
CHi_Time_Set(ctx->ng, timespecToFloat(timespec_sub(now, start)));
for(size_t nIdx = 0; nIdx < ctx->ng->count; nIdx++) {
ctx->ng->nodes[nIdx]->Perform(ctx->ng->nodes[nIdx]);
}
if(ctx->ng->eventOnFrameComplete) {
ctx->ng->eventOnFrameComplete(ctx->ng);
}
do {
clock_gettime(CLOCK_MONOTONIC, &now);
} while(timespec_less(now, end));
}
} else {
__uint128_t diff;
for(uint64_t frm = 0; ctx->ng->compilationStatus != CUTIHI_COMP_KILL_YOURSELF && (ctx->ng->duration == -1 || frm < ctx->ng->duration * 30);) {
CHi_Time_Set(ctx->ng, frm / 30.f);
for(size_t nIdx = 0; nIdx < ctx->ng->count; nIdx++) {
ctx->ng->nodes[nIdx]->Perform(ctx->ng->nodes[nIdx]);
}
struct timespec last;
clock_gettime(CLOCK_MONOTONIC, &last);
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
diff += timespec_sub(now, last).tv_nsec;
if(ctx->ng->eventOnFrameComplete) {
ctx->ng->eventOnFrameComplete(ctx->ng);
}
frm++;
}
}
for(size_t nIdx = 0; nIdx < ctx->ng->count; nIdx++) {
if(ctx->ng->nodes[nIdx]->Stop) {
ctx->ng->nodes[nIdx]->Stop(ctx->ng->nodes[nIdx]);
}
}
puts("END");
if(ctx->ng->eventOnStopComplete) {
ctx->ng->eventOnStopComplete(ctx->ng);
}
ctx->ng->compilationStatus = CUTIHI_COMP_READY;
free(ctx);
return NULL;
}
CUTIVIS void CHi_BeginCompilation(CHiNodeGraph *ng) {
ng->compilationStatus = CUTIHI_COMP_RUNNING;
struct CompileCtx *ctx = calloc(sizeof(*ctx), 1);
ctx->ng = ng;
pthread_t thrd;
pthread_create(&thrd, NULL, &compile_thread, ctx);
}
CUTIVIS void CHi_StopCompilation(CHiNodeGraph *ng) {
if(ng->compilationStatus == CUTIHI_COMP_RUNNING) {
ng->compilationStatus = CUTIHI_COMP_KILL_YOURSELF;
}
}
static int image_perform(CHiPubNode *node) {
if(node->clean) return 1;
node->sources->type = CUTIHI_VAL_SAMPLE;
if(node->sources->data.sample) CHi_Image_Free(node->sources->data.sample);
struct sail_image *simg;
SAIL_TRY(sail_load_from_file(node->sinks[0].data.text, &simg));
struct sail_image *cimg;
sail_convert_image(simg, SAIL_PIXEL_FORMAT_BPP64_BGRA, &cimg);
sail_destroy_image(simg);
CHiImage *img = CHi_Image_New(2, 4, (cimg->bytes_per_line + 15) & ~15, cimg->width, cimg->height, NULL);
CHi_Restride(cimg->pixels, img->data16, cimg->bytes_per_line, img->stride, img->height);
node->sources->data.sample = img;
for(size_t y = 0; y < img->height; y++) {
for(size_t x = 0; x < img->stride; x += 16) {
__m128i pixels = _mm_load_si128((__m128i*) ((uintptr_t) img->data16 + y * img->stride + x));
pixels = apply_gamma_epi16(pixels, _mm_set_ps(1.0f, 2.2f, 2.2f, 2.2f));
_mm_stream_si128((__m128i*) ((uintptr_t) img->data16 + y * img->stride + x), pixels);
}
}
sail_destroy_image(cimg);
node->clean = 0;
return 1;
err:
node->sources->data.sample = NULL;
return 0;
}
CUTIVIS CHiPubNode *CHi_Image() {
CHiPubNode *n = malloc(sizeof(*n));
n->type = CUTIHI_T('CIma','ge ');
n->Start = n->Stop = NULL;
n->Perform = image_perform;
n->clean = 0;
n->sinkCount = 1;
n->sinks = calloc(sizeof(*n->sinks), 1);
n->sourceCount = 1;
n->sources = calloc(sizeof(*n->sources), 1);
return n;
}
static int embed_perform(CHiPubNode *node) {
if(node->clean) return 1;
node->sources[0].type = CUTIHI_VAL_SAMPLE;
CHiImage *main = CHi_Crawl(&node->sinks[0])->data.sample;
if(node->sources->data.sample) CHi_Image_Free(node->sources->data.sample);
CHiImage *dest = node->sources->data.sample = CHi_Image_New(2, 4, main->stride, main->width, main->height, NULL);
memcpy(dest->data16, main->data16, main->stride * main->height);
for(int sid = 0; sid < CUTIHI_EMBED_MAX_SMALLS; sid++) {
CHiImage *sub = CHi_Crawl(&node->sinks[1 + sid * 3])->data.sample;
if(!sub) continue;
int sy = 0;
int dy = (int16_t) CHi_Crawl(&node->sinks[2 + sid * 3])->data.vec4[1];
if(dy < 0) {
sy = -dy;
dy = 0;
}
for(; sy < sub->height && dy < dest->height; sy++, dy++) {
int sx = 0;
int dx = (int16_t) CHi_Crawl(&node->sinks[2 + sid * 3])->data.vec4[0];
if(dx < 0) {
sx = -dx;
dx = 0;
}
for(; sx < sub->width && dx < dest->width; sx += 2, dx += 2) {
__m128i bottom = _mm_loadu_si128((__m128i*) ((uintptr_t) dest->data16 + dy * dest->stride + dx * 8));
__m128i top = _mm_loadu_si128((__m128i*) ((uintptr_t) sub->data16 + sy * sub->stride + sx * 8));
__m128i alpha = _mm_shuffle_epi8(top, _mm_set_epi8(15, 14, 15, 14, 15, 14, 15, 14, 7, 6, 7, 6, 7, 6, 7, 6));
__m128i invAlpha = _mm_sub_epi16(_mm_set1_epi16(0xFFFF), alpha);
__m128i result = _mm_add_epi16(_mm_mulhi_epu16(top, alpha), _mm_mulhi_epu16(bottom, invAlpha));
_mm_storeu_si128((__m128i*) ((uintptr_t) dest->data16 + dy * dest->stride + dx * 8), result);
}
}
}
node->clean = 0;
return 1;
}
CUTIVIS CHiPubNode *CHi_Embed() {
CHiPubNode *n = malloc(sizeof(*n));
n->type = CUTIHI_T('CEmb','ed ');
n->Start = n->Stop = NULL;
n->Perform = embed_perform;
n->clean = 0;
n->sinks = calloc(sizeof(*n->sinks), n->sinkCount = 1 + 3 * CUTIHI_EMBED_MAX_SMALLS);
for(int i = 0; i < CUTIHI_EMBED_MAX_SMALLS; i++) {
n->sinks[2 + i * 3].type = CUTIHI_VAL_VEC4;
n->sinks[2 + i * 3].data.vec4[0] = 0;
n->sinks[2 + i * 3].data.vec4[1] = 0;
n->sinks[3 + i * 3].type = CUTIHI_VAL_VEC4;
n->sinks[3 + i * 3].data.vec4[0] = 1;
}
n->sources = calloc(sizeof(*n->sources), n->sourceCount = 1);
return n;
}
static int constantsample_perform(CHiPubNode *node) {
if(node->clean) return 1;
node->sources[0].type = CUTIHI_VAL_SAMPLE;
if(node->sources->data.sample) CHi_Image_Free(node->sources->data.sample);
CHiValue *sink = CHi_Crawl(&node->sinks[0]);
CHiImage *img = CHi_Image_New(2, 4, 8 * 16, 16, 16, NULL);
for(int i = 0; i < 256; i++) {
img->data16[i * 4 + 0] = sink->data.vec4[2] * 65535;
img->data16[i * 4 + 1] = sink->data.vec4[1] * 65535;
img->data16[i * 4 + 2] = sink->data.vec4[0] * 65535;
img->data16[i * 4 + 3] = 65535;
}
node->sources->data.sample = img;
node->clean = 0;
return 1;
}
CUTIVIS CHiPubNode *CHi_ConstantSample() {
CHiPubNode *n = malloc(sizeof(*n));
n->type = CUTIHI_T('CCns','tCol');
n->Start = n->Stop = NULL;
n->Perform = constantsample_perform;
n->clean = 0;
n->sinkCount = 1;
n->sinks = calloc(sizeof(*n->sinks), 1);
n->sourceCount = 1;
n->sources = calloc(sizeof(*n->sources), 1);
return n;
}
static int modulate_perform(CHiPubNode *node) {
if(node->clean) return 1;
node->sources[0].type = CUTIHI_VAL_SAMPLE;
if(node->sources->data.sample) CHi_Image_Free(node->sources->data.sample);
node->sources->data.sample = CHi_Image_New(2, 4, 8 * 16, 16, 16, NULL);
node->clean = 0;
return 1;
}
CUTIVIS CHiPubNode *CHi_Modulate() {
CHiPubNode *n = malloc(sizeof(*n));
n->type = CUTIHI_T('CMod','ulat');
n->Start = n->Stop = NULL;
n->Perform = modulate_perform;
n->clean = 0;
n->sinkCount = 4;
n->sinks = calloc(sizeof(*n->sinks), n->sinkCount);
n->sourceCount = 1;
n->sources = calloc(sizeof(*n->sources), n->sourceCount);
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;
node->clean = 0;
return 1;
}
CUTIVIS void CHi_Time_Set(CHiNodeGraph *ng, float f) {
ng->timedelta = f - ng->time;
ng->time = f;
update_keyed_values(ng);
}
CUTIVIS float CHi_Time_Get(CHiNodeGraph *ng) {
return ng->time;
}
CUTIVIS float CHi_Time_GetDelta(CHiNodeGraph *ng) {
return ng->timedelta;
}
CUTIVIS CHiPubNode *CHi_Time() {
CHiPubNode *n = malloc(sizeof(*n));
n->type = CUTIHI_T('CTim','e ');
n->Start = n->Stop = NULL;
n->Perform = time_perform;
n->clean = 0;
n->sinkCount = 0;
n->sinks = NULL;
n->sourceCount = 1;
n->sources = calloc(sizeof(*n->sources), 1);
return n;
}
static PangoFontMap *pfontmap;
static PangoContext *pcontext;
static PangoFontDescription * pfontdesc;
static PangoLayout *playout;
static int text_perform(CHiPubNode *n) {
if(n->clean) return 1;
if(!pfontmap) {
pfontmap = pango_ft2_font_map_new();
pango_ft2_font_map_set_resolution(PANGO_FT2_FONT_MAP(pfontmap), 72, 72);
pcontext = pango_font_map_create_context(pfontmap);
pango_context_set_language(pcontext, pango_language_from_string("en_US"));
pango_context_set_base_dir(pcontext, PANGO_DIRECTION_LTR);
pfontdesc = pango_font_description_from_string("Open Sans 48");
playout = pango_layout_new(pcontext);
pango_layout_set_font_description(playout, pfontdesc);
}
pango_layout_set_markup(playout, CHi_Crawl(&n->sinks[0])->data.text, -1);
pango_ft2_font_map_set_resolution(PANGO_FT2_FONT_MAP(pfontmap), CHi_Crawl(&n->sinks[2])->data.vec4[0], CHi_Crawl(&n->sinks[2])->data.vec4[0]);
PangoRectangle extents;
pango_layout_get_extents(playout, NULL, &extents);
n->sources[0].type = CUTIHI_VAL_SAMPLE;
if(n->sources->data.sample) CHi_Image_Free(n->sources->data.sample);
size_t width = (PANGO_PIXELS(extents.width) + 15) & ~15;
CHiImage *chiret = CHi_Image_New(2, 4, 8 * width, width, PANGO_PIXELS(extents.height), NULL);
n->sources->data.sample = chiret;
FT_Bitmap bmp = {};
FT_Bitmap_New(&bmp);
bmp.width = chiret->width;
bmp.rows = chiret->height;
bmp.buffer = calloc(bmp.width, bmp.rows);
bmp.pitch = chiret->width;
bmp.pixel_mode = FT_PIXEL_MODE_GRAY;
bmp.num_grays = 256;
pango_ft2_render_layout(&bmp, playout, PANGO_PIXELS(extents.x) + (PANGO_PIXELS(extents.width) + 15) % 16 / 4, PANGO_PIXELS(extents.y));
__m128i ones = _mm_set1_epi64x(
(((size_t) (n->sinks[1].data.vec4[2] * 255) % 256) << 0) |
(((size_t) (n->sinks[1].data.vec4[1] * 255) % 256) << 16) |
(((size_t) (n->sinks[1].data.vec4[0] * 255) % 256) << 32) |
0x0100000000000000
);
for(size_t p = 0; p < bmp.width * bmp.rows; p += 2) {
__m128i alphad0 =
_mm_mullo_epi16(ones, _mm_set_epi16(bmp.buffer[p + 1], bmp.buffer[p + 1], bmp.buffer[p + 1], bmp.buffer[p + 1], bmp.buffer[p + 0], bmp.buffer[p + 0], bmp.buffer[p + 0], bmp.buffer[p + 0]));
_mm_stream_si128((__m128i*) &chiret->data16[p * 4], alphad0);
}
free(bmp.buffer);
n->clean = 0;
return 1;
}
CUTIVIS CHiPubNode *CHi_Text() {
CHiPubNode *n = malloc(sizeof(*n));
n->type = CUTIHI_T('CTex','t ');
n->Start = n->Stop = NULL;
n->Perform = text_perform;
n->clean = 0;
n->sinks = calloc(sizeof(*n->sinks), n->sinkCount = 3);
n->sinks[2].type = CUTIHI_VAL_VEC4;
n->sinks[2].data.vec4[0] = 72;
n->sources = calloc(sizeof(*n->sources), n->sourceCount = 1);
return n;
}
static int mixer_perform(CHiPubNode *n) {
n->sources[0].type = CUTIHI_VAL_SAMPLE;
if(n->sources[0].data.sample) {
CHi_Image_Free(n->sources[0].data.sample);
n->sources[0].data.sample = NULL;
}
CHiImage *src0 = CHi_Crawl(&n->sinks[0])->data.sample;
CHiImage *src1 = CHi_Crawl(&n->sinks[1])->data.sample;
if(!src0 && !src1) {
return 1;
}
assert(src0->width == src1->width && src0->height == src1->height);
n->sources[0].data.sample = CHi_Image_New(2, 1, (src0->stride + 15) & ~15, src0->width, src0->height, NULL);
for(size_t b = 0; b < src0->stride; b += 16) {
__m128i a0 = src0 ? _mm_load_si128((__m128i*) ((uintptr_t) src0->data16 + b)) : _mm_setzero_si128();
__m128i a1 = src1 ? _mm_load_si128((__m128i*) ((uintptr_t) src1->data16 + b)) : _mm_setzero_si128();
_mm_stream_si128((__m128i*) ((uintptr_t) n->sources[0].data.sample->data16 + b), _mm_adds_epi16(a0, a1));
}
n->clean = 0;
return 1;
}
CUTIVIS CHiPubNode *CHi_Mixer() {
CHiPubNode *n = calloc(1, sizeof(*n));
n->type = CUTIHI_T('CMix','er ');
n->Start = n->Stop = NULL;
n->Perform = mixer_perform;
n->clean = 0;
n->sinks = calloc(sizeof(*n->sinks), n->sinkCount = 2);
n->sources = calloc(sizeof(*n->sources), n->sourceCount = 1);
return n;
}
static int preview_perform(CHiPubNode *n) {
return 1;
}
CUTIVIS CHiPubNode *CHi_Preview() {
CHiPubNode *n = malloc(sizeof(*n));
n->type = CUTIHI_T('CPre','view');
n->Start = n->Stop = NULL;
n->Perform = preview_perform;
n->clean = 0;
n->sinks = calloc(sizeof(*n->sinks), n->sinkCount = 1);
n->sinks[0].type = CUTIHI_VAL_SAMPLE;
n->sinks[0].data.sample = NULL;
n->sources = NULL;
n->sourceCount = 0;
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));
} 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));
}
}
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->count}, sizeof(uint64_t));
for(size_t i = 0; i < ng->count; i++) {
CHiPubNode *node = ng->nodes[i];
writer(ud, &(uint16_t) {node->type}, sizeof(uint16_t));
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);
}
}
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));
}
return 0;
}

268
hi/node.h Normal file
View File

@ -0,0 +1,268 @@
#ifndef _NODE_H
#define _NODE_H
#include<stdint.h>
#include<stddef.h>
#include"defs.h"
#include"bs.h"
#include<arpa/inet.h>
#ifdef __cplusplus
extern "C" {
#endif
#define CUTIHI_T(a, b) ((((uint64_t) htonl(b)) << 32) | htonl(a))
typedef size_t(*CHiSaveWriter)(void *ud, const void *data, size_t len);
typedef enum {
CUTIHI_VAL_NONE = 0,
CUTIHI_VAL_LINKED = 1,
CUTIHI_VAL_KEYED = 2,
CUTIHI_VAL_SAMPLE = 3,
CUTIHI_VAL_TEXT = 4,
CUTIHI_VAL_VEC4 = 5,
CUTIHI_VAL_WEAK_PTR = 6,
CUTIHI_VAL_VP9BS = 666,
CUTIHI_VAL_VP8BS = 667,
CUTIHI_VAL_OPUSBS = 668,
} CHiValType;
struct CHiImage;
struct CHiPubNode;
struct CHiKeyframes;
typedef union {
struct {
struct CHiPubNode *to;
uint8_t idx;
} linked;
struct CHiImage *sample;
char *text;
float vec4[4];
CHiBSFrames *bitstream;
struct CHiKeyframes* keyed;
uintptr_t weakptr;
} CHiValueRaw;
typedef struct {
CHiValType type;
CHiValueRaw data;
} CHiValue;
typedef struct CHiPubNode {
uint64_t type;
uint32_t flags;
#define CUTIHI_PERFORM_UPDATED 1
#define CUTIHI_PERFORM_NOTUPDATED 0
int (*Perform)(struct CHiPubNode *node);
int (*Start)(struct CHiPubNode *node);
int (*Stop)(struct CHiPubNode *node);
char clean;
int (*Save)(struct CHiPubNode *node, void *ud, CHiSaveWriter writer);
size_t sinkCount;
CHiValue *sinks;
size_t sourceCount;
CHiValue *sources;
struct CHiNodeGraph *ng;
char _dfsmark;
} CHiPubNode;
typedef enum {
CUTIHI_COMP_READY,
CUTIHI_COMP_RUNNING,
CUTIHI_COMP_KILL_YOURSELF
} CHiCompilationStatus;
typedef CHiPubNode *CHiAdjacency[2];
typedef enum {
CUTIHI_EXTRAPOLATION_NONE = 0, CUTIHI_EXTRAPOLATION_CONSTANT = 1
} CHiExtrapolationMode;
typedef struct CHiKeyframes {
CHiValType type;
size_t count;
float *times;
CHiValueRaw *values;
CHiValue current;
CHiPubNode *node;
CHiExtrapolationMode extrapolationMode;
float extrapolationParameter[4];
} CHiKeyframes;
typedef struct CHiKeyframesList {
size_t count;
CHiKeyframes **keyframes;
} CHiKeyframesList;
typedef struct CHiNodeGraph {
size_t capacity;
size_t count;
CHiPubNode **nodes;
float duration;
void *ud;
void(*eventOnStopComplete)(struct CHiNodeGraph*);
void(*eventOnFrameComplete)(struct CHiNodeGraph*);
CHiCompilationStatus compilationStatus;
size_t adjacencyCount, adjacencyCapacity;
CHiAdjacency *adjacencies;
CHiKeyframesList keyframesList;
float time;
float timedelta;
} CHiNodeGraph;
CUTIVIS CHiNodeGraph *CHi_NewNodeGraph();
CUTIVIS int CHi_NodeGraphSave(CHiNodeGraph *ng, CHiSaveWriter writer, void *ud);
CUTIVIS void CHi_RegisterNode(CHiNodeGraph*, CHiPubNode*);
CUTIVIS void CHi_MakeDirty(CHiNodeGraph*, CHiPubNode*);
CUTIVIS int CHi_ConfigureSink(CHiPubNode*, size_t, CHiValue);
CUTIVIS void CHi_MakeKeyframe(CHiNodeGraph *ng, CHiPubNode *n, size_t idx);
CUTIVIS size_t CHi_MoveKeyframe(CHiNodeGraph *ng, CHiKeyframes *kfs, size_t idx, float to);
CUTIVIS size_t CHi_MoveKeyframeBy(CHiNodeGraph *ng, CHiKeyframes *kfs, size_t idx, float dt);
CUTIVIS size_t CHi_GetClosestKeyframe(CHiNodeGraph *ng, size_t kfsIdx, float t);
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 void CHi_SetDuration(CHiNodeGraph *ng, float);
CUTIVIS void CHi_BeginCompilation(CHiNodeGraph *ng);
CUTIVIS void CHi_StopCompilation(CHiNodeGraph *ng);
#define CUTIHI_NODE_IMAGE 0
#define CUTIHI_IMAGE_IN_FILE 0
#define CUTIHI_IMAGE_OUT_SAMPLE 1
CUTIVIS CHiPubNode *CHi_Image();
#define CUTIHI_NODE_EMBED 1
#define CUTIHI_EMBED_OUT_MAIN 0
#define CUTIHI_EMBED_MAX_SMALLS 3
CUTIVIS CHiPubNode *CHi_Embed();
#define CUTIHI_NODE_CONSTANTSAMPLE 2
#define CUTIHI_CONSTANTSAMPLE_IN_COLOR 0
#define CUTIHI_CONSTANTSAMPLE_OUT_SAMPLE 1
CUTIVIS CHiPubNode *CHi_ConstantSample();
#define CUTIHI_NODE_MODULATE 3
#define CUTIHI_MODULATE_IN_SAMPLE 0
#define CUTIHI_MODULATE_BRIGHTNESS 1
#define CUTIHI_MODULATE_CONTAST 2
#define CUTIHI_MODULATE_HUE 3
#define CUTIHI_MODULATE_OUT_SAMPLE 4
CUTIVIS CHiPubNode *CHi_Modulate();
#define CUTIHI_NODE_MOVIE 4
#define CUTIHI_MOVIE_IN_FILE 0
#define CUTIHI_MOVIE_IN_TIME 1
#define CUTIHI_MOVIE_IN_DELAY 2
#define CUTIHI_MOVIE_OUT_SAMPLE 2
CUTIVIS CHiPubNode *CHi_Movie();
#define CUTIHI_NODE_TIME 5
#define CUTIHI_TIME_OUT 0
CUTIVIS void CHi_Time_Set(CHiNodeGraph *ng, float);
CUTIVIS float CHi_Time_Get(CHiNodeGraph *ng);
CUTIVIS float CHi_Time_GetDelta(CHiNodeGraph *ng);
CUTIVIS CHiPubNode *CHi_Time();
#define CUTIHI_NODE_ENCODEVP9 6
#define CUTIHI_ENCODEVP9_IN_SAMPLE 0
#define CUTIHI_ENCODEVP9_IN_BITRATE 1
#define CUTIHI_ENCODEVP9_IN_BITRATE 1
#define CUTIHI_ENCODEVP9_OUT_BITSTREAM 0
#define CUTIHI_NODE_ENCODEVP8 17
CUTIVIS CHiPubNode *CHi_EncodeVP9();
CUTIVIS CHiPubNode *CHi_EncodeVP8();
CUTIVIS int CHi_EncodeVP9_Start(CHiPubNode*);
CUTIVIS int CHi_EncodeVP9_Stop(CHiPubNode*);
#define CUTIHI_NODE_MUXWEBM 7
#define CUTIHI_MUXWEBM_IN_BITSTREAM 0
#define CUTIHI_MUXWEBM_IN_AUDIOBITSTREAM 1
#define CUTIHI_MUXWEBM_IN_FILENAME 2
CUTIVIS CHiPubNode *CHi_MuxWebm();
CUTIVIS int CHi_MuxWebm_Start(CHiPubNode*);
CUTIVIS int CHi_MuxWebm_Stop(CHiPubNode*);
#define CUTIHI_NODE_WINDOW 8
#define CUTIHI_WINDOW_IN_WINDOWNAME 0
#define CUTIHI_WINDOW_OUT_SAMPLE 1
CUTIVIS CHiPubNode *CHi_Window();
CUTIVIS size_t CHi_Window_GetSourceCount();
CUTIVIS const char *CHi_Window_GetSourceName(size_t);
CUTIVIS uintptr_t CHi_Window_GetSourceData(size_t);
CUTIVIS size_t CHi_Window_GetNextSource(size_t);
#define CUTIHI_NODE_MICROPHONE 9
#define CUTIHI_MICROPHONE_IN_NAME 0
#define CUTIHI_MICROPHONE_OUT_AUDIO 1
CUTIVIS CHiPubNode *CHi_Microphone();
#define CUTIHI_NODE_TEXT 10
#define CUTIHI_TEXT_IN_TEXT 0
#define CUTIHI_TEXT_OUT_SAMPLE 0
CUTIVIS CHiPubNode *CHi_Text();
#define CUTIHI_NODE_EXPORTWAV 11
CUTIVIS CHiPubNode *CHi_ExportWav();
CUTIVIS int CHi_ExportWav_Start(CHiPubNode*);
CUTIVIS int CHi_ExportWav_Stop(CHiPubNode*);
#define CUTIHI_NODE_ENCODEOPUS 12
CUTIVIS CHiPubNode *CHi_EncodeOpus();
CUTIVIS int CHi_EncodeOpus_Start(CHiPubNode*);
CUTIVIS int CHi_EncodeOpus_Stop(CHiPubNode*);
#define CUTIHI_NODE_CAMERA 13
CUTIVIS CHiPubNode *CHi_Camera();
#define CUTIHI_NODE_PREVIEW 14
CUTIVIS CHiPubNode *CHi_Preview();
#define CUTIHI_NODE_COMPONENT_SCALE 15
CUTIVIS CHiPubNode *CHi_ComponentScale();
#define CUTIHI_NODE_KEYHOOK 16
CUTIVIS CHiPubNode *CHi_Keyhook();
#define CUTIHI_NODE_MIXER 18
CUTIVIS CHiPubNode *CHi_Mixer();
CUTIVIS CHiValue *CHi_Crawl(CHiValue*);
CUTIVIS void CHi_Save(CHiNodeGraph *ng);
#ifdef __cplusplus
}
#endif
#endif

85
hi/opus.c Normal file
View File

@ -0,0 +1,85 @@
#include"node.h"
#include<opus.h>
#include<string.h>
#include"img.h"
#include<stdlib.h>
#include<stdio.h>
struct CHiEncodeOpusNode {
CHiPubNode pubn;
size_t pcmSamples, pcmCapacity;
int16_t *pcmbuf;
size_t timestamp;
OpusEncoder *enc;
};
static int encodeopus_perform(CHiPubNode *pubn) {
struct CHiEncodeOpusNode *n = (struct CHiEncodeOpusNode*) pubn;
CHiImage *newpcm = CHi_Crawl(&pubn->sinks[0])->data.sample;
if(n->pcmSamples + newpcm->width > n->pcmCapacity) {
n->pcmbuf = realloc(n->pcmbuf, sizeof(*n->pcmbuf) * (n->pcmCapacity = ((n->pcmSamples + newpcm->width + 1023) & ~1023)));
}
memcpy(&n->pcmbuf[n->pcmSamples], newpcm->data16, sizeof(*n->pcmbuf) * newpcm->width);
n->pcmSamples += newpcm->width;
CHiBSFrames *frames = malloc(sizeof(*frames));
frames->count = 0;
size_t samp;
for(samp = 0; samp + 960 <= n->pcmSamples; samp += 960) {
frames = realloc(frames, sizeof(*frames) + sizeof(*frames->data) * (frames->count + 1));
frames->data[frames->count].timestamp = n->timestamp;
frames->data[frames->count].ptr = malloc(1276);
frames->data[frames->count].sz = opus_encode(n->enc, &n->pcmbuf[samp], 960, frames->data[frames->count].ptr, 1276);
if(frames->data[frames->count].sz < 0) puts("OPUS ERR");
frames->count++;
n->timestamp += 20;
}
memmove(n->pcmbuf, n->pcmbuf + samp, sizeof(*n->pcmbuf) * (n->pcmSamples - samp));
n->pcmSamples -= samp;
pubn->sources[0].type = CUTIHI_VAL_OPUSBS;
pubn->sources[0].data.bitstream = frames;
return 1;
}
CUTIVIS CHiPubNode *CHi_EncodeOpus() {
struct CHiEncodeOpusNode *ret = malloc(sizeof(*ret));
ret->pubn.type = CUTIHI_T('CEnc','Opus');
ret->pubn.Start = CHi_EncodeOpus_Start;
ret->pubn.Perform = &encodeopus_perform;
ret->pubn.Stop = CHi_EncodeOpus_Stop;
ret->pubn.clean = 0;
ret->pubn.sinks = calloc(sizeof(*ret->pubn.sinks), ret->pubn.sinkCount = 1);
ret->pubn.sources = calloc(sizeof(*ret->pubn.sources), ret->pubn.sourceCount = 1);
return &ret->pubn;
}
CUTIVIS int CHi_EncodeOpus_Start(CHiPubNode *pubn) {
struct CHiEncodeOpusNode *n = (struct CHiEncodeOpusNode*) pubn;
int error;
n->enc = opus_encoder_create(48000, 1, OPUS_APPLICATION_AUDIO, &error);
n->pcmSamples = 0;
n->pcmCapacity = 1024;
n->pcmbuf = malloc(sizeof(*n->pcmbuf) * n->pcmCapacity);
n->timestamp = 0;
return 1;
}
CUTIVIS int CHi_EncodeOpus_Stop(CHiPubNode *pubn) {
struct CHiEncodeOpusNode *n = (struct CHiEncodeOpusNode*) pubn;
opus_encoder_destroy(n->enc);
free(n->pcmbuf);
return 1;
}

132
hi/relay.c Normal file
View File

@ -0,0 +1,132 @@
#include"relay.h"
#include"img.h"
#include<tmmintrin.h>
#include<X11/XKBlib.h>
#include<X11/extensions/record.h>
#include<stdio.h>
#include<pthread.h>
#include<stdatomic.h>
#include<math.h>
static int scale_perform(CHiPubNode *n) {
float *scales = CHi_Crawl(&n->sinks[0])->data.vec4;
CHiImage *img = CHi_Crawl(&n->sinks[1])->data.sample;
if(n->sources[0].data.sample) {
CHi_Image_Free(n->sources[0].data.sample);
}
CHiImage *ret = n->sources[0].data.sample = CHi_Image_New(img->bpc, img->channels, img->stride, img->width, img->height, NULL);
__m128i iscales = _mm_set_epi16(
scales[3] * 65535, scales[0] * 65535, scales[1] * 65535, scales[2] * 65535,
scales[3] * 65535, scales[0] * 65535, scales[1] * 65535, scales[2] * 65535
);
for(size_t y = 0; y < img->height; y++) {
for(size_t x = 0; x < img->width; x += 16) {
__m128i pixels8 = _mm_loadu_si128((__m128i*) ((uintptr_t) img->data16 + y * img->stride + x));
__m128i mulled = _mm_mulhi_epu16(pixels8, iscales);
_mm_storeu_si128((__m128i*) ((uintptr_t) ret->data16 + y * img->stride + x), mulled);
}
}
return 1;
}
CUTIVIS CHiPubNode *CHi_ComponentScale() {
CHiPubNode *n = malloc(sizeof(*n));
n->type = CUTIHI_T('CCmp','nScl');
n->Start = n->Stop = NULL;
n->Perform = scale_perform;
n->clean = 0;
n->sinkCount = 2;
n->sinks = calloc(sizeof(*n->sinks), n->sinkCount);
n->sourceCount = 1;
n->sources = calloc(sizeof(*n->sources), n->sourceCount);
return n;
}
static Display *dpy;
typedef struct {
CHiPubNode pub;
XRecordContext rc;
pthread_t thrd;
atomic_int key;
atomic_bool on;
} CHiKeyhookNode;
static void keyhook_handler(XPointer ud, XRecordInterceptData *recdata) {
if(recdata->category != XRecordFromServer) {
return;
}
int type = ((unsigned char*) recdata->data)[0] & 0x7F;
int key = ((unsigned char*) recdata->data)[1];
int repeat = recdata->data[2] & 1;
CHiKeyhookNode *n = (CHiKeyhookNode*) ud;
printf("%i\n", key);
if(!repeat && key == n->key) {
if(type == KeyPress) {
n->on = 1;
} else if(type == KeyRelease) {
n->on = 0;
}
}
XRecordFreeData(recdata);
}
static void *keyhook_thread(void *ud) {
CHiKeyhookNode *n = ud;
XRecordRange *rr = XRecordAllocRange();
rr->device_events.first = KeyPress;
rr->device_events.last = ButtonRelease;
n->rc = XRecordCreateContext(dpy, 0, &(XRecordClientSpec) {XRecordAllClients}, 1, &rr, 1);
XRecordEnableContext(dpy, n->rc, keyhook_handler, (XPointer) n);
return NULL;
}
static int keyhook_perform(CHiPubNode *n) {
((CHiKeyhookNode*) n)->key = CHi_Crawl(&n->sinks[0])->data.vec4[0];
n->sources[0].type = CUTIHI_VAL_VEC4;
if(n->ng->compilationStatus == CUTIHI_COMP_READY || n->sinks[1].data.vec4[0] == 0) {
n->sources[0].data.vec4[0] = ((CHiKeyhookNode*) n)->on;
} else if(((CHiKeyhookNode*) n)->on) {
n->sources[0].data.vec4[0] = fminf(1, n->sources[0].data.vec4[0] + CHi_Time_GetDelta(n->ng) * n->sinks[1].data.vec4[0]);
} else {
n->sources[0].data.vec4[0] = fmaxf(0, n->sources[0].data.vec4[0] - CHi_Time_GetDelta(n->ng) * n->sinks[1].data.vec4[0]);
}
return 1;
}
CUTIVIS CHiPubNode *CHi_Keyhook() {
CHiKeyhookNode *n = malloc(sizeof(*n));
n->pub.type = CUTIHI_T('CKey','hook');
n->pub.Start = n->pub.Stop = NULL;
n->pub.Perform = keyhook_perform;
n->pub.clean = 0;
n->pub.sinkCount = 2;
n->pub.sinks = calloc(sizeof(*n->pub.sinks), n->pub.sinkCount);
n->pub.sourceCount = 1;
n->pub.sources = calloc(sizeof(*n->pub.sources), n->pub.sourceCount);
n->on = 0;
n->key = 0;
if(!dpy) {
dpy = XOpenDisplay(NULL);
}
pthread_create(&n->thrd, NULL, keyhook_thread, n);
return &n->pub;
}

14
hi/relay.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef _CUTIHI_RELAY_H
#define _CUTIHI_RELAY_H
#include"node.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif

43
hi/test.c Normal file
View File

@ -0,0 +1,43 @@
#include"node.h"
#include<vips/vips.h>
int main() {
VIPS_INIT("Cuticle Test");
CHiPubNode *noed0 = CHi_ConstantSample();
noed0->values[0].type = CUTIHI_VAL_VEC4;
noed0->values[0].vec4[0] = 255;
noed0->values[0].vec4[1] = 0;
noed0->values[0].vec4[2] = 0;
noed0->values[0].vec4[3] = 255;
CHiPubNode *noed1 = CHi_ConstantSample();
noed1->values[0].type = CUTIHI_VAL_VEC4;
noed1->values[0].vec4[0] = 0;
noed1->values[0].vec4[1] = 0;
noed1->values[0].vec4[2] = 255;
noed1->values[0].vec4[3] = 0;
CHiPubNode *noed2 = CHi_Embed();
noed2->values[1].type = CUTIHI_VAL_LINKED;
noed2->values[1].linked.to = noed0;
noed2->values[1].linked.idx = 1;
noed2->values[2].type = CUTIHI_VAL_VEC4;
noed2->values[2].vec4[0] = 0;
noed2->values[2].vec4[1] = 0;
noed2->values[2].vec4[2] = 1;
noed2->values[2].vec4[3] = 1;
noed2->values[3].type = CUTIHI_VAL_LINKED;
noed2->values[3].linked.to = noed1;
noed2->values[3].linked.idx = 1;
noed2->values[4].type = CUTIHI_VAL_VEC4;
noed2->values[4].vec4[0] = 0;
noed2->values[4].vec4[1] = 0;
noed2->values[4].vec4[2] = 1;
noed2->values[4].vec4[3] = 1;
noed2->Perform(noed2);
vips_pngsave((VipsImage*) noed2->values[0].sample, "test.png", NULL);
}

137
hi/webcam.c Normal file
View File

@ -0,0 +1,137 @@
#include"node.h"
#include<sys/ioctl.h>
#include<sys/time.h>
#include<sys/mman.h>
#include<stdlib.h>
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
#include<time.h>
#include<linux/videodev2.h>
#include<libv4l2.h>
#include<errno.h>
#include"img.h"
#include<assert.h>
#include<smmintrin.h>
static int camId = -1;
static struct Buf {
size_t length;
uint8_t *ptr;
} bufs[2];
struct v4l2_format fmt;
static void xioctl(int fh, int request, void *arg) {
int r;
do {
r = v4l2_ioctl(fh, request, arg);
} while(r == -1 && ((errno == EINTR) || (errno == EAGAIN)));
if(r == -1) {
fprintf(stderr, "error %d, %s\\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
}
static int camera_perform(CHiPubNode *pubn) {
pubn->sources[0].type = CUTIHI_VAL_SAMPLE;
fd_set fds;
FD_ZERO(&fds);
FD_SET(camId, &fds);
int r = select(camId + 1, &fds, NULL, NULL, &(struct timeval) {.tv_sec = 0, .tv_usec = 100});
if(r == -1) {
return 1;
}
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
xioctl(camId, VIDIOC_DQBUF, &buf);
CHiImage *ret;
if(pubn->sources[0].data.sample) {
ret = pubn->sources[0].data.sample;
} else {
ret = CHi_Image_New(2, 4, 8 * ((fmt.fmt.pix.width + 15) & ~15), fmt.fmt.pix.width, fmt.fmt.pix.height, NULL);
pubn->sources[0].data.sample = ret;
}
/* 24-to-32 TODO: make faster by processing 48-to-64 bytes in one iteration (like bgra32torgb24 does) */
for(size_t y = 0; y < ret->height; y++) {
for(size_t x = 0; x < ret->width; x += 2) {
__m128i asdf = _mm_loadu_si128((__m128i*) &bufs[buf.index].ptr[(y * ret->width + x) * 3]);
asdf = _mm_shuffle_epi8(asdf, _mm_set_epi8(-128, -128, 5, -128, 4, -128, 3, -128, -128, -128, 2, -128, 1, -128, 0, -128));
asdf = _mm_or_si128(asdf, _mm_set_epi32(0xFFFF0000, 0, 0xFFFF0000, 0));
_mm_stream_si128((__m128i*) ((uintptr_t) ret->data16 + y * ret->stride + x * 8), asdf);
}
}
xioctl(camId, VIDIOC_QBUF, &buf);
return 1;
}
CUTIVIS CHiPubNode *CHi_Camera() {
if(camId == -1) {
camId = v4l2_open("/dev/video0", O_RDWR | O_NONBLOCK, 0);
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 640;
fmt.fmt.pix.height = 480;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_BGR24;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
xioctl(camId, VIDIOC_S_FMT, &fmt);
assert(fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_BGR24);
struct v4l2_requestbuffers req;
memset(&req, 0, sizeof(req));
req.count = 2;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
xioctl(camId, VIDIOC_REQBUFS, &req);
for(int i = 0; i < 2; i++) {
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
xioctl(camId, VIDIOC_QUERYBUF, &buf);
bufs[i].length = buf.length;
bufs[i].ptr = v4l2_mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, camId, buf.m.offset);
assert(MAP_FAILED != bufs[i].ptr);
}
for(int i = 0; i < 2; i++) {
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
xioctl(camId, VIDIOC_QBUF, &buf);
}
xioctl(camId, VIDIOC_STREAMON, &(enum v4l2_buf_type) {V4L2_BUF_TYPE_VIDEO_CAPTURE});
}
CHiPubNode *pubn = malloc(sizeof(*pubn));
pubn->type = CUTIHI_T('CWeb','Cam ');
pubn->clean = 0;
pubn->Start = pubn->Stop = NULL;
pubn->Perform = camera_perform;
pubn->sinks = calloc(sizeof(*pubn->sinks), pubn->sinkCount = 0);
pubn->sources = calloc(sizeof(*pubn->sources), pubn->sourceCount = 1);
pubn->ng = NULL;
return pubn;
}

418
hi/webmdec.cpp Normal file
View File

@ -0,0 +1,418 @@
#include"node.h"
#include<stdlib.h>
#include<webm/webm_parser.h>
#include<webm/file_reader.h>
#include<vpx/vpx_decoder.h>
#include<vpx/vp8dx.h>
#include<assert.h>
#include<time.h>
#include"img.h"
#include<string.h>
#include<tmmintrin.h>
#include<smmintrin.h>
#include<opus.h>
#include<math.h>
#include"linearity.h"
struct CHiMovieNode;
struct CueParser : webm::Callback {
CHiMovieNode *node;
CueParser(CHiMovieNode *node) : node(node) {}
webm::Status OnInfo(const webm::ElementMetadata& metadata, const webm::Info& info) final override;
webm::Status OnTrackEntry(const webm::ElementMetadata &metadata, const webm::TrackEntry &info) override;
webm::Status OnSegmentBegin(const webm::ElementMetadata &metadata, webm::Action *action) override;
webm::Status OnCuePoint(const webm::ElementMetadata &metadata, const webm::CuePoint &cue) override;
};
struct AudioParser final : webm::Callback {
uint64_t audioTrack;
uint64_t currentClusterTimecode;
uint64_t untihl;
bool stop = true;
bool skip = false;
#define SAMPLE_ARR 48000
OpusDecoder *opus;
size_t sampleI = 0;
size_t sampleReadI = 0;
int16_t sampleArray[SAMPLE_ARR];
webm::Status OnClusterBegin(const webm::ElementMetadata &metadata, const webm::Cluster &cluster, webm::Action *action) final override {
currentClusterTimecode = cluster.timecode.value();
return webm::Status(webm::Status::kOkCompleted);
}
webm::Status OnBlockBegin(const webm::ElementMetadata &metadata, const webm::Block &block, webm::Action *action) final override {
if(block.track_number != audioTrack) {
skip = true;
*action = webm::Action::kSkip;
} else {
skip = false;
if(currentClusterTimecode + block.timecode >= untihl) {
stop = true;
} else {
stop = false;
}
}
return webm::Status(webm::Status::kOkCompleted);
}
webm::Status OnSimpleBlockBegin(const webm::ElementMetadata &metadata, const webm::SimpleBlock &block, webm::Action *action) final override {
return OnBlockBegin(metadata, block, action);
}
webm::Status OnFrame(const webm::FrameMetadata &metadata, webm::Reader *reader, uint64_t *bytes_remaining) final override {
uint8_t *data = new uint8_t[metadata.size];
uint64_t actuallyRead;
reader->Read(metadata.size, data, &actuallyRead);
if(!skip) {
int16_t *f = new int16_t[6400];
int numSamples = opus_decode(opus, data, metadata.size, f, 6400, 0);
if(numSamples >= 0) {
if(SAMPLE_ARR - sampleI >= (size_t) numSamples) {
memcpy(&sampleArray[sampleI], f, sizeof(*sampleArray) * numSamples);
sampleI = (sampleI + numSamples) % SAMPLE_ARR;
} else {
memcpy(&sampleArray[sampleI], f, sizeof(*sampleArray) * (SAMPLE_ARR - sampleI));
memcpy(sampleArray, &f[SAMPLE_ARR - sampleI], sizeof(*sampleArray) * (numSamples - SAMPLE_ARR + sampleI));
sampleI = (sampleI + numSamples) % SAMPLE_ARR;
}
}
delete[] f;
}
delete[] data;
*bytes_remaining = 0;
return webm::Status{stop ? webm::Status::kOkPartial : webm::Status::kOkCompleted};
}
};
struct FrameParser final : webm::Callback {
uint64_t videoTrack, audioTrack;
uint64_t currentClusterTimecode;
uint64_t untihl;
bool skip = true;
vpx_image *lastImg = nullptr;
CHiImage *output = nullptr;
vpx_codec_ctx_t *codec;
vpx_codec_iter_t *iter;
uint64_t currentlyAt = 0;
webm::Status OnClusterBegin(const webm::ElementMetadata &metadata, const webm::Cluster &cluster, webm::Action *action) final override {
currentClusterTimecode = cluster.timecode.value();
return webm::Status(webm::Status::kOkCompleted);
}
webm::Status OnBlockBegin(const webm::ElementMetadata &metadata, const webm::Block &block, webm::Action *action) final override {
/*if(block.track_number == videoTrack) {
printf("%lu %lu %i\n", currentClusterTimecode + block.timecode, untihl, currentlyAt <= untihl && currentClusterTimecode + block.timecode >= untihl);
}*/
if(block.track_number != videoTrack) {
*action = webm::Action::kSkip;
} else {
if(currentlyAt <= untihl && currentClusterTimecode + block.timecode >= untihl) {
skip = false;
} else {
skip = true;
}
currentlyAt = currentClusterTimecode + block.timecode;
}
return webm::Status(webm::Status::kOkCompleted);
}
webm::Status OnSimpleBlockBegin(const webm::ElementMetadata &metadata, const webm::SimpleBlock &block, webm::Action *action) final override {
return OnBlockBegin(metadata, block, action);
}
webm::Status OnFrame(const webm::FrameMetadata &metadata, webm::Reader *reader, uint64_t *bytes_remaining) final override {
//printf("FRAME WITH SKIP %i\n", skip);
uint8_t *data = new uint8_t[metadata.size];
uint64_t actuallyRead;
reader->Read(metadata.size, data, &actuallyRead);
vpx_codec_decode(codec, data, metadata.size, NULL, 0);
vpx_image *img = NULL;
while((img = vpx_codec_get_frame(codec, iter)) != NULL) {
if(lastImg) vpx_img_free(lastImg);
lastImg = img;
}
if(!skip && lastImg) {
assert(lastImg->fmt & VPX_IMG_FMT_PLANAR);
output = CHi_Image_New(2, 4, 8 * ((lastImg->d_w + 15) & ~15), lastImg->d_w, lastImg->d_h, NULL);
__m128i z = _mm_set1_epi32(0);
__m128i alpha = _mm_set_epi32(0xFFFF0000, 0, 0xFFFF0000, 0);
__m128i sub16 = _mm_set1_epi32(-16);
__m128i sub128 = _mm_set1_epi32(-128);
#pragma omp parallel for simd
for(size_t y = 0; y < lastImg->d_h; y++) {
for(size_t x = 0; x < lastImg->d_w; x += 4) {
__m128i ychannel = _mm_loadu_si128((__m128i*) (lastImg->planes[VPX_PLANE_Y] + y * lastImg->stride[VPX_PLANE_Y] + x));
__m128i uchannel = _mm_loadu_si128((__m128i*) (lastImg->planes[VPX_PLANE_U] + y / 2 * lastImg->stride[VPX_PLANE_U] + x / 2));
uchannel = _mm_unpacklo_epi8(uchannel, uchannel); // stretch color channels
__m128i vchannel = _mm_loadu_si128((__m128i*) (lastImg->planes[VPX_PLANE_V] + y / 2 * lastImg->stride[VPX_PLANE_V] + x / 2));
vchannel = _mm_unpacklo_epi8(vchannel, vchannel); // stretch color channels
/* Interleave with zeroes to push out 12 of 16 pixels (we're working in groups of four) */
__m128i ylo = _mm_add_epi32(sub16, _mm_unpacklo_epi16(_mm_unpacklo_epi8(ychannel, z), z));
__m128i ulo = _mm_add_epi32(sub128, _mm_unpacklo_epi16(_mm_unpacklo_epi8(uchannel, z), z));
__m128i vlo = _mm_add_epi32(sub128, _mm_unpacklo_epi16(_mm_unpacklo_epi8(vchannel, z), z));
/* Start parallel matrix multiplication (BT.709 matrix * 255/219 to turn from studio to full range) */
/*
/ 1.164 0 1.833 \
RGB = | 1.164 -0.218 -0.545 | * (Y - 16, U - 128, V - 128)
\ 1.164 2.160 0 /
*/
__m128i partY = _mm_mullo_epi32(ylo, _mm_set1_epi32(297));
__m128i partVR = _mm_mullo_epi32(vlo, _mm_set1_epi32(467));
__m128i partUG = _mm_mullo_epi32(ulo, _mm_set1_epi32(-56));
__m128i partVG = _mm_mullo_epi32(vlo, _mm_set1_epi32(-139));
__m128i partUB = _mm_mullo_epi32(ulo, _mm_set1_epi32(551));
/* Finish matrix multiplication by summing up parts (finishing the dot products), clip */
__m128i r = _mm_max_epi32(z, _mm_min_epi32(_mm_set1_epi32(0xFFFF), _mm_add_epi32(partY, partVR)));
__m128i g = _mm_max_epi32(z, _mm_min_epi32(_mm_set1_epi32(0xFFFF), _mm_add_epi32(partY, _mm_add_epi32(partUG, partVG))));
__m128i b = _mm_max_epi32(z, _mm_min_epi32(_mm_set1_epi32(0xFFFF), _mm_add_epi32(partY, partUB)));
r = apply_gamma_epi32(r, _mm_set1_ps(2.2f));
g = apply_gamma_epi32(g, _mm_set1_ps(2.2f));
b = apply_gamma_epi32(b, _mm_set1_ps(2.2f));
__m128i rgblo = _mm_or_si128(alpha, _mm_or_si128(_mm_or_si128(_mm_unpacklo_epi32(b, z), _mm_slli_si128(_mm_unpacklo_epi32(g, z), 2)), _mm_slli_si128(_mm_unpacklo_epi32(r, z), 4)));
_mm_stream_si128((__m128i*) ((uintptr_t) output->data16 + y * output->stride + x * 8 + 0), rgblo);
__m128i rgbhi = _mm_or_si128(alpha, _mm_or_si128(_mm_or_si128(_mm_unpackhi_epi32(b, z), _mm_slli_si128(_mm_unpackhi_epi32(g, z), 2)), _mm_slli_si128(_mm_unpackhi_epi32(r, z), 4)));
_mm_stream_si128((__m128i*) ((uintptr_t) output->data16 + y * output->stride + x * 8 + 16), rgbhi);
}
}
}
delete[] data;
*bytes_remaining = 0;
webm::Status ret{skip ? webm::Status::kOkCompleted : webm::Status::kOkPartial};
skip = true;
return ret;
}
};
struct CHiMovieNode {
int64_t timeCache = -1;
char *filepathCache;
FILE *vf;
webm::FileReader vreader;
webm::WebmParser vparser;
FrameParser fp;
std::string vcodecid;
size_t vw, vh;
FILE *af;
webm::FileReader areader;
webm::WebmParser aparser;
AudioParser ap;
std::vector<webm::CuePoint> cuepoints;
uint64_t segmentOff, videoTrack, audioTrack;
double duration;
vpx_codec_ctx_t codec;
vpx_codec_iter_t iter;
CHiPubNode pub;
};
webm::Status CueParser::OnInfo(const webm::ElementMetadata &metadata, const webm::Info &info) {
node->duration = info.duration.value() / 1000;
return webm::Status(webm::Status::kOkCompleted);
}
webm::Status CueParser::OnTrackEntry(const webm::ElementMetadata &metadata, const webm::TrackEntry &info) {
if(info.track_type.value() == webm::TrackType::kVideo && info.is_enabled.value() /*&& !info.uses_lacing.value()*/) {
node->vcodecid = info.codec_id.value();
node->videoTrack = info.track_number.value();
node->vw = info.video.value().pixel_width.value();
node->vh = info.video.value().pixel_height.value();
}
if(info.track_type.value() == webm::TrackType::kAudio && info.is_enabled.value()) {
node->audioTrack = info.track_number.value();
}
return webm::Status(webm::Status::kOkCompleted);
}
webm::Status CueParser::OnSegmentBegin(const webm::ElementMetadata &metadata, webm::Action *action) {
node->segmentOff = metadata.position + metadata.header_size;
return webm::Status(webm::Status::kOkCompleted);
}
webm::Status CueParser::OnCuePoint(const webm::ElementMetadata &metadata, const webm::CuePoint &cue) {
node->cuepoints.push_back(cue);
return webm::Status(webm::Status::kOkCompleted);
}
static int movie_perform(CHiPubNode *pub) {
CHiMovieNode *node = (CHiMovieNode*) ((uintptr_t) pub - offsetof(CHiMovieNode, pub));
int64_t t;
if(pub->sinks[1].type == CUTIHI_VAL_NONE) t = CHi_Time_Get(pub->ng) * 1000;
else t = CHi_Crawl(&pub->sinks[1])->data.vec4[0] * 1000;
pub->sources[0].type = CUTIHI_VAL_SAMPLE;
char *filepath = CHi_Crawl(&pub->sinks[0])->data.text;
if(!node->filepathCache || strcmp(node->filepathCache, filepath) != 0) {
node->vf = fopen(filepath, "rb");
new (&node->vreader) webm::FileReader{node->vf};
new (&node->vparser) webm::WebmParser{};
node->af = fopen(filepath, "rb");
new (&node->areader) webm::FileReader{node->af};
new (&node->aparser) webm::WebmParser{};
node->cuepoints.clear();
CueParser cp{node};
node->vparser.Feed(&cp, &node->vreader);
free(node->filepathCache);
node->filepathCache = filepath;
node->timeCache = std::numeric_limits<int64_t>::max();
if(node->vcodecid == "V_VP9") {
vpx_codec_dec_init(&node->codec, vpx_codec_vp9_dx(), NULL, 0);
} else if(node->vcodecid == "V_VP8") {
vpx_codec_dec_init(&node->codec, vpx_codec_vp8_dx(), NULL, 0);
} else {
return 1;
}
new (&node->fp) FrameParser{};
node->fp.videoTrack = node->videoTrack;
node->fp.codec = &node->codec;
node->fp.iter = &node->iter;
new (&node->ap) AudioParser{};
int error;
node->ap.opus = opus_decoder_create(48000, 1, &error);
node->ap.audioTrack = node->audioTrack;
}
if(t == node->timeCache) {
return 1;
}
if(pub->sources[0].data.sample) {
CHi_Image_Free(pub->sources[0].data.sample);
pub->sources[0].data.sample = nullptr;
}
if(t >= 0 && t < 1000 * node->duration) {
if(t < node->timeCache || (t - node->timeCache) > 5000) {
if(node->cuepoints.size() > 0) {
size_t i;
for(i = 0; i < node->cuepoints.size(); i++) {
if(t < node->cuepoints[i].time.value()) {
break;
}
}
if(i != 0) i--;
for(webm::Element<webm::CueTrackPositions> &p : node->cuepoints[i].cue_track_positions) {
if(p.value().track.value() == node->videoTrack) {
fseek(node->vf, node->segmentOff + p.value().cluster_position.value(), SEEK_SET);
fseek(node->af, node->segmentOff + p.value().cluster_position.value(), SEEK_SET);
break;
}
}
} else {
fseek(node->vf, 0, SEEK_SET);
fseek(node->af, 0, SEEK_SET);
}
}
node->fp.untihl = t;
node->ap.untihl = t;
/* Always necessary for some reason, else stops parsing after seek (as in no callbacks called).. */
node->vparser.DidSeek();
node->aparser.DidSeek();
node->vparser.Feed(&node->fp, &node->vreader);
node->aparser.Feed(&node->ap, &node->areader);
pub->sources[0].data.sample = node->fp.output;
node->timeCache = t;
}
if(!pub->sources[0].data.sample) {
pub->sources[0].data.sample = CHi_Image_New(2, 4, 8 * node->vw, node->vw, node->vh, NULL);
}
size_t width = roundf(CHi_Time_GetDelta(pub->ng) * 48000);
CHiImage *aud = CHi_Image_New(4, 1, 4 * width, width, 1, NULL);
if(node->pub.ng->compilationStatus == CUTIHI_COMP_RUNNING) {
if(node->ap.sampleReadI + width > SAMPLE_ARR) {
memcpy(aud->data16, node->ap.sampleArray + node->ap.sampleReadI, sizeof(*node->ap.sampleArray) * (SAMPLE_ARR - node->ap.sampleReadI));
memcpy(aud->data16 + SAMPLE_ARR - node->ap.sampleReadI, node->ap.sampleArray, sizeof(*node->ap.sampleArray) * (width - SAMPLE_ARR + node->ap.sampleReadI));
} else {
memcpy(aud->data16, node->ap.sampleArray + node->ap.sampleReadI, sizeof(*node->ap.sampleArray) * width);
}
node->ap.sampleReadI = (node->ap.sampleReadI + width) % SAMPLE_ARR;
} else {
memset(aud->data16, 0, aud->stride * aud->height);
}
if(pub->sources[1].data.sample) CHi_Image_Free(pub->sources[1].data.sample);
pub->sources[1].type = CUTIHI_VAL_SAMPLE;
pub->sources[1].data.sample = aud;
pub->clean = 0;
return 1;
}
extern "C" {
CUTIVIS CHiPubNode *CHi_Movie() {
CHiMovieNode *n = (CHiMovieNode*) malloc(sizeof(*n));
new (n) CHiMovieNode();
n->pub.type = CUTIHI_T('CMov','ie ');
n->pub.Perform = movie_perform;
n->pub.clean = 0;
n->pub.sinkCount = 2;
n->pub.sinks = (CHiValue*) calloc(sizeof(*n->pub.sinks), n->pub.sinkCount);
n->pub.sinks[1].type = CUTIHI_VAL_VEC4;
n->pub.sinks[1].data.vec4[0] = 0;
n->pub.sourceCount = 2;
n->pub.sources = (CHiValue*) calloc(sizeof(*n->pub.sources), n->pub.sourceCount);
return &n->pub;
}
}

431
hi/webmenc.cpp Normal file
View File

@ -0,0 +1,431 @@
#include"node.h"
#include<vpx/vpx_encoder.h>
#include<vpx/vp8cx.h>
#include<new>
#include<mkvmuxer/mkvwriter.h>
#include<assert.h>
#include"mode.h"
#include"img.h"
#include<math.h>
#include<smmintrin.h>
#include<queue>
#include<string.h>
#include"linearity.h"
struct CHiEncodeVP9Node {
vpx_codec_ctx_t codec;
vpx_codec_enc_cfg_t cfg;
enum {
WAITING, IN_PROGRESS
} state;
uint8_t *outY, *outU, *outV;
uint16_t strideY, strideU, strideV;
vpx_codec_iface_t *iface;
CHiPubNode pub;
};
static int encodevp9_perform(CHiPubNode *pub) {
CHiEncodeVP9Node *node = (CHiEncodeVP9Node*) ((uintptr_t) pub - offsetof(CHiEncodeVP9Node, pub));
pub->sources[0].type = CUTIHI_VAL_VP9BS;
pub->sources[0].data.bitstream = NULL;
if(node->state == CHiEncodeVP9Node::WAITING) return 1;
CHiImage *rgbIn = (CHiImage*) CHi_Crawl(&pub->sinks[0])->data.sample;
#pragma omp parallel for simd
for(size_t y = 0; y < node->cfg.g_h; y += 2) {
for(size_t x = 0; x < node->cfg.g_w; x += 16) {
__m128i rgb, partY, partU, partV, dotY, dotU, dotV;
__m128i wipY0 = _mm_setzero_si128();
__m128i wipY1 = _mm_setzero_si128();
__m128i wipU = _mm_setzero_si128();
__m128i wipV = _mm_setzero_si128();
__m128i tempU = _mm_setzero_si128();
__m128i tempV = _mm_setzero_si128();
#define DO_DAH_DOO_DOO(LoOrHi, shufY, shufUV) \
/* Process top two */\
rgb = _mm_srli_epi16(apply_gamma_epi16(line0, _mm_set1_ps(1 / 2.2f)), 8); \
/* Start matrix multiplication (BT.709 + full->studio range) */\
partY = _mm_mullo_epi16(rgb, _mm_set_epi16(0, 47, 157, 16, 0, 47, 157, 16));\
partU = _mm_mullo_epi16(rgb, _mm_set_epi16(0, -25, -85, 110, 0, -25, -85, 110));\
partV = _mm_mullo_epi16(rgb, _mm_set_epi16(0, 110, -100, -10, 0, 110, -100, -10));\
/* Finish mat-mul with dot products */\
dotY = _mm_madd_epi16(partY, _mm_set1_epi16(1));\
dotY = _mm_hadd_epi32(dotY, _mm_setzero_si128());\
dotU = _mm_madd_epi16(partU, _mm_set1_epi16(1));\
dotU = _mm_hadd_epi32(dotU, _mm_setzero_si128());\
dotV = _mm_madd_epi16(partV, _mm_set1_epi16(1));\
dotV = _mm_hadd_epi32(dotV, _mm_setzero_si128());\
/* Insert Ys */\
wipY0 = _mm_or_si128(wipY0, _mm_shuffle_epi8(dotY, shufY));\
/* Save top UV */\
tempU = dotU;\
tempV = dotV;\
\
/* Process bottom two */\
rgb = _mm_srli_epi16(apply_gamma_epi16(line1, _mm_set1_ps(1 / 2.2f)), 8); \
/* Start matrix multiplication (BT.709 + full->studio range) */\
partY = _mm_mullo_epi16(rgb, _mm_set_epi16(0, 47, 157, 16, 0, 47, 157, 16));\
partU = _mm_mullo_epi16(rgb, _mm_set_epi16(0, -25, -85, 110, 0, -25, -85, 110));\
partV = _mm_mullo_epi16(rgb, _mm_set_epi16(0, 110, -100, -10, 0, 110, -100, -10));\
/* Finish mat-mul with dot products */\
dotY = _mm_madd_epi16(partY, _mm_set1_epi16(1));\
dotY = _mm_hadd_epi32(dotY, _mm_setzero_si128());\
dotU = _mm_madd_epi16(partU, _mm_set1_epi16(1));\
dotU = _mm_hadd_epi32(dotU, _mm_setzero_si128());\
dotV = _mm_madd_epi16(partV, _mm_set1_epi16(1));\
dotV = _mm_hadd_epi32(dotV, _mm_setzero_si128());\
/* Insert Ys */\
wipY1 = _mm_or_si128(wipY1, _mm_shuffle_epi8(dotY, shufY));\
/* Save bottom UVs */\
tempU = _mm_hadd_epi32(_mm_add_epi32(tempU, dotU), _mm_setzero_si128());\
tempV = _mm_hadd_epi32(_mm_add_epi32(tempV, dotV), _mm_setzero_si128());\
\
/* Insert UVs */\
wipU = _mm_or_si128(wipU, _mm_shuffle_epi8(_mm_srli_epi32(tempU, 2), shufUV));\
wipV = _mm_or_si128(wipV, _mm_shuffle_epi8(_mm_srli_epi32(tempV, 2), shufUV));
__m128i line0 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 0) * rgbIn->stride + (x + 0) * 8)); // Load two pixels
__m128i line1 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 1) * rgbIn->stride + (x + 0) * 8)); // Load two pixels
DO_DAH_DOO_DOO(_mm_unpacklo_epi8,
_mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 5, 1),
_mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 1));
line0 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 0) * rgbIn->stride + (x + 2) * 8)); // Load two pixels
line1 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 1) * rgbIn->stride + (x + 2) * 8)); // Load two pixels
DO_DAH_DOO_DOO(_mm_unpacklo_epi8,
_mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 5, 1, -128, -128),
_mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 1, -128));
line0 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 0) * rgbIn->stride + (x + 4) * 8)); // Load two pixels
line1 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 1) * rgbIn->stride + (x + 4) * 8)); // Load two pixels
DO_DAH_DOO_DOO(_mm_unpacklo_epi8,
_mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 5, 1, -128, -128, -128, -128),
_mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 1, -128, -128));
line0 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 0) * rgbIn->stride + (x + 6) * 8)); // Load two pixels
line1 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 1) * rgbIn->stride + (x + 6) * 8)); // Load two pixels
DO_DAH_DOO_DOO(_mm_unpacklo_epi8,
_mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, 5, 1, -128, -128, -128, -128, -128, -128),
_mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 1, -128, -128, -128));
line0 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 0) * rgbIn->stride + (x + 8) * 8)); // Load two pixels
line1 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 1) * rgbIn->stride + (x + 8) * 8)); // Load two pixels
DO_DAH_DOO_DOO(_mm_unpacklo_epi8,
_mm_set_epi8(-128, -128, -128, -128, -128, -128, 5, 1, -128, -128, -128, -128, -128, -128, -128, -128),
_mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 1, -128, -128, -128, -128));
line0 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 0) * rgbIn->stride + (x + 10) * 8)); // Load two pixels
line1 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 1) * rgbIn->stride + (x + 10) * 8)); // Load two pixels
DO_DAH_DOO_DOO(_mm_unpacklo_epi8,
_mm_set_epi8(-128, -128, -128, -128, 5, 1, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128),
_mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 1, -128, -128, -128, -128, -128));
line0 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 0) * rgbIn->stride + (x + 12) * 8)); // Load two pixels
line1 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 1) * rgbIn->stride + (x + 12) * 8)); // Load two pixels
DO_DAH_DOO_DOO(_mm_unpacklo_epi8,
_mm_set_epi8(-128, -128, 5, 1, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128),
_mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, 1, -128, -128, -128, -128, -128, -128));
line0 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 0) * rgbIn->stride + (x + 14) * 8)); // Load two pixels
line1 = _mm_load_si128((__m128i*) ((uintptr_t) rgbIn->data16 + (y + 1) * rgbIn->stride + (x + 14) * 8)); // Load two pixels
DO_DAH_DOO_DOO(_mm_unpacklo_epi8,
_mm_set_epi8( 5, 1, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128),
_mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, 1, -128, -128, -128, -128, -128, -128, -128));
_mm_stream_si128((__m128i*) &node->outY[node->strideY * (y + 0) + x], _mm_add_epi8(_mm_set1_epi8(16), wipY0));
_mm_stream_si128((__m128i*) &node->outY[node->strideY * (y + 1) + x], _mm_add_epi8(_mm_set1_epi8(16), wipY1));
_mm_storeu_si128((__m128i*) &node->outU[node->strideU * (y / 2) + x / 2], _mm_add_epi8(wipU, _mm_set1_epi8(128)));
_mm_storeu_si128((__m128i*) &node->outV[node->strideV * (y / 2) + x / 2], _mm_add_epi8(wipV, _mm_set1_epi8(128)));
}
}
vpx_image_t vpxraw;
vpxraw.fmt = VPX_IMG_FMT_I420;
vpxraw.cs = VPX_CS_BT_709;
vpxraw.range = VPX_CR_STUDIO_RANGE;
vpxraw.bit_depth = 8;
vpxraw.w = vpxraw.d_w = node->cfg.g_w;
vpxraw.h = vpxraw.d_h = node->cfg.g_h;
vpxraw.r_w = vpxraw.r_h = 0;
vpxraw.x_chroma_shift = vpxraw.y_chroma_shift = 1;
vpxraw.img_data_owner = 0;
vpxraw.self_allocd = 0;
vpxraw.bps = 12;
vpxraw.stride[VPX_PLANE_Y] = node->strideY;
vpxraw.planes[VPX_PLANE_Y] = node->outY;
vpxraw.stride[VPX_PLANE_U] = node->strideU;
vpxraw.planes[VPX_PLANE_U] = node->outU;
vpxraw.stride[VPX_PLANE_V] = node->strideV;
vpxraw.planes[VPX_PLANE_V] = node->outV;
vpx_codec_encode(&node->codec, &vpxraw, CHi_Time_Get(pub->ng) * 1000.f, 1, 0, VPX_DL_REALTIME);
auto ret = (CHiBSFrames*) malloc(sizeof(CHiBSFrames));
ret->count = 0;
vpx_codec_iter_t iter = NULL;
const vpx_codec_cx_pkt_t *pkt;
while((pkt = vpx_codec_get_cx_data(&node->codec, &iter)) != NULL) {
if(pkt->kind == VPX_CODEC_CX_FRAME_PKT) {
ret = (CHiBSFrames*) realloc(ret, sizeof(CHiBSFrames) + sizeof(CHiBSFrame) * (ret->count + 1));
ret->data[ret->count].timestamp = pkt->data.frame.pts;
ret->data[ret->count].sz = pkt->data.frame.sz;
ret->data[ret->count].flags = pkt->data.frame.flags & VPX_FRAME_IS_KEY;
ret->data[ret->count].ptr = malloc(ret->data[ret->count].sz);
memcpy(ret->data[ret->count].ptr, pkt->data.frame.buf, ret->data[ret->count].sz);
ret->count++;
}
}
// if(pktRet) v->queueOut.enqueue(pktRet);
//memcpy(node->vpxraw.planes[VPX_PLANE_Y], VIPS_IMAGE_ADDR(y, 0, 0), node->vpxraw.stride[VPX_PLANE_Y] * node->vpxraw.d_h);
//memcpy(node->vpxraw.planes[VPX_PLANE_U], VIPS_IMAGE_ADDR(u, 0, 0), node->vpxraw.stride[VPX_PLANE_U] * (node->vpxraw.d_h >> node->vpxraw.y_chroma_shift));
//memcpy(node->vpxraw.planes[VPX_PLANE_V], VIPS_IMAGE_ADDR(v, 0, 0), node->vpxraw.stride[VPX_PLANE_V] * (node->vpxraw.d_h >> node->vpxraw.y_chroma_shift));
//const vpx_codec_cx_pkt_t *pkt;
//while(!node->queueOut.try_dequeue(pkt)) usleep(0);
pub->sources[0].data.bitstream = ret;
return 1;
}
CUTIVIS CHiPubNode *CHi_EncodeVP8() {
CHiEncodeVP9Node *n = (CHiEncodeVP9Node*) malloc(sizeof(*n));
new (n) CHiEncodeVP9Node();
n->pub.type = CUTIHI_T('CEnc','GVP8');
n->pub.Start = CHi_EncodeVP9_Start;
n->pub.Perform = encodevp9_perform;
n->pub.Stop = CHi_EncodeVP9_Stop;
n->pub.clean = 0;
n->pub.sinks = (CHiValue*) calloc(sizeof(*n->pub.sinks), n->pub.sinkCount = 1);
n->pub.sources = (CHiValue*) calloc(sizeof(*n->pub.sources), n->pub.sourceCount = 1);
n->state = CHiEncodeVP9Node::WAITING;
n->iface = vpx_codec_vp8_cx();
return &n->pub;
}
CUTIVIS CHiPubNode *CHi_EncodeVP9() {
CHiEncodeVP9Node *n = (CHiEncodeVP9Node*) malloc(sizeof(*n));
new (n) CHiEncodeVP9Node();
n->pub.type = CUTIHI_T('CEnc','GVP9');
n->pub.Start = CHi_EncodeVP9_Start;
n->pub.Perform = encodevp9_perform;
n->pub.Stop = CHi_EncodeVP9_Stop;
n->pub.clean = 0;
n->pub.sinks = (CHiValue*) calloc(sizeof(*n->pub.sinks), n->pub.sinkCount = 1);
n->pub.sources = (CHiValue*) calloc(sizeof(*n->pub.sources), n->pub.sourceCount = 1);
n->state = CHiEncodeVP9Node::WAITING;
n->iface = vpx_codec_vp9_cx();
return &n->pub;
}
CUTIVIS int CHi_EncodeVP9_Start(CHiPubNode *pubn) {
CHiEncodeVP9Node *node = (CHiEncodeVP9Node*) ((uintptr_t) pubn - offsetof(CHiEncodeVP9Node, pub));
node->state = CHiEncodeVP9Node::IN_PROGRESS;
CHiImage *firstFrame = (CHiImage*) CHi_Crawl(&pubn->sinks[0])->data.sample;
vpx_codec_enc_config_default(node->iface, &node->cfg, 0);
node->cfg.g_w = firstFrame->width;
node->cfg.g_h = firstFrame->height;
node->cfg.g_timebase.num = 1;
node->cfg.g_timebase.den = 30;
node->cfg.g_lag_in_frames = 0;
node->cfg.g_threads = 8;
node->cfg.kf_mode = VPX_KF_AUTO;
node->cfg.kf_max_dist = 300;
node->cfg.rc_end_usage = VPX_VBR;
node->cfg.rc_target_bitrate = 512;
node->cfg.rc_min_quantizer = 4;
node->cfg.rc_max_quantizer = 48;
vpx_codec_enc_init(&node->codec, node->iface, &node->cfg, 0);
vpx_codec_control(&node->codec, VP8E_SET_CPUUSED, 8);
vpx_codec_control(&node->codec, VP9E_SET_ROW_MT, 1);
vpx_codec_control(&node->codec, VP9E_SET_TILE_COLUMNS, 2);
vpx_codec_control(&node->codec, VP9E_SET_TILE_ROWS, 1);
vpx_codec_control(&node->codec, VP9E_SET_TUNE_CONTENT, VP9E_CONTENT_SCREEN);
node->strideY = (node->cfg.g_w + 64) & ~63;
node->strideU = (node->cfg.g_w / 2 + 64) & ~63;
node->strideV = (node->cfg.g_w / 2 + 64) & ~63;
node->outY = (uint8_t*) _mm_malloc(node->strideY * node->cfg.g_h, 16);
node->outU = (uint8_t*) _mm_malloc(node->strideU * node->cfg.g_h / 2, 16);
node->outV = (uint8_t*) _mm_malloc(node->strideV * node->cfg.g_h / 2, 16);
return 1;
}
CUTIVIS int CHi_EncodeVP9_Stop(CHiPubNode *pubn) {
CHiEncodeVP9Node *node = (CHiEncodeVP9Node*) ((uintptr_t) pubn - offsetof(CHiEncodeVP9Node, pub));
node->state = CHiEncodeVP9Node::WAITING;
_mm_free(node->outY);
_mm_free(node->outU);
_mm_free(node->outV);
return 1;
}
struct CHiMuxWebmNode {
CHiPubNode pub;
mkvmuxer::MkvWriter w;
mkvmuxer::Segment seg;
size_t videoTrack, audioTrack;
std::queue<CHiBSFrame> audioBacklog;
std::queue<CHiBSFrame> videoBacklog;
};
static int muxwebm_perform(CHiPubNode *pubn) {
using namespace mkvmuxer;
CHiMuxWebmNode *alln = (CHiMuxWebmNode*) pubn;
if(pubn->sinks[1].data.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]);
}
}
auto vp9bs = CHi_Crawl(&pubn->sinks[0])->data.bitstream;
if(vp9bs) {
for(size_t i = 0; i < vp9bs->count; i++) {
alln->videoBacklog.push(vp9bs->data[i]);
}
}
while(pubn->sinks[1].data.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);
frame.set_timestamp(alln->audioBacklog.front().timestamp * 1000000L);
frame.set_is_key(true);
alln->seg.AddGenericFrame(&frame);
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)) {
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);
frame.set_timestamp(alln->videoBacklog.front().timestamp * 1000000L);
frame.set_is_key(!!(alln->videoBacklog.front().flags & CUTIHI_BS_FLAG_KEY));
if(!alln->seg.AddGenericFrame(&frame)) puts("ADD FAIL");
alln->videoBacklog.pop();
}
return 1;
}
CUTIVIS CHiPubNode *CHi_MuxWebm() {
CHiMuxWebmNode *n = (CHiMuxWebmNode*) malloc(sizeof(*n));
n->pub.type = CUTIHI_T('CExp','Webm');
n->pub.Start = CHi_MuxWebm_Start;
n->pub.Perform = muxwebm_perform;
n->pub.Stop = CHi_MuxWebm_Stop;
n->pub.clean = 0;
n->pub.sinks = (CHiValue*) calloc(sizeof(*n->pub.sinks), n->pub.sinkCount = 3);
n->pub.sourceCount = 0;
n->pub.sources = NULL;
new (&n->audioBacklog) std::queue<CHiBSFrame>();
new (&n->videoBacklog) std::queue<CHiBSFrame>();
return &n->pub;
}
CUTIVIS int CHi_MuxWebm_Start(CHiPubNode *pubn) {
using namespace mkvmuxer;
CHiMuxWebmNode *alln = (CHiMuxWebmNode*) pubn;
new (&alln->w) MkvWriter{};
alln->w.Open(CHi_Crawl(&pubn->sinks[CUTIHI_MUXWEBM_IN_FILENAME])->data.text);
new (&alln->seg) Segment{};
alln->seg.Init(&alln->w);
alln->seg.AccurateClusterDuration(true);
alln->seg.UseFixedSizeClusterTimecode(false);
alln->seg.set_mode(Segment::kFile);
alln->seg.OutputCues(true);
alln->seg.set_duration(pubn->ng->duration * 1000);
alln->seg.GetSegmentInfo()->set_timecode_scale(1000000);
alln->seg.GetSegmentInfo()->set_writing_app("Cuticle");
/* Hack into first frame to get resolution */
CHiPubNode *evp9 = pubn->sinks[0].data.linked.to;
CHiImage *firstFrame = (CHiImage*) CHi_Crawl(&evp9->sinks[0])->data.sample;
alln->videoTrack = alln->seg.AddVideoTrack(firstFrame->width, firstFrame->height, 0);
VideoTrack *track = (VideoTrack*) alln->seg.GetTrackByNumber(alln->videoTrack);
track->set_codec_id(CHi_Crawl(&pubn->sinks[0])->type == CUTIHI_VAL_VP9BS ? Tracks::kVp9CodecId : Tracks::kVp8CodecId);
track->set_frame_rate(30);
Colour colourspace;
colourspace.set_matrix_coefficients(Colour::MatrixCoefficients::kBt709);
colourspace.set_range(Colour::Range::kBroadcastRange);
colourspace.set_transfer_characteristics(Colour::TransferCharacteristics::kIturBt709Tc);
colourspace.set_primaries(Colour::Primaries::kIturBt709P);
track->SetColour(colourspace);
alln->seg.CuesTrack(alln->videoTrack);
if(pubn->sinks[1].data.linked.to) {
struct __attribute__((packed)) {
uint32_t magic1;
uint32_t magic2;
uint8_t version;
uint8_t channels; // NUMBER OF CHANNELS IS HARDCODED TO ONE
uint16_t preskip;
uint32_t sampleRate;
uint16_t gain;
uint8_t family;
} header = {0x7375704f, 0x64616548, 1, 1, 3840, 48000, 0, 0};
alln->audioTrack = alln->seg.AddAudioTrack(48000, 1, 0);
AudioTrack *atrack = (AudioTrack*) alln->seg.GetTrackByNumber(alln->audioTrack);
atrack->set_codec_id(Tracks::kOpusCodecId);
atrack->set_seek_pre_roll(80000000);
atrack->SetCodecPrivate((const uint8_t*) &header, sizeof(header));
}
return 1;
}
CUTIVIS int CHi_MuxWebm_Stop(CHiPubNode *pubn) {
CHiMuxWebmNode *alln =(CHiMuxWebmNode*) pubn;
alln->seg.Finalize();
alln->w.Close();
return 1;
}

194
hi/window.c Normal file
View File

@ -0,0 +1,194 @@
#include"node.h"
#include<stdlib.h>
#include<X11/Xlib.h>
#include<X11/Xutil.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<X11/extensions/XShm.h>
#include<tmmintrin.h>
#include<smmintrin.h>
#include<time.h>
#include<string.h>
#include"img.h"
#include"linearity.h"
static Display *d;
static Window root;
static int find_window(Display *d, Window *w, const char *contains) {
if(contains) {
int found = 0;
Atom atom = XInternAtom(d, "_NET_CLIENT_LIST", 1);
Atom actualType;
int format;
unsigned long numItems, bytesAfter;
Window *list;
XTextProperty windowName;
int status = XGetWindowProperty(d, root, atom, 0L, ~0L, 0, AnyPropertyType, &actualType, &format, &numItems, &bytesAfter, (unsigned char**) &list);
if(status >= Success) {
for(int i = 0; i < numItems; i++) {
status = XGetWMName(d, list[i], &windowName);
if(status >= Success) {
if(windowName.value && strstr(windowName.value, contains)) {
*w = list[i];
found = 1;
break;
}
}
}
}
XFree(list);
return found;
} else {
*w = root;
return 1;
}
}
typedef struct {
CHiPubNode pub;
Window xcache;
XImage *ximg;
XShmSegmentInfo shminfo;
CHiImage *vcache;
} CHiWindowNode;
static int window_perform(CHiPubNode *n) {
CHiWindowNode *w = (CHiWindowNode*) n;
Window toshoot;
if(!find_window(d, &toshoot, CHi_Crawl(&n->sinks[0])->data.text)) return 0;
size_t stride;
if(!w->xcache || w->xcache != toshoot) {
w->xcache = toshoot;
XWindowAttributes attrs;
XGetWindowAttributes(d, toshoot, &attrs);
w->ximg = XShmCreateImage(d, attrs.visual, 32, ZPixmap, NULL, &w->shminfo, attrs.width, attrs.height);
stride = ((w->ximg->bytes_per_line + 15) & ~15);
w->shminfo.shmid = shmget(IPC_PRIVATE, stride * w->ximg->height, IPC_CREAT | 0777);
w->shminfo.shmaddr = w->ximg->data = shmat(w->shminfo.shmid, 0, 0);
w->shminfo.readOnly = False;
XShmAttach(d, &w->shminfo);
w->vcache = CHi_Image_New(2, 4, 8 * attrs.width, attrs.width, attrs.height, NULL);
} else {
stride = ((w->ximg->bytes_per_line + 15) & ~15);
}
XWindowAttributes toshootattrs;
XGetWindowAttributes(d, w->xcache, &toshootattrs);
XShmGetImage(d, w->xcache, w->ximg, 0, 0, AllPlanes);
// Turn u8 image to u16
#pragma omp parallel for
for(size_t y = 0; y < w->vcache->height; y++) {
for(size_t x = 0; x < w->vcache->width; x += 2) {
__m128i c = _mm_loadu_si128((__m128i*) ((uintptr_t) w->ximg->data + y * w->ximg->bytes_per_line + x * 4));
c = _mm_shuffle_epi8(c, _mm_set_epi8(7, -128, 6, -128, 5, -128, 4, -128, 3, -128, 2, -128, 1, -128, 0, -128));
c = apply_gamma_epi16(c, _mm_set_ps(1, 2.2f, 2.2f, 2.2f));
_mm_storeu_si128((__m128i*) ((uintptr_t) w->vcache->data16 + y * w->vcache->stride + x * 8), c);
}
}
n->sources[0].type = CUTIHI_VAL_SAMPLE;
n->sources[0].data.sample = w->vcache;
n->clean = 0;
return 1;
}
CUTIVIS CHiPubNode *CHi_Window() {
if(!d) {
d = XOpenDisplay(NULL);
root = RootWindow(d, DefaultScreen(d));
}
CHiWindowNode *n = malloc(sizeof(*n));
n->pub.type = CUTIHI_T('CWin','dow ');
n->pub.Start = n->pub.Stop = NULL;
n->pub.Perform = window_perform;
n->pub.clean = 0;
n->pub.sinkCount = 1;
n->pub.sinks = calloc(sizeof(*n->pub.sinks), 1);
n->pub.sourceCount = 1;
n->pub.sources = calloc(sizeof(*n->pub.sources), 1);
n->xcache = 0;
n->vcache = NULL;
return &n->pub;
}
// All of the following are ews
CUTIVIS size_t CHi_Window_GetSourceCount() {
Atom atom = XInternAtom(d, "_NET_CLIENT_LIST", 1);
Atom actualType;
int format;
unsigned long numItems, bytesAfter;
Window *list;
int status = XGetWindowProperty(d, root, atom, 0L, ~0L, 0, AnyPropertyType, &actualType, &format, &numItems, &bytesAfter, (unsigned char**) &list);
return status >= Success ? numItems : 0;
}
CUTIVIS const char *CHi_Window_GetSourceName(size_t idx) {
int found = 0;
Atom atom = XInternAtom(d, "_NET_CLIENT_LIST", 1);
Atom actualType;
int format;
unsigned long numItems, bytesAfter;
Window *list;
XTextProperty windowName;
int status = XGetWindowProperty(d, root, atom, 0L, ~0L, 0, AnyPropertyType, &actualType, &format, &numItems, &bytesAfter, (unsigned char**) &list);
if(status >= Success) {
status = XGetWMName(d, list[idx], &windowName);
if(status >= Success) {
found = 1;
}
}
return found ? strdup(windowName.value ? windowName.value : "") : NULL;
}
CUTIVIS uintptr_t CHi_Window_GetSourceData(size_t idx) {
Atom atom = XInternAtom(d, "_NET_CLIENT_LIST", 1);
Atom actualType;
int format;
unsigned long numItems, bytesAfter;
Window *list;
int status = XGetWindowProperty(d, root, atom, 0L, ~0L, 0, AnyPropertyType, &actualType, &format, &numItems, &bytesAfter, (unsigned char**) &list);
if(status >= Success) {
return list[idx];
}
return 0;
}
CUTIVIS size_t CHi_Window_GetNextSource(size_t i) {
return i + 1;
}

BIN
keyframe.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
keyframe_extrap.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
tlml.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
tlmo.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
tlmr.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

838
ui/frame.cpp Normal file
View File

@ -0,0 +1,838 @@
#include"frame.h"
#include<wx/menu.h>
#include<wx/dcclient.h>
#include<wx/colourdata.h>
#include<wx/colordlg.h>
#include<wx/filedlg.h>
#include<wx/textctrl.h>
#include<wx/wrapsizer.h>
#include<wx/stattext.h>
#include"timectrl.h"
#include<algorithm>
#include<hi/mode.h>
#include<memory>
#include<tmmintrin.h>
#include<wx/settings.h>
#include"hi/microphone.h"
#include<wx/choicdlg.h>
#include<wx/app.h>
#include<thread>
#include<chrono>
#include"timeline.h"
#include<functional>
#include<algorithm>
#define SSE_MATHFUN_WITH_CODE
#include<hi/kumb.h>
#include<hi/linearity.h>
Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystemSettings::GetMetric(wxSYS_SCREEN_X) / 2, wxSystemSettings::GetMetric(wxSYS_SCREEN_Y) / 2}) {
aui.SetManagedWindow(this);
viewer = new ImageViewer(this);
graph = new NodeGraph(this);
compsets = new CompositionSettings(this);
timeline = new Timeline(this);
auto info = wxAuiPaneInfo().Center().Caption("Preview").BestSize(GetSize().x, GetSize().y * 3 / 4);
aui.AddPane(viewer, info);
info = wxAuiPaneInfo().Bottom().Caption("Node Graph").BestSize(GetSize().x, GetSize().y * 1 / 4);
aui.AddPane(graph, info);
info = wxAuiPaneInfo().Right().Caption("Composition");
aui.AddPane(compsets, info);
info = wxAuiPaneInfo().Bottom().Row(2).Caption("Timeline");
aui.AddPane(timeline, info);
stba = CreateStatusBar();
tlba = CreateToolBar();
static wxBitmap modeImgs[] = {
[CUTIHI_MODE_LIVE] = wxBitmap{"tlml.bmp", wxBITMAP_TYPE_ANY},
[CUTIHI_MODE_OFFLINE] = wxBitmap{"tlmo.bmp", wxBITMAP_TYPE_ANY}
};
int modeTool = tlba->AddTool(wxID_ANY, "Mode", wxBitmap{"tlml.bmp", wxBITMAP_TYPE_ANY})->GetId();
CHi_SetMode(CUTIHI_MODE_LIVE);
tlba->Bind(wxEVT_COMMAND_TOOL_CLICKED, [=](wxCommandEvent &ev){
if(ev.GetId() == modeTool) {
CHi_SetMode((CHiMode) ((CHi_GetMode() + 1) % 2));
tlba->SetToolNormalBitmap(modeTool, modeImgs[CHi_GetMode()]);
}
});
tlba->Realize();
aui.SetFlags(wxAUI_MGR_LIVE_RESIZE | wxAUI_MGR_DEFAULT);
aui.Update();
Centre();
}
Frame::~Frame() {
aui.UnInit();
}
bool GrNode::MouseOverPort(wxPoint point, bool &source, int &i) {
if(point.y < 26 || point.x < 0 || point.x > GetSize().x) return false;
int p = (point.y - 26) / 20;
if((point.x >= 15 || p >= (int) sinks.size()) && (point.x < GetSize().x - 15 || p >= (int) sources.size())) return false;
int isSource = point.x >= GetSize().x - 10;
source = isSource;
i = p;
return true;
}
void GrNode::MakeKeyframe(int sinkIdx) {
auto ng = (NodeGraph*) GetParent();
CHi_MakeKeyframe(ng->backendNG, this->logical, sinkIdx);
((Frame*) ng->GetParent())->timeline->Refresh();
}
GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80}) {
Bind(wxEVT_PAINT, [this](wxPaintEvent &ev){
wxPaintDC dc(this);
dc.SetBrush(wxBrush{wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)});
dc.SetPen(HasFocus() ? *wxRED_PEN : *wxBLACK_PEN);
dc.DrawRoundedRectangle(5, 0, GetSize().x - 10, GetSize().y, 5);
dc.DrawText(name, GetSize().x / 2 - dc.GetTextExtent(name).x / 2, 2);
bool hoverIsSource;
int hoverI;
bool hover = MouseOverPort(ScreenToClient(wxGetMousePosition()), hoverIsSource, hoverI);
int y = 13;
int i = 0;
for(Port &p : sinks) {
wxColour col = p.type == GrNode::Port::Type::FILE_OPEN ? wxColour{255, 0, 0}
: p.type == GrNode::Port::Type::FILE_SAVE ? wxColour{255, 0, 0}
: p.type == GrNode::Port::Type::COLOR ? wxColour{0, 0, 255}
: p.type == GrNode::Port::Type::VEC2 ? wxColour{0, 255, 0}
: p.type == GrNode::Port::Type::TEXT ? wxColour{255, 255, 0}
: p.type == GrNode::Port::Type::WINDOW_SOURCE ? wxColour{255, 255, 0}
: p.type == GrNode::Port::Type::SAMPLE ? wxColour{252, 111, 255}
: p.type == GrNode::Port::Type::MIC_SOURCE ? wxColour{255, 255, 255}
: wxColour{128, 128, 128};
if(hover && !hoverIsSource && i == hoverI) {
col.Set(std::min(col.Red() + 50, 255), std::min(col.Green() + 50, 255), std::min(col.Blue() + 50, 255));
}
wxSize sz = dc.GetTextExtent(p.name);
dc.SetBrush(wxBrush{col});
dc.DrawText(p.name, 15, (y += 20) - sz.y / 2);
dc.DrawCircle(5, y, 5);
i++;
}
y = 13;
i = 0;
for(Port &p : sources) {
wxColour col = p.type == GrNode::Port::Type::FILE_OPEN ? wxColour{255, 0, 0}
: p.type == GrNode::Port::Type::FILE_SAVE ? wxColour{255, 0, 0}
: p.type == GrNode::Port::Type::COLOR ? wxColour{0, 0, 255}
: p.type == GrNode::Port::Type::VEC2 ? wxColour{0, 255, 0}
: p.type == GrNode::Port::Type::TEXT ? wxColour{255, 255, 0}
: p.type == GrNode::Port::Type::WINDOW_SOURCE ? wxColour{255, 255, 0}
: p.type == GrNode::Port::Type::SAMPLE ? wxColour{252, 111, 255}
: p.type == GrNode::Port::Type::MIC_SOURCE ? wxColour{255, 255, 255}
: wxColour{128, 128, 128};
if(hover && hoverIsSource && i == hoverI) {
col.Set(std::min(col.Red() + 50, 255), std::min(col.Green() + 50, 255), std::min(col.Blue() + 50, 255));
}
wxSize sz = dc.GetTextExtent(p.name);
dc.SetBrush(wxBrush{col});
dc.DrawText(p.name, GetSize().x - sz.x - 15, (y += 20) - sz.y / 2);
dc.DrawCircle(GetSize().x - 6, y, 5);
i++;
}
});
Bind(wxEVT_LEFT_DOWN, [parent, this](wxMouseEvent &ev){
SetFocus();
parent->Refresh();
bool isSource;
int p;
if(MouseOverPort(ev.GetPosition(), isSource, p)) {
if(parent->attacheeNode && parent->attacheePortIsSource != isSource) {
if(parent->attacheePortIsSource) {
parent->Alinken({this, p, parent->attacheeNode, parent->attacheePort});
} else {
parent->Alinken({parent->attacheeNode, parent->attacheePort, this, p});
}
parent->attacheeNode = NULL;
} else {
parent->attacheeNode = this;
parent->attacheePort = p;
parent->attacheePortIsSource = isSource;
}
} else {
CaptureMouse();
parent->attacheeNode = NULL;
parent->dragged = this;
parent->dragPos = ClientToScreen(ev.GetPosition());
}
});
Bind(wxEVT_MOTION, [parent, this](wxMouseEvent &ev){
if(wxGetMouseState().LeftIsDown()) {
if(HasCapture()) {
wxPoint neu = ClientToScreen(ev.GetPosition());
SetPosition(GetPosition() + neu - parent->dragPos);
parent->dragPos = neu;
}
}
parent->Refresh();
SetFocus();
});
Bind(wxEVT_LEFT_UP, [parent, this](wxMouseEvent &ev){
if(HasCapture()) {
ReleaseMouse();
parent->dragged = NULL;
}
});
Bind(wxEVT_LEFT_DCLICK, [parent, this](wxMouseEvent &ev){
if(ev.GetPosition().y >= 26) {
parent->attacheeNode = NULL;
int p = (ev.GetPosition().y - 26) / 20;
if(p >= (int) sinks.size()) return;
if(sinks[p].type == Port::Type::COLOR) {
wxColourData data;
data.SetChooseFull(true);
CHiValue *currentVal = CHi_Crawl(&this->logical->sinks[p]);
if(currentVal->type == CUTIHI_VAL_VEC4) {
data.SetColour({
(uint8_t) (currentVal->data.vec4[0] * 255.f),
(uint8_t) (currentVal->data.vec4[1] * 255.f),
(uint8_t) (currentVal->data.vec4[2] * 255.f),
(uint8_t) (currentVal->data.vec4[3] * 255.f),
});
}
wxColourDialog dlg(this, &data);
if(dlg.ShowModal() == wxID_OK) {
CHiValue newv;
newv.type = CUTIHI_VAL_VEC4;
newv.data.vec4[0] = dlg.GetColourData().GetColour().Red() / 255.f;
newv.data.vec4[1] = dlg.GetColourData().GetColour().Green() / 255.f;
newv.data.vec4[2] = dlg.GetColourData().GetColour().Blue() / 255.f;
newv.data.vec4[3] = dlg.GetColourData().GetColour().Alpha() / 255.f;
CHi_ConfigureSink(this->logical, p, newv);
parent->Dirtify(this);
}
} else if(sinks[p].type == Port::Type::FILE_OPEN) {
wxFileDialog dlg(this, wxFileSelectorPromptStr, wxEmptyString, wxEmptyString, wxFileSelectorDefaultWildcardStr, wxFD_OPEN | wxFD_PREVIEW);
if(dlg.ShowModal() == wxID_OK) {
CHiValue newv;
newv.type = CUTIHI_VAL_TEXT;
newv.data.text = strdup(dlg.GetPath().utf8_str());
CHi_ConfigureSink(this->logical, p, newv);
parent->Dirtify(this);
}
} else if(sinks[p].type == Port::Type::FILE_SAVE) {
wxFileDialog dlg(this, wxFileSelectorPromptStr, wxEmptyString, wxEmptyString, wxFileSelectorDefaultWildcardStr, wxFD_SAVE | wxFD_PREVIEW | wxFD_OVERWRITE_PROMPT);
if(dlg.ShowModal() == wxID_OK) {
CHiValue newv;
newv.type = CUTIHI_VAL_TEXT;
newv.data.text = strdup(dlg.GetPath().utf8_str());
CHi_ConfigureSink(this->logical, p, newv);
parent->Dirtify(this);
}
} else if(sinks[p].type >= Port::Type::VEC1 && sinks[p].type <= Port::Type::VEC4) {
auto ctrls = std::make_shared<std::vector<wxTextCtrl*>>();
for(int i = 0; i <= (int) sinks[p].type - (int) Port::Type::VEC1; i++) {
wxTextCtrl *tc = new wxTextCtrl(GetParent(), wxID_ANY, wxString::Format("%f", this->logical->sinks[p].data.vec4[i]), GetParent()->ScreenToClient(ClientToScreen({5 + 60 * i, (p + 1) * 20})));
tc->Bind(wxEVT_KEY_DOWN, [=](wxKeyEvent &ev){
if(ev.GetKeyCode() == WXK_RETURN) {
double d;
if(tc->GetValue().ToDouble(&d)) {
CHiValue newv = *CHi_Crawl(&this->logical->sinks[p]);
newv.type = CUTIHI_VAL_VEC4;
newv.data.vec4[i] = d;
CHi_ConfigureSink(this->logical, p, newv);
auto it = std::find(ctrls->begin(), ctrls->end(), tc);
ctrls->operator[]((it - ctrls->begin() + 1) % ctrls->size())->SetFocus();
ctrls->erase(it);
CallAfter([tc](){tc->Destroy();});
parent->Dirtify(this);
}
} else if(ev.GetKeyCode() == WXK_TAB) {
ctrls->operator[]((i + ctrls->size() + (wxGetKeyState(WXK_SHIFT) ? -1 : 1)) % ctrls->size())->SetFocus();
parent->Dirtify(this);
} else ev.Skip();
});
ctrls->push_back(tc);
}
ctrls->operator[](0)->SetFocus();
} else if(sinks[p].type == Port::Type::TEXT) {
wxTextCtrl *ctrl = new wxTextCtrl(GetParent(), wxID_ANY, this->logical->sinks[p].data.text, GetParent()->ScreenToClient(ClientToScreen({5, (p + 1) * 26})));
ctrl->SetValue(wxString{CHi_Crawl(&this->logical->sinks[p])->data.text});
ctrl->SetFocus();
ctrl->Bind(wxEVT_KILL_FOCUS, [=](wxFocusEvent &ev){
CHiValue newv;
newv.type = CUTIHI_VAL_TEXT;
char *c = (char*) malloc(ctrl->GetValue().Len() + 1);
memcpy(c, ctrl->GetValue().c_str(), ctrl->GetValue().Len() + 1);
newv.data.text = c;
CHi_ConfigureSink(this->logical, p, newv);
CallAfter([ctrl](){ctrl->Destroy();});
parent->Dirtify(this);
});
ctrl->Bind(wxEVT_KEY_DOWN, [=](wxKeyEvent &ev){
if(ev.GetKeyCode() == WXK_RETURN) {
CHiValue newv;
newv.type = CUTIHI_VAL_TEXT;
char *c = (char*) malloc(ctrl->GetValue().Len() + 1);
memcpy(c, ctrl->GetValue().c_str(), ctrl->GetValue().Len() + 1);
newv.data.text = c;
CHi_ConfigureSink(this->logical, p, newv);
CallAfter([ctrl](){ctrl->Destroy();});
parent->Dirtify(this);
} else ev.Skip();
});
} else if(sinks[p].type == Port::Type::MIC_SOURCE) {
std::vector<wxString> choices;
std::vector<void*> datae;
for(size_t i = CHi_Microphone_GetNextSource(-1); i < CHi_Microphone_GetSourceCount(); i = CHi_Microphone_GetNextSource(i)) {
choices.push_back(wxString::FromUTF8(CHi_Microphone_GetSourceName(i)));
datae.push_back((void*) (uintptr_t) i);
}
wxSingleChoiceDialog dlg(this, "", "Choose Source", choices.size(), choices.data(), datae.data());
if(dlg.ShowModal() == wxID_OK) {
CHiValue newv;
newv.type = CUTIHI_VAL_VEC4;
newv.data.vec4[0] = (size_t) (uintptr_t) dlg.GetSelectionData();
CHi_ConfigureSink(this->logical, p, newv);
parent->Dirtify(this);
}
} else if(sinks[p].type == Port::Type::WINDOW_SOURCE) {
std::vector<const char*> choicesOrig;
std::vector<wxString> choices;
for(size_t i = CHi_Window_GetNextSource(-1); i < CHi_Window_GetSourceCount(); i = CHi_Window_GetNextSource(i)) {
auto name = CHi_Window_GetSourceName(i);
choicesOrig.push_back(name);
choices.push_back(wxString::FromUTF8(name));
}
wxSingleChoiceDialog dlg(this, "", "Choose Source", choices.size(), choices.data(), (void**) nullptr);
if(dlg.ShowModal() == wxID_OK) {
CHiValue newv;
newv.type = CUTIHI_VAL_TEXT;
newv.data.text = strdup(choicesOrig[dlg.GetSelection()]);
CHi_ConfigureSink(this->logical, p, newv);
parent->Dirtify(this);
}
}
}
});
Bind(wxEVT_KEY_DOWN, [=](wxKeyEvent &ev){
if(ev.GetKeyCode() == 'I') {
bool isSource;
int p;
if(MouseOverPort(ev.GetPosition(), isSource, p) && !isSource) {
MakeKeyframe(p);
}
} else if(ev.GetKeyCode() == WXK_DELETE) {
bool isSource;
int p;
if(MouseOverPort(ev.GetPosition(), isSource, p)) {
CHiPubNode *daNode = this->logical;
int daPortIdx = p;
for(auto it = parent->links.begin(); it != parent->links.end(); it++) {
auto &link = *it;
if((isSource ? link.output : link.input) == this && (isSource ? link.o : link.i) == p) {
parent->links.erase(it);
if(isSource) {
daNode = link.input->logical;
daPortIdx = link.i;
isSource = false;
}
break;
}
}
if(!isSource) {
CHiValue val;
val.type = CUTIHI_VAL_NONE;
CHi_ConfigureSink(daNode, daPortIdx, val);
}
parent->Dirtify(this);
parent->Refresh();
}
}
});
}
GrNode::~GrNode() {
}
void GrNode::Fit() {
SetSize(GetSize().x, (std::max(sinks.size(), sources.size()) + 1) * 23);
}
ImageViewer::ImageViewer(Frame *f) : wxPanel(f, wxID_ANY) {
Bind(wxEVT_PAINT, [this](wxPaintEvent &ev){
if(bm.IsOk()) {
wxPaintDC dc(this);
dc.DrawBitmap(bm, pos);
}
});
Bind(wxEVT_MIDDLE_DOWN, [this](wxMouseEvent &ev){
CaptureMouse();
drag = ev.GetPosition();
});
Bind(wxEVT_MOTION, [this](wxMouseEvent &ev){
if(HasCapture()) {
pos += ev.GetPosition() - drag;
drag = ev.GetPosition();
Refresh();
}
});
Bind(wxEVT_MIDDLE_UP, [this](wxMouseEvent &ev){
if(HasCapture()) {
ReleaseMouse();
}
});
Bind(wxEVT_MOUSEWHEEL, [this](wxMouseEvent &ev){
img.SetData((unsigned char*) buf, bufW, bufH, true);
ResizeImage(siez + 25 * ev.GetWheelDelta() / ev.GetWheelRotation());
});
}
__attribute__((optimize("O3"))) static uint8_t *bgra64torgb24(uint8_t *orig, size_t stride, size_t w, size_t h) {
auto T0 = wxGetUTCTimeUSec();
uint8_t *ret = (uint8_t*) _mm_malloc(w * h * 3 + 16, 16);
#pragma omp parallel for
for(size_t y = 0; y < h; y++) {
uint8_t *temp = orig + stride * y;
uint8_t *dest = ret + 3 * w * y;
for(size_t x = 0; x < (w & ~15); x += 16, temp += 128, dest += 48) {
__m128i z[8] = {};
for(int zi = 0; zi < 8; zi++) {
z[zi] = _mm_loadu_si128((__m128i*) temp + zi);
z[zi] = apply_gamma_epi16(z[zi], _mm_set_ps(1, 1 / 2.2f, 1 / 2.2f, 1 / 2.2f));
}
__m128i a = _mm_shuffle_epi8(z[0], _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 9, 11, 13, 1, 3, 5));
__m128i b = _mm_shuffle_epi8(z[1], _mm_set_epi8(-128, -128, -128, -128, 9, 11, 13, 1, 3, 5, -128, -128, -128, -128, -128, -128));
__m128i c = _mm_shuffle_epi8(z[2], _mm_set_epi8(13, 1, 3, 5, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128));
__m128i d = _mm_shuffle_epi8(z[2], _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 9, 11));
__m128i e = _mm_shuffle_epi8(z[3], _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, 9, 11, 13, 1, 3, 5, -128, -128));
__m128i f = _mm_shuffle_epi8(z[4], _mm_set_epi8(-128, -128, 9, 11, 13, 1, 3, 5, -128, -128, -128, -128, -128, -128, -128, -128));
__m128i g = _mm_shuffle_epi8(z[5], _mm_set_epi8(3, 5, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128));
__m128i h = _mm_shuffle_epi8(z[5], _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 9, 11, 13, 1));
__m128i i = _mm_shuffle_epi8(z[6], _mm_set_epi8(-128, -128, -128, -128, -128, -128, 9, 11, 13, 1, 3, 5, -128, -128, -128, -128));
__m128i j = _mm_shuffle_epi8(z[7], _mm_set_epi8(9, 11, 13, 1, 3, 5, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128));
_mm_storeu_si128((__m128i*) dest + 0, _mm_or_si128(_mm_or_si128(a, b), c));
_mm_storeu_si128((__m128i*) dest + 1, _mm_or_si128(_mm_or_si128(_mm_or_si128(d, e), f), g));
_mm_storeu_si128((__m128i*) dest + 2, _mm_or_si128(_mm_or_si128(h, i), j));
}
for(size_t x = w & ~15; x < w; x++, temp += 8, dest += 3) {
uint64_t s = *(uint64_t*) temp;
dest[0] = powf(((s >> 40) & 0xFF) / 255.f, 1 / 2.2f) * 255.f;
dest[1] = powf(((s >> 24) & 0xFF) / 255.f, 1 / 2.2f) * 255.f;
dest[2] = powf(((s >> 8) & 0xFF) / 255.f, 1 / 2.2f) * 255.f;
}
}
auto T1 = wxGetUTCTimeUSec();
printf("%f\n", (T1 - T0).ToDouble() / 1000);
return ret;
}
void ImageViewer::SetImage(CHiImage *chim) {
if(!chim) return;
if(buf) _mm_free(buf);
buf = bgra64torgb24((uint8_t*) chim->data16, chim->stride, bufW = chim->width, bufH = chim->height);
img.SetData((unsigned char*) buf, chim->width, chim->height, true);
ResizeImage(siez);
}
void ImageViewer::ResizeImage(float size) {
siez = size;
img.Rescale(siez, (float) bufH / bufW * siez);
bm = wxBitmap(img);
Refresh();
}
NodeGraph::NodeGraph(Frame *f) : wxPanel(f, wxID_ANY) {
backendNG = CHi_NewNodeGraph();
backendNG->ud = f;
{
GrNode *v = new GrNode(this);
v->logical = CHi_Preview();
v->name = "Preview";
v->sinks = {{"Video", GrNode::Port::Type::SAMPLE}};
CHi_RegisterNode(backendNG, v->logical);
gnodes.push_back(v);
}
Bind(wxEVT_CONTEXT_MENU, [this, f](wxContextMenuEvent &ev){
wxMenu menu;
int idConstant = menu.Append(wxID_ANY, "Constant", "")->GetId();
int idImage = menu.Append(wxID_ANY, "Image", "")->GetId();
int idMovie = menu.Append(wxID_ANY, "Movie", "")->GetId();
int idWindow = menu.Append(wxID_ANY, "Window", "")->GetId();
int idText = menu.Append(wxID_ANY, "Text", "")->GetId();
int idMicrophone = menu.Append(wxID_ANY, "Microphone", "")->GetId();
int idMixer = menu.Append(wxID_ANY, "Mixer", "")->GetId();
int idCamera = menu.Append(wxID_ANY, "Live Digital Camera", "")->GetId();
int idTime = menu.Append(wxID_ANY, "Time", "")->GetId();
int idEmbed = menu.Append(wxID_ANY, "Embed", "")->GetId();
int idComponentScale = menu.Append(wxID_ANY, "Scale", "")->GetId();
int idModulate = menu.Append(wxID_ANY, "Modulate", "")->GetId();
int idKeyhook = menu.Append(wxID_ANY, "Keyhook (Live)", "")->GetId();
int idEncodeVp8 = menu.Append(wxID_ANY, "Encode VP8", "")->GetId();
int idEncodeVp9 = menu.Append(wxID_ANY, "Encode VP9", "")->GetId();
int idEncodeOpus = menu.Append(wxID_ANY, "Encode Opus", "")->GetId();
int idMuxWebm = menu.Append(wxID_ANY, "Mux WebM", "")->GetId();
int idMuxWav = menu.Append(wxID_ANY, "Muv Wav", "")->GetId();
wxPoint position = ScreenToClient(wxGetMousePosition());
menu.Bind(wxEVT_MENU, [=](wxCommandEvent &ev){
std::function<void()> after = [](){};
GrNode *noed = nullptr;
if(ev.GetId() == idConstant) {
noed = new GrNode(this);
noed->logical = CHi_ConstantSample();
printf("%p\n", noed->logical->sinks[0].data.vec4);
noed->name = "Constant";
noed->sinks = {{"Color", GrNode::Port::Type::COLOR}};
noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(ev.GetId() == idImage) {
noed = new GrNode(this);
noed->logical = CHi_Image();
noed->name = "Image";
noed->sinks = {{"Filepath", GrNode::Port::Type::FILE_OPEN}};
noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(ev.GetId() == idEmbed) {
noed = new GrNode(this);
noed->logical = CHi_Embed();
noed->name = "Embed";
noed->sinks = {
{"Frame", GrNode::Port::Type::SAMPLE},
{"Sub 1", GrNode::Port::Type::SAMPLE}, {" Pos", GrNode::Port::Type::VEC2}, {" Size", GrNode::Port::Type::VEC1},
{"Sub 2", GrNode::Port::Type::SAMPLE}, {" Pos", GrNode::Port::Type::VEC2}, {" Size", GrNode::Port::Type::VEC1},
{"Sub 3", GrNode::Port::Type::SAMPLE}, {" Pos", GrNode::Port::Type::VEC2}, {" Size", GrNode::Port::Type::VEC1}
};
noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(ev.GetId() == idModulate) {
noed = new GrNode(this);
noed->logical = CHi_Modulate();
noed->name = "Modulate";
noed->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}, {"Brightness", GrNode::Port::Type::VEC1}, {"Contrast", GrNode::Port::Type::VEC1}, {"Hue", GrNode::Port::Type::VEC1}};
noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(ev.GetId() == idMovie) {
noed = new GrNode(this);
noed->logical = CHi_Movie();
noed->name = "Movie";
noed->sinks = {{"Filepath", GrNode::Port::Type::FILE_OPEN}, {"Time", GrNode::Port::Type::VEC1}};
noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}, {"Audio", GrNode::Port::Type::SAMPLE}};
after = [=](){
size_t portIdx = std::distance(noed->sinks.begin(), std::find_if(noed->sinks.begin(), noed->sinks.end(), [](GrNode::Port& p) -> bool {
return p.name == "Time";
}));
CHi_MakeKeyframe(backendNG, noed->logical, portIdx);
float params[4] = {1};
CHi_SetExtrapolationMode(backendNG, noed->logical, portIdx, CUTIHI_EXTRAPOLATION_CONSTANT, params);
};
} else if(ev.GetId() == idEncodeVp9) {
noed = new GrNode(this);
noed->logical = CHi_EncodeVP9();
noed->name = "Encode VP9";
noed->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}};
noed->sources = {{"Bitstream"}};
} else if(ev.GetId() == idEncodeVp8) {
noed = new GrNode(this);
noed->logical = CHi_EncodeVP8();
noed->name = "Encode VP8";
noed->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}};
noed->sources = {{"Bitstream"}};
} else if(ev.GetId() == idMuxWebm) {
noed = new GrNode(this);
noed->logical = CHi_MuxWebm();
noed->name = "Mux WebM";
noed->sinks = {{"Video Bitstream"}, {"Audio Bitstream"}, {"Filename", GrNode::Port::Type::FILE_SAVE}};
noed->sources = {};
} else if(ev.GetId() == idWindow) {
noed = new GrNode(this);
noed->logical = CHi_Window();
noed->name = "Window";
noed->sinks = {{"Name", GrNode::Port::Type::WINDOW_SOURCE}};
noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(ev.GetId() == idText) {
noed = new GrNode(this);
noed->logical = CHi_Text();
noed->name = "Text";
noed->sinks = {{"Text", GrNode::Port::Type::TEXT}, {"Color", GrNode::Port::Type::COLOR}, {"DPI", GrNode::Port::Type::VEC1}};
noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(ev.GetId() == idTime) {
noed = new GrNode(this);
noed->logical = CHi_Time();
noed->name = "Time";
noed->sinks = {};
noed->sources = {{"t", GrNode::Port::Type::VEC1}};
} else if(ev.GetId() == idMicrophone) {
noed = new GrNode(this);
noed->logical = CHi_Microphone();
noed->name = "Microphone";
noed->sinks = {{"Source", GrNode::Port::Type::MIC_SOURCE}};
noed->sources = {{"Audio", GrNode::Port::Type::SAMPLE}};
} else if(ev.GetId() == idMixer) {
noed = new GrNode(this);
noed->logical = CHi_Mixer();
noed->name = "Mixer";
noed->sinks = {{"Sink 1", GrNode::Port::Type::SAMPLE}, {"Sink 2", GrNode::Port::Type::SAMPLE}};
noed->sources = {{"Audio", GrNode::Port::Type::SAMPLE}};
} else if(ev.GetId() == idMuxWav) {
noed = new GrNode(this);
noed->logical = CHi_ExportWav();
noed->name = "Mux Wav";
noed->sinks = {{"Filename", GrNode::Port::Type::FILE_SAVE}, {"Audio", GrNode::Port::Type::SAMPLE}};
noed->sources = {};
} else if(ev.GetId() == idEncodeOpus) {
noed = new GrNode(this);
noed->logical = CHi_EncodeOpus();
noed->name = "Encode Opus";
noed->sinks = {{"Audio", GrNode::Port::Type::SAMPLE}};
noed->sources = {{"Bitstream"}};
} else if(ev.GetId() == idCamera) {
noed = new GrNode(this);
noed->logical = CHi_Camera();
noed->name = "Live Digital Camera";
noed->sinks = {};
noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(ev.GetId() == idComponentScale) {
noed = new GrNode(this);
noed->logical = CHi_ComponentScale();
noed->name = "Scale";
noed->sinks = {{"Vector", GrNode::Port::Type::VEC4}, {"Sample", GrNode::Port::Type::SAMPLE}};
noed->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
} else if(ev.GetId() == idKeyhook) {
noed = new GrNode(this);
noed->logical = CHi_Keyhook();
noed->name = "Keyhook";
noed->sinks = {{"Key", GrNode::Port::Type::VEC1}, {"Smooth Time", GrNode::Port::Type::VEC1}};
noed->sources = {{"Bool", GrNode::Port::Type::VEC1}};
}
if(noed) {
noed->Fit();
noed->SetPosition(position);
CHi_RegisterNode(backendNG, noed->logical);
gnodes.push_back(noed);
after();
}
});
PopupMenu(&menu);
});
Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &ev){
SetFocusIgnoringChildren();
Refresh();
});
Bind(wxEVT_LEFT_UP, [this](wxMouseEvent &ev){
if(attacheeNode) attacheeNode = NULL;
});
Bind(wxEVT_MIDDLE_DOWN, [this](wxMouseEvent &ev){
dragged = nullptr;
dragPos = ClientToScreen(ev.GetPosition());
CaptureMouse();
});
Bind(wxEVT_MIDDLE_UP, [this](wxMouseEvent &ev){
if(HasCapture()) {
ReleaseMouse();
}
});
Bind(wxEVT_MOTION, [this](wxMouseEvent &ev) {
Refresh();
if(HasCapture()) {
wxPoint neu = ClientToScreen(ev.GetPosition());
for(auto gr : gnodes) {
gr->SetPosition(gr->GetPosition() + neu - dragPos);
}
dragPos = neu;
}
});
Bind(wxEVT_PAINT, [this](wxPaintEvent &ev) {
wxPaintDC dc(this);
wxPoint p[2];
for(Link l : links) {
p[0] = l.input->GetPosition() + wxPoint{0, 33 + 20 * l.i};
p[1] = l.output->GetPosition() + wxPoint{l.output->GetSize().x, 33 + 20 * l.o};
dc.DrawSpline(2, p);
}
if(attacheeNode) {
p[0] = attacheeNode->GetPosition() + wxPoint{attacheePortIsSource ? attacheeNode->GetSize().x : 0, 33 + 20 * attacheePort};
p[1] = ScreenToClient(wxGetMousePosition());
dc.DrawSpline(2, p);
}
});
}
NodeGraph::~NodeGraph() {
}
void NodeGraph::Alinken(Link l) {
CHiValue newv;
newv.type = CUTIHI_VAL_LINKED;
newv.data.linked.to = l.output->logical;
newv.data.linked.idx = l.o;
if(!CHi_ConfigureSink(l.input->logical, l.i, newv)) {
((Frame*) GetParent())->stba->SetStatusText("Uh oh! Hur-hur, there's a WAACKY cycle! Can't do, sorry friend.");
return;
}
for(auto it = links.begin(); it != links.end(); it++) {
if((*it).input == l.input && (*it).i == l.i) {
links.erase(it);
break;
}
}
links.push_back(l);
Dirtify(l.input);
Refresh();
}
void NodeGraph::Dirtify(GrNode *g) {
g->logical->clean = 0;
for(auto &it : links) {
if(it.output == g) {
Dirtify(it.input);
}
}
if(g == gnodes[0]) {
if(CHi_Hysteresis(g->logical)) {
CHiValue *val = CHi_Crawl(&g->logical->sinks[0]);
if(val->type == CUTIHI_VAL_SAMPLE && val->data.sample) {
((Frame*) GetParent())->viewer->SetImage(val->data.sample);
}
}
}
}
bool operator==(const NodeGraph::Link &l, const NodeGraph::Link &r) {
return l.input == r.input && l.i == r.i && l.output == r.output && l.o == r.o;
}
static bool dfs(NodeGraph *ng, std::set<GrNode*> &p, GrNode *g) {
p.insert(g);
bool cyclic = false;
for(const NodeGraph::Link &l : ng->links) {
if(l.output == g && (std::find(p.begin(), p.end(), g) != p.end() || dfs(ng, p, l.input))) {
cyclic = true;
break;
}
}
p.erase(std::find(p.begin(), p.end(), g));
return cyclic;
}
bool NodeGraph::DetectCycles(GrNode *root) {
std::set<GrNode*> p{};
return dfs(this, p, root);
}
CompositionSettings::CompositionSettings(Frame *parent) : wxPanel(parent, wxID_ANY) {
auto sz = new wxBoxSizer(wxVERTICAL);
sz->Add(this->durationEnable = new wxCheckBox(this, wxID_ANY, "Duration"), 0, wxALIGN_CENTER);
sz->Add(this->duration = new ctTimeCtrl(this, 120), 0, wxEXPAND);
durationEnable->SetValue(true);
durationEnable->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent&){
duration->Enable(durationEnable->GetValue());
});
sz->Add(this->btnPerform = new wxButton(this, wxID_ANY, "Compile"), 0, wxEXPAND);
btnPerform->Bind(wxEVT_BUTTON, [=](wxCommandEvent &ev){
if(btnPerform->GetLabel() == "Kill") {
CHi_StopCompilation(parent->graph->backendNG);
btnPerform->Disable();
} else {
CHi_SetDuration(parent->graph->backendNG, durationEnable->IsChecked() ? duration->GetSeconds() : -1);
CHi_BeginCompilation(parent->graph->backendNG);
btnPerform->SetLabel("Kill");
std::thread{[=](){
while(parent->graph->backendNG->compilationStatus == CUTIHI_COMP_RUNNING) {
parent->CallAfter([=](){
float t = CHi_Time_Get(parent->graph->backendNG);
parent->stba->SetStatusText(wxString::Format("%02i:%02i:%06.03fs", (int) (t / 3600), (int) (t / 60), fmodf(t, 60)));
});
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
parent->CallAfter([=](){
parent->stba->SetStatusText("Compilation has ended.");
});
}}.detach();
}
});
parent->graph->backendNG->eventOnStopComplete = +[](CHiNodeGraph *ng){
wxTheApp->CallAfter([ng](){
wxButton *btn = ((Frame*) ng->ud)->compsets->btnPerform;
btn->Enable();
btn->SetLabel("Compile");
});
};
SetSizerAndFit(sz);
}

131
ui/frame.h Normal file
View File

@ -0,0 +1,131 @@
#ifndef _CUTICLE_FRAME_H
#define _CUTICLE_FRAME_H
#include<vector>
#include<set>
#include<wx/frame.h>
#include<wx/panel.h>
#include<wx/aui/aui.h>
#include<wx/button.h>
#include<wx/spinctrl.h>
#include<wx/statusbr.h>
#include<wx/toolbar.h>
#include<wx/checkbox.h>
#include<hi/node.h>
#include<hi/img.h>
struct NodeGraph;
struct ImageViewer;
struct CompositionSettings;
struct Timeline;
struct Frame : wxFrame {
wxAuiManager aui;
ImageViewer *viewer;
NodeGraph *graph;
CompositionSettings *compsets;
Timeline *timeline;
wxStatusBar *stba;
wxToolBar *tlba;
Frame();
virtual ~Frame();
};
struct GrNode : wxPanel {
struct Port {
wxString name;
enum class Type {
NONE, FILE_OPEN, COLOR, VEC1, VEC2, VEC3, VEC4, TEXT, SAMPLE, FILE_SAVE, MIC_SOURCE, WINDOW_SOURCE
} type;
};
std::vector<Port> sinks;
std::vector<Port> sources;
wxString name;
CHiPubNode *logical;
GrNode(NodeGraph*);
virtual ~GrNode();
bool MouseOverPort(wxPoint p, bool &source, int &i);
void MakeKeyframe(int i);
virtual void Fit() override;
};
struct ctTimeCtrl;
struct CompositionSettings : wxPanel {
ctTimeCtrl *duration;
wxCheckBox *durationEnable;
wxButton *btnPerform;
CompositionSettings(Frame*);
virtual ~CompositionSettings() = default;
};
struct ImageViewer : wxPanel {
wxPoint pos;
wxImage img;
wxBitmap bm;
size_t bufW, bufH;
uint8_t *buf = nullptr;
float siez = 512;
wxPoint drag;
ImageViewer(Frame*);
virtual ~ImageViewer() = default;
void SetImage(CHiImage *img);
void ResizeImage(float);
};
struct NodeGraph : wxPanel {
struct Link {
GrNode *input;
int i;
GrNode *output;
int o;
struct Comparator {
bool operator ()(const Link &a, const Link &b) {
if(a.input != b.input) return a.input < b.input;
else if(a.i != b.i) return a.i < b.i;
else if(a.output != b.output) return a.output < b.output;
else return a.o < b.o;
}
};
};
GrNode *attacheeNode = NULL;
int attacheePort;
int attacheePortIsSource;
GrNode *dragged = NULL;
wxPoint dragPos;
CHiNodeGraph *backendNG = NULL;
std::vector<GrNode*> gnodes;
std::vector<Link> links;
NodeGraph(Frame*);
virtual ~NodeGraph();
void Alinken(Link l);
void Dirtify(GrNode *g);
bool DetectCycles(GrNode*);
};
#endif

12
ui/main.cpp Normal file
View File

@ -0,0 +1,12 @@
#include<wx/wx.h>
#include"frame.h"
struct App : wxApp {
virtual bool OnInit() {
(new Frame())->Show(true);
return true;
}
};
wxIMPLEMENT_APP(App);

46
ui/textctrl.cpp Normal file
View File

@ -0,0 +1,46 @@
#include"timectrl.h"
ctTimeCtrl::ctTimeCtrl(wxWindow *parent, double seconds) : wxTextCtrl(parent, wxID_ANY) {
SetSeconds(seconds);
Bind(wxEVT_CHAR, [this](wxKeyEvent& ev){
if(ev.GetKeyCode() == WXK_LEFT || ev.GetKeyCode() == WXK_RIGHT) ev.Skip();
else if(ev.GetKeyCode() >= '0' && ev.GetKeyCode() <= '9') {
uint32_t c = GetValue()[GetInsertionPoint()].GetValue();
if(c >= '0' && c <= '9') {
// SetValue calls EVT_TEXT
wxString neu = GetValue().Clone();
neu.SetChar(GetInsertionPoint(), ev.GetUnicodeKey());
SetValue(neu);
CallAfter([this](){
SetInsertionPoint(GetInsertionPoint() + 1);
});
}
} else if(GetInsertionPoint() < (long) GetValue().Length() && GetValue()[GetInsertionPoint()].GetValue() == ev.GetUnicodeKey()) {
CallAfter([this](){
SetInsertionPoint(GetInsertionPoint() + 1);
});
}
});
Bind(wxEVT_TEXT, [this](wxCommandEvent &ev){
long h, m;
double s;
GetValue().ToCLong(&h);
GetValue().Mid(3).ToCLong(&m);
GetValue().Mid(6).ToCDouble(&s);
this->seconds = (h * 60 + m) * 60 + s;
});
}
double ctTimeCtrl::GetSeconds() {
return seconds;
}
void ctTimeCtrl::SetSeconds(double seconds) {
this->seconds = seconds;
ChangeValue(wxString::Format("%02i:%02i:%02.3g", (int) (seconds / 3600), (int) (seconds / 60), fmodf(seconds, 60)));
}

14
ui/timectrl.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include<wx/textctrl.h>
struct ctTimeCtrl : wxTextCtrl {
ctTimeCtrl(wxWindow *parent, double seconds);
virtual ~ctTimeCtrl() = default;
double GetSeconds();
void SetSeconds(double);
private:
double seconds;
};

273
ui/timeline.cpp Normal file
View File

@ -0,0 +1,273 @@
#include"timeline.h"
#include<wx/menu.h>
#include<wx/dcclient.h>
#include<wx/settings.h>
#include<algorithm>
#include<float.h>
#include"frame.h"
static wxBitmap bmpKf;
static wxBitmap bmpKfExtrap;
template<typename T>
static T mod(T a, T b) {
return (a % b + b) % b;
}
#define ZERO_TIME_BASE 128
bool Timeline::MouseOverKF(wxPoint p, size_t &kfsIdxRet, size_t &kfIdxRet) {
auto f = (Frame*) GetParent();
int kfsIdx = p.y / bmpKf.GetHeight() - 1;
if(kfsIdx < 0 || kfsIdx >= f->graph->backendNG->keyframesList.count) {
return false;
}
float t = (p.x + camX - ZERO_TIME_BASE) / (float) scale;
float threshold = bmpKf.GetWidth() / (float) scale / 2;
size_t idx = CHi_GetClosestKeyframe(f->graph->backendNG, kfsIdx, t);
if(fabs(f->graph->backendNG->keyframesList.keyframes[kfsIdx]->times[idx] - t) > threshold) {
return false;
}
kfsIdxRet = kfsIdx;
kfIdxRet = idx;
return true;
}
Timeline::Timeline(struct Frame *parent) : wxPanel(parent, wxID_ANY) {
bmpKf = wxBitmap{"keyframe.bmp", wxBITMAP_TYPE_ANY};
bmpKfExtrap = wxBitmap{"keyframe_extrap.bmp", wxBITMAP_TYPE_ANY};
Bind(wxEVT_PAINT, &Timeline::Paint, this);
Bind(wxEVT_MIDDLE_DOWN, [=](wxMouseEvent &ev){
captureMode = Timeline::CaptureMode::CAM;
CaptureMouse();
mouseX = ev.GetX();
});
Bind(wxEVT_MIDDLE_UP, [=](wxMouseEvent &ev){
if(HasCapture() && captureMode == Timeline::CaptureMode::CAM) {
ReleaseMouse();
}
});
Bind(wxEVT_LEFT_DOWN, [=](wxMouseEvent &ev){
auto f = (Frame*) GetParent();
size_t kfsIdx, kfIdx;
if(MouseOverKF(ev.GetPosition(), kfsIdx, kfIdx)) {
captureMode = Timeline::CaptureMode::KF;
CaptureMouse();
mouseX = ev.GetX();
this->captureKfsIdx = kfsIdx;
this->captureKfIdx = kfIdx;
} else {
float t = (ev.GetX() + camX - ZERO_TIME_BASE) / (float) scale;
// Snap to closest keyframe, in all keyframes
if(ev.ControlDown()) {
float minDist = FLT_MAX;
float newT = t;
for(size_t kfsIdx = 0; kfsIdx < f->graph->backendNG->keyframesList.count; kfsIdx++) {
size_t kfIdx = CHi_GetClosestKeyframe(f->graph->backendNG, kfsIdx, t);
float dist = fabs(f->graph->backendNG->keyframesList.keyframes[kfsIdx]->times[kfIdx] - t);
if(dist < minDist) {
minDist = dist;
newT = f->graph->backendNG->keyframesList.keyframes[kfsIdx]->times[kfIdx];
}
}
t = newT;
}
CHi_Time_Set(f->graph->backendNG, t < 0 ? 0 : t);
Refresh();
f->graph->Dirtify(f->graph->gnodes[0]);
}
});
Bind(wxEVT_LEFT_UP, [=](wxMouseEvent &ev){
if(HasCapture() && captureMode == Timeline::CaptureMode::KF) {
ReleaseMouse();
}
});
Bind(wxEVT_MOTION, [=](wxMouseEvent &ev){
auto f = (Frame*) GetParent();
if(HasCapture()) {
if(captureMode == Timeline::CaptureMode::CAM) {
camX += mouseX - ev.GetX();
if(camX < 0) {
camX = 0;
}
Refresh();
} else if(captureMode == Timeline::CaptureMode::KF) {
int64_t diff = ev.GetX() - mouseX;
float timeDiff = (float) diff / this->scale;
captureKfIdx = CHi_MoveKeyframeBy(f->graph->backendNG, f->graph->backendNG->keyframesList.keyframes[captureKfsIdx], captureKfIdx, timeDiff);
Refresh();
f->graph->Dirtify(f->graph->gnodes[0]);
}
mouseX = ev.GetX();
} else {
// This is really baad..
size_t kfsIdx, kfIdx;
if(GetToolTipText() == "" && MouseOverKF(ScreenToClient(wxGetMousePosition()), kfsIdx, kfIdx)) {
CHiKeyframes *kfs = f->graph->backendNG->keyframesList.keyframes[kfsIdx];
CHiPubNode *node = kfs->node;
auto it = std::find_if(f->graph->gnodes.begin(), f->graph->gnodes.end(), [=](GrNode *g){
return g->logical == node;
});
assert(it != f->graph->gnodes.end());
auto &sinks = (*it)->sinks;
for(size_t i = 0; i < node->sinkCount; i++) {
if(node->sinks[i].type == CUTIHI_VAL_KEYED && node->sinks[i].data.keyed == kfs) {
CHiValueRaw *val = &node->sinks[i].data.keyed->values[kfIdx];
switch(sinks[i].type) {
case GrNode::Port::Type::TEXT:
case GrNode::Port::Type::FILE_OPEN:
case GrNode::Port::Type::FILE_SAVE:
SetToolTip(wxString{val->text});
break;
case GrNode::Port::Type::VEC1:
SetToolTip(wxString::Format("%g", val->vec4[0]));
break;
case GrNode::Port::Type::VEC2:
SetToolTip(wxString::Format("(%g, %g)", val->vec4[0], val->vec4[1]));
break;
case GrNode::Port::Type::VEC3:
SetToolTip(wxString::Format("(%g, %g, %g)", val->vec4[0], val->vec4[1], val->vec4[2]));
break;
case GrNode::Port::Type::VEC4:
case GrNode::Port::Type::COLOR:
SetToolTip(wxString::Format("(%g, %g, %g, %g)", val->vec4[0], val->vec4[1], val->vec4[2], val->vec4[3]));
break;
default:
SetToolTip(" ");
}
break;
}
}
} else {
SetToolTip(nullptr);
}
}
});
Bind(wxEVT_MOUSEWHEEL, [=](wxMouseEvent &ev){
int delta = ev.GetWheelRotation() / ev.GetWheelDelta();
while(delta > 0) {
scale *= 2;
if(scale > 800) {
scale = 800;
}
delta--;
}
while(delta < 0) {
scale /= 2;
if(scale < 25) {
scale = 25;
}
delta++;
}
Refresh();
});
Bind(wxEVT_CONTEXT_MENU, [=](wxContextMenuEvent &ev){
wxPoint position = ScreenToClient(wxGetMousePosition());
size_t kfsIdx, kfIdx;
if(MouseOverKF(ScreenToClient(wxGetMousePosition()), kfsIdx, kfIdx)) {
wxMenu menu;
int idDel = menu.Append(wxID_ANY, "Delete")->GetId();
menu.Bind(wxEVT_MENU, [=](wxCommandEvent &ev){
if(ev.GetId() == idDel) {
auto f = (Frame*) GetParent();
auto kfs = f->graph->backendNG->keyframesList.keyframes[kfsIdx];
CHi_DeleteKeyframe(f->graph->backendNG, kfs, kfIdx);
Refresh();
}
});
PopupMenu(&menu);
}
});
SetMinSize(wxSize{0, 64});
//SetSize(wxSize{GetSize().x, std::max(GetMinSize().y, GetSize().y)});
Fit();
}
void Timeline::Paint(wxPaintEvent &ev) {
auto frame = (Frame*) GetParent();
wxPaintDC dc{this};
dc.SetPen(wxPen{wxColour{160, 60, 60}});
{
int x = CHi_Time_Get(frame->graph->backendNG) * scale - camX + ZERO_TIME_BASE;
dc.DrawLine(x, 0, x, GetSize().y);
}
dc.SetPen(wxPen{wxSystemSettings::GetColour(wxSYS_COLOUR_INACTIVECAPTIONTEXT)});
float t = std::ceil((float) camX / scale);
for(int64_t x = ZERO_TIME_BASE + mod<int64_t>(-camX, scale); x < GetSize().x; x += scale) {
dc.DrawLine(x, 0, x, 10);
dc.DrawText(wxString::Format("%gs", t), x + 4, 0);
t++;
}
auto kfsList = &frame->graph->backendNG->keyframesList;
for(size_t kfsIdx = 0; kfsIdx < kfsList->count; kfsIdx++) {
CHiKeyframes *kfs = kfsList->keyframes[kfsIdx];
for(size_t kfIdx = 0; kfIdx < kfs->count; kfIdx++) {
wxBitmap &bmp = kfIdx == kfs->count - 1 && kfs->extrapolationMode != CUTIHI_EXTRAPOLATION_NONE ? bmpKfExtrap : bmpKf;
dc.DrawBitmap(bmp, ZERO_TIME_BASE - camX + scale * kfs->times[kfIdx] - bmpKf.GetWidth() / 2, bmpKf.GetHeight() * (kfsIdx + 1));
}
}
}

30
ui/timeline.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef _CUTICLE_TIMELINE_H
#define _CUTICLE_TIMELINE_H
#include<wx/frame.h>
#include<wx/panel.h>
struct Frame;
struct Timeline : wxPanel {
int64_t camX = 0;
int64_t mouseX = 0;
size_t captureKfsIdx, captureKfIdx;
enum class CaptureMode {
CAM, KF
} captureMode;
int scale = 100;
Timeline(struct Frame *parent);
virtual ~Timeline() = default;
void Paint(wxPaintEvent&);
bool MouseOverKF(wxPoint p, size_t &kfsIdx, size_t &kfIdx);
};
#endif