#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); }