Move to Git
This commit is contained in:
commit
18da1dabcd
24
Makefile
Normal file
24
Makefile
Normal 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
26
hi/bs.h
Normal 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
3
hi/defs.h
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define CUTIVIS __attribute__((visibility("default")))
|
39
hi/img.c
Normal file
39
hi/img.c
Normal 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
29
hi/img.h
Normal 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
|
114
hi/linearity.h
Normal file
114
hi/linearity.h
Normal 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
19
hi/loopback.c
Normal 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(¶ms));
|
||||||
|
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
209
hi/microphone.c
Normal 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, ¶ms, 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
18
hi/microphone.h
Normal 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
11
hi/mode.c
Normal 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
19
hi/mode.h
Normal 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
899
hi/node.c
Normal 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
268
hi/node.h
Normal 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
85
hi/opus.c
Normal 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
132
hi/relay.c
Normal 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
14
hi/relay.h
Normal 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
43
hi/test.c
Normal 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
137
hi/webcam.c
Normal 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
418
hi/webmdec.cpp
Normal 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
431
hi/webmenc.cpp
Normal 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
194
hi/window.c
Normal 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
BIN
keyframe.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
keyframe_extrap.bmp
Normal file
BIN
keyframe_extrap.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
838
ui/frame.cpp
Normal file
838
ui/frame.cpp
Normal 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
131
ui/frame.h
Normal 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
12
ui/main.cpp
Normal 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
46
ui/textctrl.cpp
Normal 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
14
ui/timectrl.h
Normal 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
273
ui/timeline.cpp
Normal 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
30
ui/timeline.h
Normal 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
|
Loading…
Reference in New Issue
Block a user