commit 18da1dabcd0abf436dfc65f86260115e06f4158a Author: mid <> Date: Wed Jun 26 20:47:26 2024 +0300 Move to Git diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9b99b26 --- /dev/null +++ b/Makefile @@ -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` diff --git a/hi/bs.h b/hi/bs.h new file mode 100644 index 0000000..fdaa4a1 --- /dev/null +++ b/hi/bs.h @@ -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 \ No newline at end of file diff --git a/hi/defs.h b/hi/defs.h new file mode 100644 index 0000000..4554737 --- /dev/null +++ b/hi/defs.h @@ -0,0 +1,3 @@ +#pragma once + +#define CUTIVIS __attribute__((visibility("default"))) \ No newline at end of file diff --git a/hi/img.c b/hi/img.c new file mode 100644 index 0000000..3777f10 --- /dev/null +++ b/hi/img.c @@ -0,0 +1,39 @@ +#include"img.h" + +#include +#include + +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); + } +} diff --git a/hi/img.h b/hi/img.h new file mode 100644 index 0000000..fccc685 --- /dev/null +++ b/hi/img.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#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 diff --git a/hi/kumb.h b/hi/kumb.h new file mode 100644 index 0000000..f5b36df --- /dev/null +++ b/hi/kumb.h @@ -0,0 +1,1523 @@ +/*! + @file sse_mathfun.h + + SIMD (SSE1+MMX or SSE2) implementation of sin, cos, exp and log + + Inspired by Intel Approximate Math library, and based on the + corresponding algorithms of the cephes math library + + The default is to use the SSE1 version. If you define USE_SSE2 the + the SSE2 intrinsics will be used in place of the MMX intrinsics. Do + not expect any significant performance improvement with SSE2. +*/ + +/* Copyright (C) 2010,2011 RJVB - extensions */ +/* Copyright (C) 2007 Julien Pommier + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + (this is the zlib license) +*/ + +#ifndef _SSE_MATHFUN_H + +#ifdef USE_SSE_AUTO +# ifdef __SSE2__ +# if defined(__GNUC__) +# warning "USE_SSE2" +# endif +# define USE_SSE2 +# endif +# if defined(__SSE3__) || defined(__SSSE3__) +# if defined(__GNUC__) +# warning "USE_SSE3" +# endif +# define USE_SSE2 +# define USE_SSE3 +# endif +# if defined(__SSE4__) || defined(__SSE4_1__) || defined(__SSE4_2__) || ((_M_IX86_FP > 1) && !defined(_M_AMD64)) +# if defined(__GNUC__) +# warning "USE_SSE4" +# endif +# define USE_SSE2 +# define USE_SSE3 +# define USE_SSE4 +# endif +#endif + +#include +#include +#include + +/* yes I know, the top of this file is quite ugly */ + +/*! + macros to obtain the required 16bit alignment + */ +#ifdef _MSC_VER /* visual c++ */ +# define ALIGN16_BEG __declspec(align(16)) +# define ALIGN16_END +# define inline __forceinline +#else /* gcc or icc */ +# define ALIGN16_BEG +# define ALIGN16_END __attribute__((aligned(16))) +#endif + +/* __m128 is ugly to write */ +/*! + an SSE vector of 4 floats + */ +typedef __m128 v4sf; // vector of 4 float (sse1) + +#if defined(USE_SSE3) || defined(USE_SSE4) +# define USE_SSE2 +#endif + +/*! + an SSE/MMX vector of 4 32bit integers + */ +#ifdef __APPLE_CC__ + typedef int v4si __attribute__ ((__vector_size__ (16), __may_alias__)); +#else + typedef __m128i v4si; // vector of 4 int (sse2) +#endif +// RJVB 20111028: some support for double precision semantics +/*! + an SSE2+ vector of 2 doubles + */ +typedef __m128d v2df; // vector of 2 double (sse2) +/*! + an MMX vector of 2 32bit ints + */ +typedef __m64 v2si; // vector of 2 int (mmx) + +#if defined(USE_SSE3) || defined(USE_SSE4) +# define USE_SSE3 +# include +# if defined(__SSSE3__) || (_M_IX86_FP > 1) +# include +# endif +#endif + +#if defined(USE_SSE4) +# define USE_SSE4 +# include +#endif + +#ifdef __GNUC__0 +# define _MM_SET_PD(b,a) (v2df){(a),(b)} +# define _MM_SET1_PD(a) (v2df){(a),(a)} +// static inline v2df _MM_SET1_PD(double a) +// { +// return (v2df){a,a}; +// } +# define _MM_SETR_PD(a,b) (v2df){(a),(b)} +# define _MM_SETZERO_PD() (v2df){0.0,0.0} +# define _MM_SET_PS(d,c,b,a) (v4sf){(a),(b),(c),(d)} +# define _MM_SET1_PS(a) (v4sf){(a),(a),(a),(a)} +// static inline v4sf _MM_SET1_PS(float a) +// { +// return (v4sf){a,a,a,a}; +// } +# define _MM_SETR_PS(a,b,c,d) (v4sf){(a),(b),(c),(d)} +# define _MM_SETZERO_PS() (v4sf){0.0f,0.0f,0.0f,0.0f} +# define _MM_SETZERO_SI128() (__m128i)(__v4si){0,0,0,0} +# define _MM_SETZERO_SI64() ALIGN16_BEG (__m64 ALIGN16_END)0LL +#else +# define _MM_SET_PD(b,a) _mm_setr_pd((a),(b)) +# define _MM_SET1_PD(a) _mm_set1_pd((a)) +# define _MM_SETR_PD(a,b) _mm_setr_pd((a),(b)) +# define _MM_SETZERO_PD() _mm_setzero_pd() +# define _MM_SET_PS(d,c,b,a) _mm_setr_ps((a),(b),(c),(d)) +# define _MM_SET1_PS(a) _mm_set1_ps((a)) +# define _MM_SETR_PS(a,b,c,d) _mm_setr_ps((a),(b),(c),(d)) +# define _MM_SETZERO_PS() _mm_setzero_ps() +# define _MM_SETZERO_SI128() _mm_setzero_si128() +# define _MM_SETZERO_SI64() _mm_setzero_si64() +#endif +#define VELEM(type,a,n) (((type*)&a)[n]) + +/* declare some SSE constants -- why can't I figure a better way to do that? */ +#define _PS_CONST(Name, Val) \ + static const ALIGN16_BEG float _ps_##Name[4] ALIGN16_END = { (const float)(Val), (const float)(Val), (const float)(Val), (const float)(Val) } +#define _PI32_CONST(Name, Val) \ + static const ALIGN16_BEG int _pi32_##Name[4] ALIGN16_END = { Val, Val, Val, Val } +#define _PS_CONST_TYPE(Name, Type, Val) \ + static const ALIGN16_BEG Type _ps_##Name[4] ALIGN16_END = { Val, Val, Val, Val } + +#define _PD_CONST(Name, Val) \ + static const ALIGN16_BEG double _pd_##Name[2] ALIGN16_END = { (const double)(Val), (const double)(Val) } +#define _PD_CONST_TYPE(Name, Type, Val) \ + static const ALIGN16_BEG Type _pd_##Name[2] ALIGN16_END = { Val, Val } + +#ifdef SSE_MATHFUN_WITH_CODE + +_PS_CONST(1 , 1.0f); +_PS_CONST(0p5, 0.5f); +/* the smallest non denormalized float number */ +_PS_CONST_TYPE(min_norm_pos, int, 0x00800000); +_PS_CONST_TYPE(mant_mask, int, 0x7f800000); +_PS_CONST_TYPE(inv_mant_mask, int, ~0x7f800000); + +_PS_CONST_TYPE(sign_mask, int, 0x80000000); +_PS_CONST_TYPE(inv_sign_mask, int, ~0x80000000); + +_PI32_CONST(1, 1); +_PI32_CONST(inv1, ~1); +_PI32_CONST(2, 2); +_PI32_CONST(4, 4); +_PI32_CONST(0x7f, 0x7f); + +_PS_CONST(cephes_SQRTHF, 0.707106781186547524); +_PS_CONST(cephes_log_p0, 7.0376836292E-2); +_PS_CONST(cephes_log_p1, - 1.1514610310E-1); +_PS_CONST(cephes_log_p2, 1.1676998740E-1); +_PS_CONST(cephes_log_p3, - 1.2420140846E-1); +_PS_CONST(cephes_log_p4, + 1.4249322787E-1); +_PS_CONST(cephes_log_p5, - 1.6668057665E-1); +_PS_CONST(cephes_log_p6, + 2.0000714765E-1); +_PS_CONST(cephes_log_p7, - 2.4999993993E-1); +_PS_CONST(cephes_log_p8, + 3.3333331174E-1); +_PS_CONST(cephes_log_q1, -2.12194440e-4); +_PS_CONST(cephes_log_q2, 0.693359375); + +#ifdef USE_SSE2 + _PD_CONST(1, 1.0); + _PD_CONST(_1, -1.0); + _PD_CONST(0p5, 0.5); + /* the smallest non denormalised float number */ +// _PD_CONST_TYPE(min_norm_pos, int, 0x00800000); +// _PD_CONST_TYPE(mant_mask, int, 0x7f800000); +// _PD_CONST_TYPE(inv_mant_mask, int, ~0x7f800000); + + _PD_CONST_TYPE(sign_mask, long long, 0x8000000000000000LL); + _PD_CONST_TYPE(inv_sign_mask, long long, ~0x8000000000000000LL); + +#endif + +#if defined (__MINGW32__) + +/* the ugly part below: many versions of gcc used to be completely buggy with respect to some intrinsics + The movehl_ps is fixed in mingw 3.4.5, but I found out that all the _mm_cmp* intrinsics were completely + broken on my mingw gcc 3.4.5 ... + + Note that the bug on _mm_cmp* does occur only at -O0 optimization level +*/ + +inline __m128 my_movehl_ps(__m128 a, const __m128 b) { + asm ( + "movhlps %2,%0\n\t" + : "=x" (a) + : "0" (a), "x"(b) + ); + return a; } +#warning "redefined _mm_movehl_ps (see gcc bug 21179)" +#define _mm_movehl_ps my_movehl_ps + +inline __m128 my_cmplt_ps(__m128 a, const __m128 b) { + asm ( + "cmpltps %2,%0\n\t" + : "=x" (a) + : "0" (a), "x"(b) + ); + return a; + } +inline __m128 my_cmpgt_ps(__m128 a, const __m128 b) { + asm ( + "cmpnleps %2,%0\n\t" + : "=x" (a) + : "0" (a), "x"(b) + ); + return a; +} +inline __m128 my_cmpeq_ps(__m128 a, const __m128 b) { + asm ( + "cmpeqps %2,%0\n\t" + : "=x" (a) + : "0" (a), "x"(b) + ); + return a; +} +#warning "redefined _mm_cmpxx_ps functions..." +#define _mm_cmplt_ps my_cmplt_ps +#define _mm_cmpgt_ps my_cmpgt_ps +#define _mm_cmpeq_ps my_cmpeq_ps +#endif + +#ifndef USE_SSE2 +typedef union xmm_mm_union { + __m128 xmm; + __m64 mm[2]; +} xmm_mm_union; + +#define COPY_XMM_TO_MM(xmm_, mm0_, mm1_) { \ + xmm_mm_union u; u.xmm = xmm_; \ + mm0_ = u.mm[0]; \ + mm1_ = u.mm[1]; \ +} + +#define COPY_MM_TO_XMM(mm0_, mm1_, xmm_) { \ + xmm_mm_union u; u.mm[0]=mm0_; u.mm[1]=mm1_; xmm_ = u.xmm; \ + } + +#endif // USE_SSE2 + +/*! + natural logarithm computed for 4 simultaneous float + @n + return NaN for x <= 0 +*/ +static inline v4sf log_ps(v4sf x) +{ + v4sf e; +#ifdef USE_SSE2 + v4si emm0; +#else + v2si mm0, mm1; +#endif + v4sf one = *(v4sf*)_ps_1; + v4sf invalid_mask = _mm_cmple_ps(x, _MM_SETZERO_PS()); + + x = _mm_max_ps(x, *(v4sf*)_ps_min_norm_pos); /* cut off denormalized stuff */ + +#ifndef USE_SSE2 + /* part 1: x = frexpf(x, &e); */ + COPY_XMM_TO_MM(x, mm0, mm1); + mm0 = _mm_srli_pi32(mm0, 23); + mm1 = _mm_srli_pi32(mm1, 23); +#else + emm0 = _mm_srli_epi32(_mm_castps_si128(x), 23); +#endif + /* keep only the fractional part */ + x = _mm_and_ps(x, *(v4sf*)_ps_inv_mant_mask); + x = _mm_or_ps(x, *(v4sf*)_ps_0p5); + +#ifndef USE_SSE2 + /* now e=mm0:mm1 contain the really base-2 exponent */ + mm0 = _mm_sub_pi32(mm0, *(v2si*)_pi32_0x7f); + mm1 = _mm_sub_pi32(mm1, *(v2si*)_pi32_0x7f); + e = _mm_cvtpi32x2_ps(mm0, mm1); + _mm_empty(); /* bye bye mmx */ +#else + emm0 = _mm_sub_epi32(emm0, *(v4si*)_pi32_0x7f); + e = _mm_cvtepi32_ps(emm0); +#endif + + e = _mm_add_ps(e, one); + + /* part2: + if( x < SQRTHF ) { + e -= 1; + x = x + x - 1.0; + } else { x = x - 1.0; } + */ + { + v4sf z, y; + v4sf mask = _mm_cmplt_ps(x, *(v4sf*)_ps_cephes_SQRTHF); + v4sf tmp = _mm_and_ps(x, mask); + x = _mm_sub_ps(x, one); + e = _mm_sub_ps(e, _mm_and_ps(one, mask)); + x = _mm_add_ps(x, tmp); + + + z = _mm_mul_ps(x,x); + + y = *(v4sf*)_ps_cephes_log_p0; + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_log_p1); + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_log_p2); + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_log_p3); + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_log_p4); + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_log_p5); + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_log_p6); + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_log_p7); + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_log_p8); + y = _mm_mul_ps(y, x); + + y = _mm_mul_ps(y, z); + + tmp = _mm_mul_ps(e, *(v4sf*)_ps_cephes_log_q1); + y = _mm_add_ps(y, tmp); + + + tmp = _mm_mul_ps(z, *(v4sf*)_ps_0p5); + y = _mm_sub_ps(y, tmp); + + tmp = _mm_mul_ps(e, *(v4sf*)_ps_cephes_log_q2); + x = _mm_add_ps(x, y); + x = _mm_add_ps(x, tmp); + x = _mm_or_ps(x, invalid_mask); // negative arg will be NAN + } + return x; +} + +_PS_CONST(exp_hi, 88.3762626647949f); +_PS_CONST(exp_lo, -88.3762626647949f); + +_PS_CONST(cephes_LOG2EF, 1.44269504088896341); +_PS_CONST(cephes_exp_C1, 0.693359375); +_PS_CONST(cephes_exp_C2, -2.12194440e-4); + +_PS_CONST(cephes_exp_p0, 1.9875691500E-4); +_PS_CONST(cephes_exp_p1, 1.3981999507E-3); +_PS_CONST(cephes_exp_p2, 8.3334519073E-3); +_PS_CONST(cephes_exp_p3, 4.1665795894E-2); +_PS_CONST(cephes_exp_p4, 1.6666665459E-1); +_PS_CONST(cephes_exp_p5, 5.0000001201E-1); + +/*! + computes e**x of the 4 floats in x + */ +static inline v4sf exp_ps(v4sf x) +{ v4sf tmp = _MM_SETZERO_PS(), fx, mask, y, z; + v4sf pow2n; +#ifdef USE_SSE2 + v4si emm0; +#else + v2si mm0, mm1; +#endif + v4sf one = *(v4sf*)_ps_1; + + x = _mm_min_ps(x, *(v4sf*)_ps_exp_hi); + x = _mm_max_ps(x, *(v4sf*)_ps_exp_lo); + + /* express exp(x) as exp(g + n*log(2)) */ + fx = _mm_mul_ps(x, *(v4sf*)_ps_cephes_LOG2EF); + fx = _mm_add_ps(fx, *(v4sf*)_ps_0p5); + + /* how to perform a floorf with SSE: just below */ +#ifndef USE_SSE2 + /* step 1 : cast to int */ + tmp = _mm_movehl_ps(tmp, fx); + mm0 = _mm_cvttps_pi32(fx); + mm1 = _mm_cvttps_pi32(tmp); + /* step 2 : cast back to float */ + tmp = _mm_cvtpi32x2_ps(mm0, mm1); +#else + emm0 = _mm_cvttps_epi32(fx); + tmp = _mm_cvtepi32_ps(emm0); +#endif + /* if greater, substract 1 */ + mask = _mm_cmpgt_ps(tmp, fx); + mask = _mm_and_ps(mask, one); + fx = _mm_sub_ps(tmp, mask); + + tmp = _mm_mul_ps(fx, *(v4sf*)_ps_cephes_exp_C1); + z = _mm_mul_ps(fx, *(v4sf*)_ps_cephes_exp_C2); + x = _mm_sub_ps(x, tmp); + x = _mm_sub_ps(x, z); + + z = _mm_mul_ps(x,x); + + y = *(v4sf*)_ps_cephes_exp_p0; + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_exp_p1); + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_exp_p2); + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_exp_p3); + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_exp_p4); + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_exp_p5); + y = _mm_mul_ps(y, z); + y = _mm_add_ps(y, x); + y = _mm_add_ps(y, one); + + /* build 2^n */ +#ifndef USE_SSE2 + z = _mm_movehl_ps(z, fx); + mm0 = _mm_cvttps_pi32(fx); + mm1 = _mm_cvttps_pi32(z); + mm0 = _mm_add_pi32(mm0, *(v2si*)_pi32_0x7f); + mm1 = _mm_add_pi32(mm1, *(v2si*)_pi32_0x7f); + mm0 = _mm_slli_pi32(mm0, 23); + mm1 = _mm_slli_pi32(mm1, 23); + + COPY_MM_TO_XMM(mm0, mm1, pow2n); + _mm_empty(); +#else + emm0 = _mm_cvttps_epi32(fx); + emm0 = _mm_add_epi32(emm0, *(v4si*)_pi32_0x7f); + emm0 = _mm_slli_epi32(emm0, 23); + pow2n = _mm_castsi128_ps(emm0); +#endif + y = _mm_mul_ps(y, pow2n); + return y; +} + +_PS_CONST(minus_cephes_DP1, -0.78515625); +_PS_CONST(minus_cephes_DP2, -2.4187564849853515625e-4); +_PS_CONST(minus_cephes_DP3, -3.77489497744594108e-8); +_PS_CONST(sincof_p0, -1.9515295891E-4); +_PS_CONST(sincof_p1, 8.3321608736E-3); +_PS_CONST(sincof_p2, -1.6666654611E-1); +_PS_CONST(coscof_p0, 2.443315711809948E-005); +_PS_CONST(coscof_p1, -1.388731625493765E-003); +_PS_CONST(coscof_p2, 4.166664568298827E-002); +_PS_CONST(cephes_FOPI, 1.27323954473516); // 4 / M_PI + +#ifdef USE_SSE2 + _PD_CONST(minus_cephes_DP1, -0.78515625); + _PD_CONST(minus_cephes_DP2, -2.4187564849853515625e-4); + _PD_CONST(minus_cephes_DP3, -3.77489497744594108e-8); + _PD_CONST(sincof_p0, -1.9515295891E-4); + _PD_CONST(sincof_p1, 8.3321608736E-3); + _PD_CONST(sincof_p2, -1.6666654611E-1); + _PD_CONST(coscof_p0, 2.443315711809948E-005); + _PD_CONST(coscof_p1, -1.388731625493765E-003); + _PD_CONST(coscof_p2, 4.166664568298827E-002); + _PD_CONST(cephes_FOPI, 1.27323954473516); // 4 / M_PI +#endif + + +/*! + evaluation of 4 sines at onces, using only SSE1+MMX intrinsics so + it runs also on old athlons XPs and the pentium III of your grand + mother. + @n + The code is the exact rewriting of the cephes sinf function. + Precision is excellent as long as x < 8192 (I did not bother to + take into account the special handling they have for greater values + -- it does not return garbage for arguments over 8192, though, but + the extra precision is missing). + @n + Note that it is such that sinf((float)M_PI) = 8.74e-8, which is the + surprising but correct result. + @n + Performance is also surprisingly good, 1.33 times faster than the + macos vsinf SSE2 function, and 1.5 times faster than the + __vrs4_sinf of amd's ACML (which is only available in 64 bits). Not + too bad for an SSE1 function (with no special tuning) ! + However the latter libraries probably have a much better handling of NaN, + Inf, denormalized and other special arguments.. + @n + On my core 1 duo, the execution of this function takes approximately 95 cycles. + @n + From what I have observed on the experiments with Intel AMath lib, switching to an + SSE2 version would improve the perf by only 10%. + @n + Since it is based on SSE intrinsics, it has to be compiled at -O2 to + deliver full speed. +*/ +static inline v4sf sin_ps(v4sf x) +{ // any x + v4sf xmm1, xmm2 = _MM_SETZERO_PS(), xmm3, sign_bit, y, y2, z, tmp; + + v4sf swap_sign_bit, poly_mask; +#ifdef USE_SSE2 + v4si emm0, emm2; +#else + v2si mm0, mm1, mm2, mm3; +#endif + sign_bit = x; + /* take the absolute value */ + x = _mm_and_ps(x, *(v4sf*)_ps_inv_sign_mask); + /* extract the sign bit (upper one) */ + sign_bit = _mm_and_ps(sign_bit, *(v4sf*)_ps_sign_mask); + + /* scale by 4/Pi */ + y = _mm_mul_ps(x, *(v4sf*)_ps_cephes_FOPI); + + //printf("plop:"); print4(y); +#ifdef USE_SSE2 + /* store the integer part of y in mm0 */ + emm2 = _mm_cvttps_epi32(y); + /* j=(j+1) & (~1) (see the cephes sources) */ + emm2 = _mm_add_epi32(emm2, *(v4si*)_pi32_1); + emm2 = _mm_and_si128(emm2, *(v4si*)_pi32_inv1); + y = _mm_cvtepi32_ps(emm2); + /* get the swap sign flag */ + emm0 = _mm_and_si128(emm2, *(v4si*)_pi32_4); + emm0 = _mm_slli_epi32(emm0, 29); + /* get the polynom selection mask + there is one polynom for 0 <= x <= Pi/4 + and another one for Pi/4 0 ){ + v2df *va = (v2df*) xa, vsum = _MM_SETZERO_PD(); + int i, N_4 = N-4+1; + for( i = 0 ; i < N_4 ; va+=2 ){ + vsum = _mm_add_pd( vsum, _mm_add_pd( va[0], va[1] ) ); + i += 4; + } + sum = VELEM(double,vsum,0) + VELEM(double,vsum,1); + for( ; i < N; i++ ){ + sum += xa[i]; + } + } + else{ + sum = 0.0; + } + return sum; +} + + +/*! + computes the cumulative sum of the squares of the values in double array xa[n] using SSE2 intrinsics + */ +static inline double CumSumSq( double *xa, int n ) +{ __m128d vsumsq; + register int i, N_4 = n-4+1; + register double sumsq = 0; + for( i = 0 ; i < N_4 ; i+=4, xa+=4 ){ +#ifdef __GNUC__ + vsumsq = *((__m128d*)&xa[2]) * *((__m128d*)&xa[2]) + *((__m128d*)xa) * *((__m128d*)xa); +#else + vsumsq = _mm_add_pd( _mm_mul_pd( *((__m128d*)&xa[2]), *((__m128d*)&xa[2]) ), + _mm_mul_pd( *((__m128d*)xa), *((__m128d*)xa) ) ); +#endif + sumsq += *((double*)&vsumsq) + ((double*)&vsumsq)[1]; + } + for( ; i < n ; i++, xa++ ){ + sumsq += *xa * *xa; + } + return sumsq; +} + +/*! + computes the cumulative sum of the values and their squares in double array xa[n] using SSE2 intrinsics + */ +static inline double CumSumSumSq( double *xa, int n, double *sumSQ ) +{ __m128d vsum, vsumsq; + register int i, N_4 = n-4+1; + register double sum = 0.0, sumsq = 0; + for( i = 0 ; i < N_4 ; i+=4, xa+=4 ){ +#ifdef __GNUC__ + vsum = *((__m128d*)&xa[2]) + *((__m128d*)xa); + vsumsq = *((__m128d*)&xa[2]) * *((__m128d*)&xa[2]) + *((__m128d*)xa) * *((__m128d*)xa); +#else + vsum = _mm_add_pd( *((__m128d*)&xa[2]), *((__m128d*)xa) ); + vsumsq = _mm_add_pd( _mm_mul_pd( *((__m128d*)&xa[2]), *((__m128d*)&xa[2]) ), + _mm_mul_pd( *((__m128d*)xa), *((__m128d*)xa) ) ); +#endif + sum += *((double*)&vsum) + ((double*)&vsum)[1]; + sumsq += *((double*)&vsumsq) + ((double*)&vsumsq)[1]; + } + for( ; i < n ; i++, xa++ ){ + sum += *xa; + sumsq += *xa * *xa; + } + *sumSQ = sumsq; + return sum; +} + +/*! + scalar version of CumSum without explicit SSE2 intrinsics + */ +static inline double scalCumSum( double *xa, int n ) +{ register int i; + register double sum = 0.0; + for( i = 0 ; i < n ; i++ ){ + sum += *xa++; + } + return sum; +} + +/*! + scalar version of CumSumSq without explicit SSE2 intrinsics + */ +static inline double scalCumSumSq( double *xa, int n ) +{ register int i; + register double sumsq = 0.0; + for( i = 0 ; i < n ; i++, xa++ ){ + sumsq += *xa * *xa; + } + return sumsq; +} + +/*! + scalar version of CumSumSumSq without explicit SSE2 intrinsics + */ +static inline double scalCumSumSumSq( double *xa, int n, double *sumSQ ) +{ register int i; + register double sum = 0.0, sumsq = 0.0; + for( i = 0 ; i < n ; i++, xa++ ){ + sum += *xa; + sumsq += *xa * *xa; + } + *sumSQ = sumsq; + return sum; +} + +/*! + computes the cumulative product of the double array xa[n] using SSE2 intrinsics + */ +static inline double CumMul(double *xa, int N) +{ double cum; + if( xa && N > 0 ){ + v2df *va = (v2df*) xa, vcum = _MM_SET1_PD(1.0); + int i, N_4 = N-4+1; + for( i = 0 ; i < N_4 ; va+=2 ){ + vcum = _mm_mul_pd( vcum, _mm_mul_pd( va[0], va[1] ) ); + i += 4; + } + cum = VELEM(double,vcum,0) * VELEM(double,vcum,1); + for( ; i < N; i++ ){ + cum *= xa[i]; + } + } + else{ + cum = 0.0; + } + return cum; +} + +#else + +/*! + computes the cumulative sum of the double array xa[n] using traditional scalar code + */ +static inline double CumSum( double *xa, int n ) +{ register int i; + register double sum = 0.0; + for( i = 0 ; i < n ; i++ ){ + sum += *xa++; + } + return sum; +} + +/*! + alternative for CumSum + */ +static inline double scalCumSum( double *xa, int n ) +{ + return CumSum(xa,n); +} + +/*! + computes the cumulative sum of the squares of the values in double array xa[n] using traditional scalar code + */ +static inline double CumSumSq( double *xa, int n ) +{ register int i; + register double sumsq = 0.0; + for( i = 0 ; i < n ; i++, xa++ ){ + sumsq += *xa * *xa; + } + return sumsq; +} + +/*! + alternative for CumSumSq + */ +static inline double scalCumSumSq( double *xa, int n ) +{ + return CumSumSq(xa,n); +} + +/*! + computes the cumulative sum of the values and their squares in double array xa[n] using traditional scalar code + */ +static inline double CumSumSumSq( double *xa, int n, double *sumSQ ) +{ register int i; + register double sum = 0.0, sumsq = 0.0; + for( i = 0 ; i < n ; i++, xa++ ){ + sum += *xa; + sumsq += *xa * *xa; + } + *sumSQ = sumsq; + return sum; +} + +/*! + alternative for CumSumSumSq + */ +static inline double scalCumSumSumSq( double *xa, int n, double *sumSQ ) +{ + return CumSumSumSq(xa,n,sumSQ); +} + +#endif //USE_SSE2 + +#endif // SSE_MATHFUN_WITH_CODE + +//// Some SSE "extensions", and equivalents not using SSE explicitly: + +#ifdef USE_SSE2 + +# if defined(__x86_64__) || defined(x86_64) || defined(_LP64) +// static inline v2df _mm_abs_pd( v2df a ) +// { _PD_CONST_TYPE(abs_mask, long long, ~0x8000000000000000LL); +// return _mm_and_pd(a, *(v2df*)_pd_abs_mask); +// } + /*! + SSE2 'intrinsic' to take the absolute value of a + */ + static inline v2df _mm_abs_pd( register v2df a ) + { const static long long am1[2] = {~0x8000000000000000LL,~0x8000000000000000LL}; + return _mm_and_pd(a, *((v2df*)am1) ); + } + static inline double _mm_abs_sd( double a ) + { const static long long am2 = {~0x8000000000000000LL}; + v2si r = _mm_and_si64( *((v2si*)&a), *((v2si*)&am2) ); + return *((double*) &r); + } +# else + // no native support for 64bit ints: don't lose time on that! + /*! + SSE2 'intrinsic' to take the absolute value of a + */ + static inline v2df _mm_abs_pd( register v2df a ) + { const v4si am1 = _mm_set_epi32(0x7fffffff,0xffffffff,0x7fffffff,0xffffffff); + return _mm_and_pd(a, *((v2df*)&am1) ); + } + static inline double _mm_abs_sd( double a ) + { const static unsigned long long am2 = 0x7fffffffffffffffLL; + const v4si am1 = _mm_set_epi32(0x7fffffff,0xffffffff,0x7fffffff,0xffffffff); + v2si r = _mm_and_si64( *((v2si*)&a), *((v2si*)&am1) ); + _mm_empty(); + return *((double*)&r); +// union { double d; v2si r; } ret; +// ret.r = _mm_and_si64( *((v2si*)&a), *((v2si*)&am1) ); +// a = ret.d; +// return a; + } +# endif // i386 or x86_64 + static inline v4sf _mm_abs_ps( register v4sf a ) + { const v4si am1 = _mm_set_epi32(0x7fffffff,0x7fffffff,0x7fffffff,0x7fffffff); + return _mm_and_ps(a, *((v4sf*)&am1) ); + } + +/*! + clip a value to a min/max range + */ +static inline v2df _mm_clip_pd( v2df val, v2df valMin, v2df valMax ) +{ + return _mm_max_pd( _mm_min_pd( val, valMax ), valMin ); +} + +/*! + return an SSE2 vector of 2 doubles initialised with val0 and val1, clipped to + the specified range + */ +static inline v2df _mm_setr_clipped_pd( double val0, double val1, v2df valMin, v2df valMax ) +{ + return _mm_clip_pd( _MM_SETR_PD(val0,val1), valMin, valMax ); +} +#endif // USE_SSE2 +#ifdef USE_SSE4 + static inline double ssceil(double a) + { v2df va = _mm_ceil_pd( _MM_SETR_PD(a,0) ); +# if !defined(__x86_64__) && !defined(x86_64) && !defined(_LP64) + _mm_empty(); +# endif + return *((double*)&va); + } + + static inline double ssfloor(double a) + { v2df va = _mm_floor_pd( _MM_SETR_PD(a,0) ); +# if !defined(__x86_64__) && !defined(x86_64) && !defined(_LP64) + _mm_empty(); +# endif + return *((double*)&va); + } + static inline double ssround( double a ) + { v2df va = _mm_round_pd( _MM_SETR_PD(a,0), _MM_FROUND_TO_NEAREST_INT|_MM_FROUND_NO_EXC); +# if !defined(__x86_64__) && !defined(x86_64) && !defined(_LP64) + _mm_empty(); +# endif + return *((double*)&va); + } +#else + static inline double ssceil(double a) + { + return ceil(a); + } + static inline double ssfloor(double a) + { + return floor(a); + } + static inline double ssround( double a ) + { + return (a >= 0)? floor( a + 0.5 ) : -ceil( -a - 0.5 ); + } +#endif //USE_SSE4 + + +// SSE-like convenience functions (note the absence of a leading _!) + +/*! + return an SSE2 vector of 2 doubles initialised with val0 and val1, clipped to + the specified range. Does not use SSE2 intrinsics. + */ +static inline v2df *mm_setr_clipped_pd( v2df *val, double val0, double val1, v2df *valMin, v2df *valMax ) +{ + if( val0 > ((double*)valMax)[0] ){ + ((double*)val)[0] = ((double*)valMax)[0]; + } + else if( val0 < ((double*)valMin)[0] ){ + ((double*)val)[0] = ((double*)valMin)[0]; + } + else{ + ((double*)val)[0] = val0; + } + if( val1 > ((double*)valMax)[1] ){ + ((double*)val)[1] = ((double*)valMax)[1]; + } + else if( val1 < ((double*)valMin)[1] ){ + ((double*)val)[1] = ((double*)valMin)[1]; + } + else{ + ((double*)val)[1] = val1; + } + return val; +} + +/*! + SSE2 'intrinsic' to take the absolute value of a. Doesn't use SSE2 intrinsics + */ +static inline v2df *mm_clip_pd( v2df *val, v2df *valMin, v2df *valMax ) +{ + if( ((double*)val)[0] > ((double*)valMax)[0] ){ + ((double*)val)[0] = ((double*)valMax)[0]; + } + else if( ((double*)val)[0] < ((double*)valMin)[0] ){ + ((double*)val)[0] = ((double*)valMin)[0]; + } + if( ((double*)val)[1] > ((double*)valMax)[1] ){ + ((double*)val)[1] = ((double*)valMax)[1]; + } + else if( ((double*)val)[1] < ((double*)valMin)[1] ){ + ((double*)val)[1] = ((double*)valMin)[1]; + } + return val; +} + +/*! + emulation of the _mm_add_pd SSE2 intrinsic + */ +static inline v2df *mm_add_pd( v2df *c, v2df *a, v2df *b ) +{ + ((double*)c)[0] = ((double*)a)[0] + ((double*)b)[0]; + ((double*)c)[1] = ((double*)a)[1] + ((double*)b)[1]; + return c; +} + +/*! + emulation of the _mm_add_pd SSE2 intrinsic + */ +static inline v2df *mm_sub_pd( v2df *c, v2df *a, v2df *b ) +{ + ((double*)c)[0] = ((double*)a)[0] - ((double*)b)[0]; + ((double*)c)[1] = ((double*)a)[1] - ((double*)b)[1]; + return c; +} + +/*! + emulation of the _mm_sub_pd SSE2 intrinsic + */ +static inline v2df *mm_div_pd( v2df *c, v2df *a, v2df *b ) +{ + ((double*)c)[0] = ((double*)a)[0] / ((double*)b)[0]; + ((double*)c)[1] = ((double*)a)[1] / ((double*)b)[1]; + return c; +} + +/*! + emulation of the _mm_mul_pd SSE2 intrinsic + */ +static inline v2df *mm_mul_pd( v2df *c, v2df *a, v2df *b ) +{ + ((double*)c)[0] = ((double*)a)[0] * ((double*)b)[0]; + ((double*)c)[1] = ((double*)a)[1] * ((double*)b)[1]; + return c; +} + +/*! + non SSE emulation of the _mm_abs_pd 'intrinsic' defined elsewhere in this file + */ +static inline v2df *mm_abs_pd( v2df *val, v2df *a ) +{ + ((double*)val)[0] = (((double*)a)[0] >= 0)? ((double*)a)[0] : -((double*)a)[0]; + ((double*)val)[1] = (((double*)a)[1] >= 1)? ((double*)a)[1] : -((double*)a)[1]; + return val; +} + +/*! + emulation of the _mm_round_pd SSE4 intrinsic. + @n + NB: the SSE4 intrinsic is at least twice as fast as the non-SSE calculation, PER value + so it pays to replace round(x) with _mm_round_pd(_mm_setr_pd(x)) - idem for floor and ceil + */ +static inline v2df *mm_round_pd( v2df *val, v2df *a ) +{ + ((double*)val)[0] = (((double*)a)[0] >= 0)? floor( ((double*)a)[0] + 0.5 ) : -ceil( -((double*)a)[0] - 0.5 ); + ((double*)val)[1] = (((double*)a)[1] >= 0)? floor( ((double*)a)[1] + 0.5 ) : -ceil( -((double*)a)[1] - 0.5 ); + return val; +} + +#define _SSE_MATHFUN_H +#endif diff --git a/hi/linearity.h b/hi/linearity.h new file mode 100644 index 0000000..3816363 --- /dev/null +++ b/hi/linearity.h @@ -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); +} + diff --git a/hi/loopback.c b/hi/loopback.c new file mode 100644 index 0000000..f4ad032 --- /dev/null +++ b/hi/loopback.c @@ -0,0 +1,19 @@ +#include + +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)); +} diff --git a/hi/microphone.c b/hi/microphone.c new file mode 100644 index 0000000..c499e26 --- /dev/null +++ b/hi/microphone.c @@ -0,0 +1,209 @@ +#include"node.h" + +#include +#include"img.h" +#include +#include +#include +#include +#include +#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; +} diff --git a/hi/microphone.h b/hi/microphone.h new file mode 100644 index 0000000..83c0345 --- /dev/null +++ b/hi/microphone.h @@ -0,0 +1,18 @@ +#ifndef _CUTIHI_MICROPHONE_H +#define _CUTIHI_MICROPHONE_H + +#include + +#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 \ No newline at end of file diff --git a/hi/mode.c b/hi/mode.c new file mode 100644 index 0000000..0be4ca7 --- /dev/null +++ b/hi/mode.c @@ -0,0 +1,11 @@ +#include"mode.h" + +static CHiMode moed; + +CUTIVIS void CHi_SetMode(CHiMode mode) { + moed = mode; +} + +CUTIVIS CHiMode CHi_GetMode() { + return moed; +} \ No newline at end of file diff --git a/hi/mode.h b/hi/mode.h new file mode 100644 index 0000000..3390ddd --- /dev/null +++ b/hi/mode.h @@ -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 diff --git a/hi/node.c b/hi/node.c new file mode 100644 index 0000000..07aaa2b --- /dev/null +++ b/hi/node.c @@ -0,0 +1,899 @@ +#include"node.h" + +#include +#include"img.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include"mode.h" +#include +#include +#include + +#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; +} diff --git a/hi/node.h b/hi/node.h new file mode 100644 index 0000000..0826362 --- /dev/null +++ b/hi/node.h @@ -0,0 +1,268 @@ +#ifndef _NODE_H +#define _NODE_H + +#include +#include + +#include"defs.h" + +#include"bs.h" + +#include + +#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 diff --git a/hi/opus.c b/hi/opus.c new file mode 100644 index 0000000..e3da848 --- /dev/null +++ b/hi/opus.c @@ -0,0 +1,85 @@ +#include"node.h" + +#include +#include +#include"img.h" +#include +#include + +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; +} diff --git a/hi/relay.c b/hi/relay.c new file mode 100644 index 0000000..10a1fe4 --- /dev/null +++ b/hi/relay.c @@ -0,0 +1,132 @@ +#include"relay.h" + +#include"img.h" +#include +#include +#include +#include +#include +#include +#include + +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; +} diff --git a/hi/relay.h b/hi/relay.h new file mode 100644 index 0000000..253b306 --- /dev/null +++ b/hi/relay.h @@ -0,0 +1,14 @@ +#ifndef _CUTIHI_RELAY_H +#define _CUTIHI_RELAY_H + +#include"node.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/hi/test.c b/hi/test.c new file mode 100644 index 0000000..4caff75 --- /dev/null +++ b/hi/test.c @@ -0,0 +1,43 @@ +#include"node.h" + +#include + +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); +} \ No newline at end of file diff --git a/hi/webcam.c b/hi/webcam.c new file mode 100644 index 0000000..7f942ed --- /dev/null +++ b/hi/webcam.c @@ -0,0 +1,137 @@ +#include"node.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include"img.h" +#include +#include + +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; +} diff --git a/hi/webmdec.cpp b/hi/webmdec.cpp new file mode 100644 index 0000000..0cb0693 --- /dev/null +++ b/hi/webmdec.cpp @@ -0,0 +1,418 @@ +#include"node.h" + +#include +#include +#include + +#include +#include + +#include +#include + +#include"img.h" + +#include + +#include +#include + +#include + +#include + +#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 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::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 &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; +} +} diff --git a/hi/webmenc.cpp b/hi/webmenc.cpp new file mode 100644 index 0000000..ccdf1d6 --- /dev/null +++ b/hi/webmenc.cpp @@ -0,0 +1,431 @@ +#include"node.h" + +#include +#include + +#include + +#include + +#include + +#include"mode.h" + +#include"img.h" +#include + +#include + +#include + +#include + +#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 audioBacklog; + std::queue 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(); + new (&n->videoBacklog) std::queue(); + 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; +} diff --git a/hi/window.c b/hi/window.c new file mode 100644 index 0000000..f7fde1a --- /dev/null +++ b/hi/window.c @@ -0,0 +1,194 @@ +#include"node.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#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; +} diff --git a/keyframe.bmp b/keyframe.bmp new file mode 100644 index 0000000..a33eda6 Binary files /dev/null and b/keyframe.bmp differ diff --git a/keyframe_extrap.bmp b/keyframe_extrap.bmp new file mode 100644 index 0000000..da37ece Binary files /dev/null and b/keyframe_extrap.bmp differ diff --git a/tlml.bmp b/tlml.bmp new file mode 100644 index 0000000..53a0b20 Binary files /dev/null and b/tlml.bmp differ diff --git a/tlmo.bmp b/tlmo.bmp new file mode 100644 index 0000000..c9dcb0f Binary files /dev/null and b/tlmo.bmp differ diff --git a/tlmr.bmp b/tlmr.bmp new file mode 100644 index 0000000..53a0b20 Binary files /dev/null and b/tlmr.bmp differ diff --git a/ui/frame.cpp b/ui/frame.cpp new file mode 100644 index 0000000..a195d43 --- /dev/null +++ b/ui/frame.cpp @@ -0,0 +1,838 @@ +#include"frame.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include"timectrl.h" +#include +#include +#include +#include +#include +#include"hi/microphone.h" +#include +#include +#include +#include +#include"timeline.h" +#include +#include + +#define SSE_MATHFUN_WITH_CODE +#include + +#include + +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>(); + 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 choices; + std::vector 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 choicesOrig; + std::vector 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 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 &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 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); +} diff --git a/ui/frame.h b/ui/frame.h new file mode 100644 index 0000000..9432b90 --- /dev/null +++ b/ui/frame.h @@ -0,0 +1,131 @@ +#ifndef _CUTICLE_FRAME_H +#define _CUTICLE_FRAME_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +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 sinks; + std::vector 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 gnodes; + std::vector links; + + NodeGraph(Frame*); + virtual ~NodeGraph(); + + void Alinken(Link l); + + void Dirtify(GrNode *g); + + bool DetectCycles(GrNode*); +}; + + + +#endif diff --git a/ui/main.cpp b/ui/main.cpp new file mode 100644 index 0000000..63e5aab --- /dev/null +++ b/ui/main.cpp @@ -0,0 +1,12 @@ +#include + +#include"frame.h" + +struct App : wxApp { + virtual bool OnInit() { + (new Frame())->Show(true); + return true; + } +}; + +wxIMPLEMENT_APP(App); \ No newline at end of file diff --git a/ui/textctrl.cpp b/ui/textctrl.cpp new file mode 100644 index 0000000..6bf09c8 --- /dev/null +++ b/ui/textctrl.cpp @@ -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))); +} \ No newline at end of file diff --git a/ui/timectrl.h b/ui/timectrl.h new file mode 100644 index 0000000..e8914a0 --- /dev/null +++ b/ui/timectrl.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +struct ctTimeCtrl : wxTextCtrl { + ctTimeCtrl(wxWindow *parent, double seconds); + virtual ~ctTimeCtrl() = default; + + double GetSeconds(); + void SetSeconds(double); + +private: + double seconds; +}; \ No newline at end of file diff --git a/ui/timeline.cpp b/ui/timeline.cpp new file mode 100644 index 0000000..f5deb60 --- /dev/null +++ b/ui/timeline.cpp @@ -0,0 +1,273 @@ +#include"timeline.h" + +#include +#include +#include + +#include + +#include + +#include"frame.h" + +static wxBitmap bmpKf; +static wxBitmap bmpKfExtrap; + +template +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(-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)); + } + + } +} diff --git a/ui/timeline.h b/ui/timeline.h new file mode 100644 index 0000000..9553111 --- /dev/null +++ b/ui/timeline.h @@ -0,0 +1,30 @@ +#ifndef _CUTICLE_TIMELINE_H +#define _CUTICLE_TIMELINE_H + +#include +#include + +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