#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 static void ShapeGrNode(GrNode *gn) { if(gn->logical->type == CUTIHI_T('CPre','view')) { gn->name = "Preview"; gn->sinks = {{"Video", GrNode::Port::Type::SAMPLE}}; } else if(gn->logical->type == CUTIHI_T('CMix','er ')) { gn->name = "Mixer"; gn->sinks = {{"Sink 1", GrNode::Port::Type::SAMPLE}, {"Sink 2", GrNode::Port::Type::SAMPLE}}; gn->sources = {{"Audio", GrNode::Port::Type::SAMPLE}}; } else if(gn->logical->type == CUTIHI_T('CTex','t ')) { gn->name = "Text"; gn->sinks = {{"Text", GrNode::Port::Type::TEXT}, {"Color", GrNode::Port::Type::COLOR}, {"DPI", GrNode::Port::Type::VEC1}}; gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; } else if(gn->logical->type == CUTIHI_T('CTim','e ')) { gn->name = "Time"; gn->sinks = {}; gn->sources = {{"t", GrNode::Port::Type::VEC1}}; } else if(gn->logical->type == CUTIHI_T('CMod','ulat')) { gn->name = "Modulate"; gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}, {"Brightness", GrNode::Port::Type::VEC1}, {"Contrast", GrNode::Port::Type::VEC1}, {"Hue", GrNode::Port::Type::VEC1}}; gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; } else if(gn->logical->type == CUTIHI_T('CCns','tCol')) { gn->name = "Constant"; gn->sinks = {{"Color", GrNode::Port::Type::COLOR}}; gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; } else if(gn->logical->type == CUTIHI_T('CEmb','ed ')) { gn->name = "Embed"; gn->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} }; gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; } else if(gn->logical->type == CUTIHI_T('CIma','ge ')) { gn->name = "Image"; gn->sinks = {{"Filepath", GrNode::Port::Type::FILE_OPEN}}; gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; } else if(gn->logical->type == CUTIHI_T('CWin','dow ')) { gn->name = "Window"; gn->sinks = {{"Name", GrNode::Port::Type::WINDOW_SOURCE}}; gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; } else if(gn->logical->type == CUTIHI_T('CInA','udio')) { gn->name = "Microphone"; gn->sinks = {{"Source", GrNode::Port::Type::MIC_SOURCE}}; gn->sources = {{"Audio", GrNode::Port::Type::SAMPLE}}; } else if(gn->logical->type == CUTIHI_T('CExp','Wave')) { gn->name = "Mux Wav"; gn->sinks = {{"Filename", GrNode::Port::Type::FILE_SAVE}, {"Audio", GrNode::Port::Type::SAMPLE}}; gn->sources = {}; } else if(gn->logical->type == CUTIHI_T('CMov','ie ')) { gn->name = "Movie"; gn->sinks = {{"Filepath", GrNode::Port::Type::FILE_OPEN}, {"Time", GrNode::Port::Type::VEC1}}; gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}, {"Audio", GrNode::Port::Type::SAMPLE}}; } else if(gn->logical->type == CUTIHI_T('CEnc','GVP8')) { gn->name = "Encode VP8"; gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}}; gn->sources = {{"Bitstream"}}; } else if(gn->logical->type == CUTIHI_T('CEnc','GVP9')) { gn->name = "Encode VP9"; gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}}; gn->sources = {{"Bitstream"}}; } else if(gn->logical->type == CUTIHI_T('CExp','Webm')) { gn->name = "Mux WebM"; gn->sinks = {{"Video Bitstream"}, {"Audio Bitstream"}, {"Filename", GrNode::Port::Type::FILE_SAVE}}; gn->sources = {}; } else if(gn->logical->type == CUTIHI_T('CKey','hook')) { gn->name = "Keyhook"; gn->sinks = {{"Key", GrNode::Port::Type::VEC1}, {"Smooth Time", GrNode::Port::Type::VEC1}}; gn->sources = {{"Bool", GrNode::Port::Type::VEC1}}; } else if(gn->logical->type == CUTIHI_T('CEnc','Opus')) { gn->name = "Encode Opus"; gn->sinks = {{"Audio", GrNode::Port::Type::SAMPLE}}; gn->sources = {{"Bitstream"}}; } else if(gn->logical->type == CUTIHI_T('CWeb','Cam ')) { gn->name = "Live Digital Camera"; gn->sinks = {}; gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; } else if(gn->logical->type == CUTIHI_T('CCmp','nScl')) { gn->name = "Scale"; gn->sinks = {{"Vector", GrNode::Port::Type::VEC4}, {"Sample", GrNode::Port::Type::SAMPLE}}; gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}}; } else { puts("Unknown node type."); } } 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); 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().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} }; auto modeTool = tlba->AddTool(wxID_ANY, "Mode", wxBitmap{"tlml.bmp", wxBITMAP_TYPE_ANY}); CHi_SetMode(CUTIHI_MODE_LIVE); tlba->AddTool(wxID_OPEN, "Load", wxBitmap{"btn_load.bmp", wxBITMAP_TYPE_ANY}); tlba->AddTool(wxID_SAVE, "Save", wxBitmap{"btn_save.bmp", wxBITMAP_TYPE_ANY}); tlba->Bind(wxEVT_COMMAND_TOOL_CLICKED, [=](wxCommandEvent &ev){ if(ev.GetId() == modeTool->GetId()) { CHi_SetMode((CHiMode) ((CHi_GetMode() + 1) % 2)); tlba->SetToolNormalBitmap(modeTool->GetId(), modeImgs[CHi_GetMode()]); // wxToolBarToolBase::SetShortHelp doesn't work if(CHi_GetMode() == CUTIHI_MODE_LIVE) { tlba->SetToolShortHelp(modeTool->GetId(), "Set to live processing."); } else { tlba->SetToolShortHelp(modeTool->GetId(), "Set to offline processing."); } } else if(ev.GetId() == wxID_SAVE) { wxFileDialog save{this, "Save", "", "", "Cuticle Project (*.ctc)|*.ctc", wxFD_SAVE | wxFD_OVERWRITE_PROMPT}; if(save.ShowModal() == wxID_OK) { FILE *f = fopen(save.GetPath().mb_str(), "wb"); CHi_NodeGraphSave(graph->backendNG, +[](void *ud, const void *data, size_t len){ auto f = (FILE*) ud; return fwrite(data, 1, len, f); }, f); for(size_t n = 0; n < graph->backendNG->count; n++) { GrNode *gn = *std::find_if(graph->gnodes.begin(), graph->gnodes.end(), [=](GrNode *gn){ return gn->logical == graph->backendNG->nodes[n]; }); int32_t pos[2] = {gn->GetPosition().x, gn->GetPosition().y}; fwrite(pos, sizeof(pos), 1, f); } fclose(f); } } else if(ev.GetId() == wxID_OPEN) { wxFileDialog load{this, "Load", "", "", "Cuticle Project (*.ctc)|*.ctc", wxFD_OPEN | wxFD_FILE_MUST_EXIST}; if(load.ShowModal() == wxID_OK) { FILE *f = fopen(load.GetPath().mb_str(), "rb"); if(CHi_NodeGraphLoad(graph->backendNG, +[](void *ud, void *data, size_t len){ auto f = (FILE*) ud; return fread(data, 1, len, f); }, f) == 0) { for(GrNode *gnode : graph->gnodes) { gnode->Destroy(); } graph->gnodes.clear(); graph->links.clear(); for(size_t n = 0; n < graph->backendNG->count; n++) { GrNode *gnode = new GrNode(graph); int32_t pos[2]; fread(pos, sizeof(pos), 1, f); gnode->SetPosition({pos[0], pos[1]}); gnode->logical = graph->backendNG->nodes[n]; ShapeGrNode(gnode); gnode->Fit(); graph->gnodes.push_back(gnode); } for(size_t n = 0; n < graph->backendNG->count; n++) { CHiPubNode *node = graph->backendNG->nodes[n]; for(size_t s = 0; s < node->sinkCount; s++) { if(node->sinks[s].type == CUTIHI_VAL_LINKED) { graph->links.push_back(NodeGraph::Link{graph->gnodes[n], s, *std::find_if(graph->gnodes.begin(), graph->gnodes.end(), [=](GrNode *gn){ return gn->logical == node->sinks[s].data.linked.to; }), node->sinks[s].data.linked.idx}); } } } toolbar.duration->SetSeconds(std::max(0.f, graph->backendNG->duration)); if(graph->backendNG->duration <= 0) { toolbar.durationEnable->SetValue(false); toolbar.duration->Enable(false); } } fclose(f); } } }); tlba->AddSeparator(); toolbar.durationEnable = new wxCheckBox(tlba, wxID_ANY, ""); toolbar.duration = new ctTimeCtrl(tlba, 120); toolbar.duration->Bind(wxEVT_COMMAND_TEXT_UPDATED, [=](wxCommandEvent&){ CHi_SetDuration(graph->backendNG, toolbar.durationEnable->IsChecked() ? toolbar.duration->GetSeconds() : -1); }); toolbar.durationEnable->SetValue(true); toolbar.durationEnable->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent&){ toolbar.duration->Enable(toolbar.durationEnable->GetValue()); if(!toolbar.durationEnable->GetValue()) { CHi_SetDuration(graph->backendNG, -1); } }); toolbar.btnPerform = new wxButton(tlba, wxID_ANY, "Compile"); toolbar.btnPerform->Bind(wxEVT_BUTTON, [=](wxCommandEvent &ev){ if(toolbar.btnPerform->GetLabel() == "Kill") { CHi_StopCompilation(graph->backendNG); toolbar.btnPerform->Disable(); tlba->EnableTool(wxID_SAVE, false); tlba->EnableTool(wxID_OPEN, false); } else { CHi_BeginCompilation(graph->backendNG); toolbar.btnPerform->SetLabel("Kill"); std::thread{[=](){ while(graph->backendNG->compilationStatus == CUTIHI_COMP_RUNNING) { CallAfter([=](){ float t = CHi_Time_Get(graph->backendNG); 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)); } CallAfter([=](){ stba->SetStatusText("Compilation has ended."); }); }}.detach(); } }); tlba->AddControl(toolbar.durationEnable); tlba->AddControl(toolbar.duration); tlba->AddControl(toolbar.btnPerform); graph->backendNG->eventOnStopComplete = +[](CHiNodeGraph *ng){ wxTheApp->CallAfter([ng](){ wxButton *btn = ((Frame*) ng->ud)->toolbar.btnPerform; btn->Enable(); btn->SetLabel("Compile"); auto tlba = ((Frame*) ng->ud)->tlba; tlba->EnableTool(wxID_SAVE, true); tlba->EnableTool(wxID_OPEN, true); }); }; 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_TEXT; newv.data.text = strdup(CHi_Microphone_GetSourceName((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(); ShapeGrNode(v); 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(); } else if(ev.GetId() == idImage) { noed = new GrNode(this); noed->logical = CHi_Image(); } else if(ev.GetId() == idEmbed) { noed = new GrNode(this); noed->logical = CHi_Embed(); } else if(ev.GetId() == idModulate) { noed = new GrNode(this); noed->logical = CHi_Modulate(); } else if(ev.GetId() == idMovie) { noed = new GrNode(this); noed->logical = CHi_Movie(); 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(); } else if(ev.GetId() == idEncodeVp8) { noed = new GrNode(this); noed->logical = CHi_EncodeVP8(); } else if(ev.GetId() == idMuxWebm) { noed = new GrNode(this); noed->logical = CHi_MuxWebm(); } else if(ev.GetId() == idWindow) { noed = new GrNode(this); noed->logical = CHi_Window(); } else if(ev.GetId() == idText) { noed = new GrNode(this); noed->logical = CHi_Text(); } else if(ev.GetId() == idTime) { noed = new GrNode(this); noed->logical = CHi_Time(); } else if(ev.GetId() == idMicrophone) { noed = new GrNode(this); noed->logical = CHi_Microphone(); } else if(ev.GetId() == idMixer) { noed = new GrNode(this); noed->logical = CHi_Mixer(); } else if(ev.GetId() == idMuxWav) { noed = new GrNode(this); noed->logical = CHi_ExportWav(); } else if(ev.GetId() == idEncodeOpus) { noed = new GrNode(this); noed->logical = CHi_EncodeOpus(); } else if(ev.GetId() == idCamera) { noed = new GrNode(this); noed->logical = CHi_Camera(); } else if(ev.GetId() == idComponentScale) { noed = new GrNode(this); noed->logical = CHi_ComponentScale(); } else if(ev.GetId() == idKeyhook) { noed = new GrNode(this); noed->logical = CHi_Keyhook(); } if(noed) { ShapeGrNode(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->logical->type == CUTIHI_T('CPre','view')) { 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); }