From fea7f681da47d87d74faf02a81f0e21896a12360 Mon Sep 17 00:00:00 2001 From: brunoherbelin Date: Sun, 26 Apr 2026 21:52:17 +0200 Subject: [PATCH 1/5] New support for user-defined range of values for ImageFilter GUI --- src/Filter/ImageFilter.cpp | 29 +++++++++++++++-- src/Filter/ImageFilter.h | 4 +++ src/Visitor/ImGuiVisitor.cpp | 57 +++++++++++++++++++++++++++++---- src/Visitor/ImGuiVisitor.h | 2 ++ src/Window/ShaderEditWindow.cpp | 5 ++- 5 files changed, 85 insertions(+), 12 deletions(-) diff --git a/src/Filter/ImageFilter.cpp b/src/Filter/ImageFilter.cpp index fd0b310..d4d60f3 100644 --- a/src/Filter/ImageFilter.cpp +++ b/src/Filter/ImageFilter.cpp @@ -130,9 +130,9 @@ FilteringProgram::FilteringProgram(const std::string &name, const std::string &f } FilteringProgram::FilteringProgram(const FilteringProgram &other) : - name_(other.name_), filename_(other.filename_), code_(other.code_), + name_(other.name_), filename_(other.filename_), code_(other.code_), two_pass_filter_(other.two_pass_filter_), parameters_(other.parameters_), - textures_(other.textures_) + ranges_(other.ranges_), textures_(other.textures_) { } @@ -145,6 +145,8 @@ FilteringProgram& FilteringProgram::operator= (const FilteringProgram& other) this->code_ = other.code_; this->parameters_.clear(); this->parameters_ = other.parameters_; + this->ranges_.clear(); + this->ranges_ = other.ranges_; this->textures_.clear(); this->textures_ = other.textures_; this->two_pass_filter_ = other.two_pass_filter_; @@ -185,6 +187,15 @@ void FilteringProgram::removeParameter(const std::string &p) parameters_.erase(p); } +std::pair< float, float> FilteringProgram::getParameterRange(const std::string &p) +{ + std::pair< float, float> default_range(0.f, 1.f); + if (ranges_.find(p) != ranges_.end()) + return ranges_[p]; + else + return default_range; +} + bool FilteringProgram::hasTexture(const std::string &t) { return textures_.find(t) != textures_.end(); @@ -502,6 +513,7 @@ FilteringProgram ImageFilter::program () const #define REGEX_VARIABLE_NAME "[a-zA-Z_][\\w]+" #define REGEX_UNIFORM_VALUE "(\\s*=\\s*[[:digit:]]+(\\.[[:digit:]]*)?)?\\s*\\;" #define REGEX_SAMPLER_DECLARATION "uniform\\s+sampler2D\\s+" +#define REGEX_RANGE_COMMENT "\\/\\/\\s*range\\s*\\[\\s*([+-]?[[:digit:]]+(\\.[[:digit:]]*)?)\\s+([+-]?[[:digit:]]+(\\.[[:digit:]]*)?)\\s*\\]" void ImageFilter::setProgram(const FilteringProgram &f, std::promise *ret) { @@ -568,6 +580,19 @@ void ImageFilter::setProgram(const FilteringProgram &f, std::promise parameters_; + std::map< std::string, std::pair > ranges_; // list of texture inputs : uniform sampler2D names and source id std::map< std::string, uint64_t > textures_; @@ -71,6 +72,9 @@ public: bool hasParameter(const std::string &p); void removeParameter(const std::string &p); + void setParameterRange(const std::string &p, float min, float max) { ranges_[p] = std::make_pair(min, max); } + std::pair< float, float> getParameterRange(const std::string &p); + // set the list of textures inline void setTextures(const std::map< std::string, uint64_t > &textures) { textures_ = textures; } diff --git a/src/Visitor/ImGuiVisitor.cpp b/src/Visitor/ImGuiVisitor.cpp index 43352b1..9660745 100644 --- a/src/Visitor/ImGuiVisitor.cpp +++ b/src/Visitor/ImGuiVisitor.cpp @@ -1143,21 +1143,27 @@ void ImGuiVisitor::visit (ResampleFilter& f) } } -void list_parameters_(ImageFilter &f, std::ostringstream &oss) +void ImGuiVisitor::list_parameters_(ImageFilter &f, std::ostringstream &oss, bool editrange) { ImGuiIO& io = ImGui::GetIO(); - std::map filter_parameters = f.program().parameters(); + FilteringProgram prog = f.program(); + std::map filter_parameters = prog.parameters(); for (auto param = filter_parameters.rbegin(); param != filter_parameters.rend(); ++param) { ImGui::PushID( param->first.c_str() ); float v = param->second; - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - if (ImGui::SliderFloat( "##ImageFilterParameterEdit", &v, 0.f, 1.f, "%.2f")) { + auto range = prog.getParameterRange(param->first); + float rmin = range.first, rmax = range.second; + if (editrange) + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN - 1.6f * ImGui::GetTextLineHeightWithSpacing()); + else + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + if (ImGui::SliderFloat( "##ImageFilterParameterEdit", &v, rmin, rmax, "%.2f")) { f.setProgramParameter(param->first, v); } if (ImGui::IsItemHovered() && io.MouseWheel != 0.f ){ - v = CLAMP( v + 0.01f * io.MouseWheel, 0.f, 1.f); + v = CLAMP( v + 0.01f * (rmax - rmin) * io.MouseWheel, rmin, rmax); f.setProgramParameter(param->first, v); oss << " " << param->first << " " << std::setprecision(3) << v; Action::manager().store(oss.str()); @@ -1166,9 +1172,46 @@ void list_parameters_(ImageFilter &f, std::ostringstream &oss) oss << " " << param->first << " " << std::setprecision(3) <second; Action::manager().store(oss.str()); } + if (editrange) { + ImGui::SameLine(0, IMGUI_SAME_LINE); + if (ImGui::Button( "[ ]", ImVec2(1.6f * ImGui::GetTextLineHeight(), 0)) ) { + // + // Add '// range [ min max ]' to uniform declaration in shader code + // + // 1 get code + std::pair< std::string, std::string > code = prog.code(); + std::string &glsl = code.first; + + // 2 find uniform declaration for this parameter : "uniform float " + param->first + std::string decl_search = std::string("uniform float ") + param->first; + auto decl_pos = glsl.find(decl_search); + if (decl_pos != std::string::npos) { + auto semi_pos = glsl.find(';', decl_pos); + auto eol_pos = glsl.find('\n', decl_pos); + if (semi_pos != std::string::npos && (eol_pos == std::string::npos || semi_pos < eol_pos)) { + // 4 add comment with range at the end of the line + char range_comment[64]; + snprintf(range_comment, sizeof(range_comment), " // range [%.6g %.6g]", rmin, rmax); + auto replace_start = semi_pos + 1; + auto replace_end = (eol_pos != std::string::npos) ? eol_pos : glsl.size(); + glsl.replace(replace_start, replace_end - replace_start, range_comment); + } + } + // 3 set code with new comment + prog.setCode(code); + f.setProgram(prog); + // 4 show in GUI + UserInterface::manager().shadercontrol.setVisible(true); + } + if (ImGui::IsItemHovered()) { + char text_buf[512]; + ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "// range [%.6g %.6g]\nEdit code to modify", rmin, rmax); + ImGuiToolkit::ToolTip(text_buf); + } + } ImGui::SameLine(0, IMGUI_SAME_LINE); if (ImGuiToolkit::TextButton( param->first.c_str() )) { - v = 0.5f; + v = (rmax - rmin) * 0.5f + rmin; f.setProgramParameter(param->first, v); oss << " " << param->first << " " << std::setprecision(3) << v; Action::manager().store(oss.str()); @@ -1480,7 +1523,7 @@ void ImGuiVisitor::visit (ImageFilter& f) // List of parameters & textures oss << "Custom "; - list_parameters_(f, oss); + list_parameters_(f, oss, true); list_textures_(f, oss); } diff --git a/src/Visitor/ImGuiVisitor.h b/src/Visitor/ImGuiVisitor.h index a7c7499..8ddacbc 100644 --- a/src/Visitor/ImGuiVisitor.h +++ b/src/Visitor/ImGuiVisitor.h @@ -11,6 +11,8 @@ class ImGuiVisitor: public Visitor InfoVisitor info; std::ostringstream oss; + void list_parameters_(class ImageFilter &f, std::ostringstream &oss, bool editrange = false); + public: ImGuiVisitor(); inline void reset () { info.reset(); } diff --git a/src/Window/ShaderEditWindow.cpp b/src/Window/ShaderEditWindow.cpp index 083d864..cd15764 100644 --- a/src/Window/ShaderEditWindow.cpp +++ b/src/Window/ShaderEditWindow.cpp @@ -142,9 +142,8 @@ void ShaderEditWindow::setVisible(bool on) Settings::application.widget.shader_editor = on; - // reset current - current_ = nullptr; - _cs_id = 0; + // force refresh + Refresh(); } bool ShaderEditWindow::Visible() const From 4bc0cdc2203dca11cc5b71b047ad74ba129c9640 Mon Sep 17 00:00:00 2001 From: brunoherbelin Date: Tue, 28 Apr 2026 22:56:21 +0200 Subject: [PATCH 2/5] Add OSC target filter to set GLSL code directly Co-authored-by: Copilot --- src/Source/SourceCallback.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Source/SourceCallback.cpp b/src/Source/SourceCallback.cpp index c22eedd..0b65ad8 100644 --- a/src/Source/SourceCallback.cpp +++ b/src/Source/SourceCallback.cpp @@ -1298,6 +1298,11 @@ void SetFilter::update(Source *s, float dt) Log::Info("Filter Alpha: unknown operation '%s'", target_method_.c_str()); } break; case FrameBufferFilter::FILTER_IMAGE: { + ImageFilter *__f = dynamic_cast(clonesrc->filter()); + if (__f == nullptr) { + Log::Info("Filter Image: failed to get image filter"); + break; + } // Open the file std::ifstream file(target_method_); // Check if the file is opened successfully @@ -1309,11 +1314,14 @@ void SetFilter::update(Source *s, float dt) prog.setName(target_method_); prog.setCode({fileContent, ""}); // get alpha filter - ImageFilter *__f = dynamic_cast(clonesrc->filter()); __f->setProgram(prog); } - else - Log::Info("Filter Custom: can't read file '%s'", target_method_.c_str()); + else { + FilteringProgram prog; + prog.setName( __f->program().name() ); + prog.setCode({target_method_, ""}); + __f->setProgram(prog); + } // Close the file file.close(); } break; From 510ad0be9e5b7d6390e5cce6e799b2eabbe6e504 Mon Sep 17 00:00:00 2001 From: brunoherbelin Date: Fri, 1 May 2026 18:27:15 +0200 Subject: [PATCH 3/5] New OSC command to set code to Clone filter or Shader source Co-authored-by: Copilot --- src/ControlManager.cpp | 11 +++++- src/ControlManager.h | 1 + src/Source/SourceCallback.cpp | 63 +++++++++++++++++++++++++++++++---- src/Source/SourceCallback.h | 25 +++++++++++--- 4 files changed, 88 insertions(+), 12 deletions(-) diff --git a/src/ControlManager.cpp b/src/ControlManager.cpp index 25a7dfb..9a6bbc5 100644 --- a/src/ControlManager.cpp +++ b/src/ControlManager.cpp @@ -1162,7 +1162,16 @@ bool Control::receiveSourceAttribute(Source *target, const std::string &attribut else arguments >> t >> osc::EndMessage; if (str && uniform_value != NAN) { - target->call(new SetFilterUniform(std::string(str), uniform_value, t), true); + target->call(new SetUniform(std::string(str), uniform_value, t), true); + } + } + /// e.g. '/vimix/current/code s 'void mainImage(out vec4 o, in vec2 i) { o = vec4( i / iResolution.xy,1,1); }' + else if (attribute.compare(OSC_SOURCE_CODE) == 0) { + std::string code; + const char *str = nullptr; + arguments >> str >> osc::EndMessage; + if (str && strlen(str) > 0) { + target->call(new SetCode(std::string(str)), true); } } /// e.g. '/vimix/current/filter sf blur 0.5' diff --git a/src/ControlManager.h b/src/ControlManager.h index 902750f..262c0d6 100644 --- a/src/ControlManager.h +++ b/src/ControlManager.h @@ -77,6 +77,7 @@ #define OSC_SOURCE_TEXANGLE "/texture_angle" #define OSC_SOURCE_FILTER "/filter" #define OSC_SOURCE_UNIFORM "/uniform" +#define OSC_SOURCE_CODE "/code" #define OSC_SOURCE_BLENDING "/blending" #define OSC_SOURCE_FLAG "/flag" diff --git a/src/Source/SourceCallback.cpp b/src/Source/SourceCallback.cpp index 0b65ad8..5001181 100644 --- a/src/Source/SourceCallback.cpp +++ b/src/Source/SourceCallback.cpp @@ -23,6 +23,7 @@ #include "Source.h" #include "ImageProcessingShader.h" #include "CloneSource.h" +#include "ShaderSource.h" #include "Filter/ImageFilter.h" #include "Filter/DelayFilter.h" #include "MediaSource.h" @@ -1417,7 +1418,7 @@ void SetFilter::accept(Visitor& v) v.visit(*this); } -SetFilterUniform::SetFilterUniform(const std::string &uniform, float value, float ms) +SetUniform::SetUniform(const std::string &uniform, float value, float ms) : SourceCallback() , uniform_(uniform) , target_(value) @@ -1427,12 +1428,13 @@ SetFilterUniform::SetFilterUniform(const std::string &uniform, float value, floa imagefilter = nullptr; } -void SetFilterUniform::update(Source *s, float dt) +void SetUniform::update(Source *s, float dt) { SourceCallback::update(s, dt); CloneSource *clonesrc = dynamic_cast(s); + ShaderSource *shadersrc = dynamic_cast(s); - if (s->locked() || !clonesrc) + if (s->locked() || ( !clonesrc && !shadersrc ) ) status_ = FINISHED; @@ -1482,22 +1484,69 @@ void SetFilterUniform::update(Source *s, float dt) } -void SetFilterUniform::multiply (float factor) +void SetUniform::multiply (float factor) { target_ *= factor; } -SourceCallback *SetFilterUniform::clone() const +SourceCallback *SetUniform::clone() const { - return new SetFilterUniform(uniform_, target_, duration_); + return new SetUniform(uniform_, target_, duration_); } -void SetFilterUniform::accept(Visitor& v) +void SetUniform::accept(Visitor& v) { SourceCallback::accept(v); v.visit(*this); } +SetCode::SetCode(const std::string &code) : SourceCallback(), + code_(code) +{ +} + +void SetCode::update(Source *s, float dt) +{ + SourceCallback::update(s, dt); + CloneSource *clonesrc = dynamic_cast(s); + ShaderSource *shadersrc = dynamic_cast(s); + + if (s->locked() || ( !clonesrc && !shadersrc ) ) + status_ = FINISHED; + + // apply when ready + if (status_ == READY) { + // if there is an image filter in the source + ImageFilter *__f = nullptr; + if (clonesrc) + __f = dynamic_cast(clonesrc->filter()); + else + __f = dynamic_cast(shadersrc->filter()); + if (__f) { + FilteringProgram prog; + prog.setName( __f->program().name() ); + prog.setCode({code_, ""}); + __f->setProgram(prog); + } + else + Log::Info("setCode: failed to get image filter"); + + status_ = FINISHED; + } +} + +SourceCallback *SetCode::clone() const +{ + return new SetCode(code_); +} + +void SetCode::accept(Visitor& v) +{ + SourceCallback::accept(v); + v.visit(*this); +} + + SetBlending::SetBlending(const std::string &method) : SourceCallback() , target_method_(method) diff --git a/src/Source/SourceCallback.h b/src/Source/SourceCallback.h index 19182ea..0f1c37a 100644 --- a/src/Source/SourceCallback.h +++ b/src/Source/SourceCallback.h @@ -51,7 +51,8 @@ public: CALLBACK_INVERT, CALLBACK_POSTERIZE, CALLBACK_FILTER, - CALLBACK_FILTER_UNIFORM, + CALLBACK_UNIFORM, + CALLBACK_CODE, CALLBACK_BLENDING, CALLBACK_INVALID } CallbackType; @@ -508,7 +509,7 @@ public: void accept (Visitor& v) override; }; -class SetFilterUniform : public SourceCallback +class SetUniform : public SourceCallback { std::string uniform_; float target_; @@ -517,7 +518,7 @@ class SetFilterUniform : public SourceCallback class ImageFilter *imagefilter; public: - SetFilterUniform (const std::string &uniform = std::string(), + SetUniform (const std::string &uniform = std::string(), float value = NAN, float ms = 0.f); float value () const { return target_; } @@ -528,7 +529,23 @@ public: void update (Source *s, float) override; void multiply (float factor) override; SourceCallback *clone () const override; - CallbackType type () const override { return CALLBACK_FILTER_UNIFORM; } + CallbackType type () const override { return CALLBACK_UNIFORM; } + void accept (Visitor& v) override; +}; + +class SetCode : public SourceCallback +{ + std::string code_; + +public: + SetCode (const std::string &code = std::string()); + + const std::string &code () const { return code_; } + void setCode (const std::string &c) { code_ = c; } + + void update (Source *s, float) override; + SourceCallback *clone () const override; + CallbackType type () const override { return CALLBACK_CODE; } void accept (Visitor& v) override; }; From 22fcd4a33c72172dfe399d023b96c79002515b7e Mon Sep 17 00:00:00 2001 From: brunoherbelin Date: Fri, 1 May 2026 20:38:42 +0200 Subject: [PATCH 4/5] Fixed OSC target code and filter; compile and log, accept either code or filename Co-authored-by: Copilot --- src/ControlManager.cpp | 3 ++ src/Source/SourceCallback.cpp | 79 +++++++++++++++++++++++---------- src/Source/SourceCallback.h | 3 ++ src/Window/ShaderEditWindow.cpp | 2 +- 4 files changed, 62 insertions(+), 25 deletions(-) diff --git a/src/ControlManager.cpp b/src/ControlManager.cpp index 9a6bbc5..3e2b277 100644 --- a/src/ControlManager.cpp +++ b/src/ControlManager.cpp @@ -1172,6 +1172,9 @@ bool Control::receiveSourceAttribute(Source *target, const std::string &attribut arguments >> str >> osc::EndMessage; if (str && strlen(str) > 0) { target->call(new SetCode(std::string(str)), true); + // show and refresh source editor if source is currently selected + if (target == Mixer::manager().currentSource()) + UserInterface::manager().showSourceEditor(target); } } /// e.g. '/vimix/current/filter sf blur 0.5' diff --git a/src/Source/SourceCallback.cpp b/src/Source/SourceCallback.cpp index 5001181..404a6d3 100644 --- a/src/Source/SourceCallback.cpp +++ b/src/Source/SourceCallback.cpp @@ -1304,27 +1304,18 @@ void SetFilter::update(Source *s, float dt) Log::Info("Filter Image: failed to get image filter"); break; } - // Open the file + FilteringProgram prog; + prog.setName( __f->program().name() ); + // try to open a file std::ifstream file(target_method_); // Check if the file is opened successfully if (file.is_open()) { - // Read the content of the file into an std::string - std::string fileContent((std::istreambuf_iterator(file)), - std::istreambuf_iterator()); - FilteringProgram prog; - prog.setName(target_method_); - prog.setCode({fileContent, ""}); - // get alpha filter - __f->setProgram(prog); + prog.setFilename(target_method_); + __f->setProgram(prog); + file.close(); } - else { - FilteringProgram prog; - prog.setName( __f->program().name() ); - prog.setCode({target_method_, ""}); - __f->setProgram(prog); - } - // Close the file - file.close(); + else + Log::Info("Filter Image: failed to open file '%s'", target_method_.c_str()); } break; default: break; @@ -1501,7 +1492,7 @@ void SetUniform::accept(Visitor& v) } SetCode::SetCode(const std::string &code) : SourceCallback(), - code_(code) + code_(code), compilation_(nullptr) { } @@ -1514,6 +1505,25 @@ void SetCode::update(Source *s, float dt) if (s->locked() || ( !clonesrc && !shadersrc ) ) status_ = FINISHED; + // update if there is an ongoing compilation + if (status_ == ACTIVE && compilation_ != nullptr ) { + + static std::chrono::milliseconds timeout = std::chrono::milliseconds(4); + if (compilation_return_.wait_for(timeout) == std::future_status::ready ) + { + // get message returned from compilation + std::string status_ = compilation_return_.get(); + Log::Info("setCode: %s", status_.c_str()); + + // end compilation promise + delete compilation_; + compilation_ = nullptr; + + // done + status_ = FINISHED; + } + } + // apply when ready if (status_ == READY) { // if there is an image filter in the source @@ -1523,16 +1533,37 @@ void SetCode::update(Source *s, float dt) else __f = dynamic_cast(shadersrc->filter()); if (__f) { + + // set code to the image filter and start compilation FilteringProgram prog; prog.setName( __f->program().name() ); - prog.setCode({code_, ""}); - __f->setProgram(prog); - } - else - Log::Info("setCode: failed to get image filter"); - status_ = FINISHED; + // try to open a file + std::ifstream file(code_); + // Check if the file is opened successfully + if (file.is_open()) { + prog.setFilename(code_); + } + else { + prog.resetFilename(); + prog.setCode({code_, ""}); + } + file.close(); // close anyways + + // compile new code + compilation_ = new std::promise(); + __f->setProgram(prog, compilation_); + compilation_return_ = compilation_->get_future(); + + // building + status_ = ACTIVE; + } + else { + Log::Info("setCode: failed to get image filter"); + status_ = FINISHED; + } } + } SourceCallback *SetCode::clone() const diff --git a/src/Source/SourceCallback.h b/src/Source/SourceCallback.h index 0f1c37a..c2bf5f2 100644 --- a/src/Source/SourceCallback.h +++ b/src/Source/SourceCallback.h @@ -3,6 +3,7 @@ #include #include +#include #include class Visitor; @@ -536,6 +537,8 @@ public: class SetCode : public SourceCallback { std::string code_; + std::promise *compilation_; + std::future compilation_return_; public: SetCode (const std::string &code = std::string()); diff --git a/src/Window/ShaderEditWindow.cpp b/src/Window/ShaderEditWindow.cpp index cd15764..7e50260 100644 --- a/src/Window/ShaderEditWindow.cpp +++ b/src/Window/ShaderEditWindow.cpp @@ -70,7 +70,7 @@ void saveEditorText(const std::string &filename) /// SHADER EDITOR /// /// -ShaderEditWindow::ShaderEditWindow() : WorkspaceWindow("Shader"), _cs_id(0), current_(nullptr), show_shader_inputs_(false) +ShaderEditWindow::ShaderEditWindow() : WorkspaceWindow("Shader"), _cs_id(0), current_(nullptr), show_shader_inputs_(false), compilation_(nullptr) { auto lang = TextEditor::LanguageDefinition::GLSL(); From 2d9fac8e9f1c2c7db9df11e3a48710a9c3b611d4 Mon Sep 17 00:00:00 2001 From: brunoherbelin Date: Fri, 1 May 2026 21:18:20 +0200 Subject: [PATCH 5/5] Update webpage info and screenshot --- docs/index.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/index.md b/docs/index.md index bd996cd..0add76d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,16 +1,16 @@ vimix performs **graphical mixing and blending** of several movie clips and -computer generated graphics, with image processing effects in real-time. +computer generated graphics, with image processing effects in real-time. Vimix supports GPU accelerated decoding and encoding of videos. Its intuitive and hands-on user interface gives direct control on image opacity and shape for producing live graphics during concerts and VJ-ing sessions. -The output image is typically projected full-screen on external monitors -or projectors, but can be streamed live (SRT) or recorded (without audio). +Video mapping can be configured for projection on all connected monitors. +The output can also be live streamed (SRT, shared memory) or recorded. -![screenshot](vimix_screenshot.png) +![screenshot_vimix](https://github.com/brunoherbelin/vimix/blob/22653d5367e296f977c75e3717cb5e9065c6e09f/docs/vimix_screenshot.png) -Check the [Graphical User Manual](https://github.com/brunoherbelin/vimix/wiki/User-manual) or [demo videos](https://vimeo.com/vimix) to discover vimix. -Watch this selection of [videos by Jean Detheux](https://vimeo.com/showcase/7871359) to see what vimix can do. +Check the [User Manual](https://github.com/brunoherbelin/vimix/wiki/User-manual) or [demo videos](https://vimeo.com/vimix) to discover vimix. +Watch this selection of [videos by Jean Detheux](https://vimeo.com/showcase/7871359) or [video tutorials](https://github.com/brunoherbelin/vimix/wiki/Video-tutorials) to see what vimix can do. ## Install vimix @@ -20,9 +20,11 @@ Watch this selection of [videos by Jean Detheux](https://vimeo.com/showcase/7871 Install the [flathub](https://flathub.org/apps/details/io.github.brunoherbelin.Vimix), [snap](https://snapcraft.io/vimix) or [Deb](https://tracker.debian.org/pkg/vimix) package. +Get Latest Beta version: [build your own flatpak](https://github.com/brunoherbelin/vimix/blob/master/flatpak/README.md). + ### Mac OSX -Download package from [Github Releases](https://github.com/brunoherbelin/vimix/releases). +Download packages from [Github Releases](https://github.com/brunoherbelin/vimix/releases). ### Windows @@ -31,19 +33,18 @@ Download package from [Github Releases](https://github.com/brunoherbelin/vimix/r ## Control vimix with OSC You can control remotely vimix with [OSC](https://en.wikipedia.org/wiki/Open_Sound_Control), using [TouchOSC Mk1](https://github.com/brunoherbelin/vimix/wiki/TouchOSC-companion) -or using the [vimix OSC API](https://github.com/brunoherbelin/vimix/wiki/Open-Sound-Control-API) from your OSC applications. +or using the [vimix OSC API](https://github.com/brunoherbelin/vimix/wiki/Open-Sound-Control-API) to connect from [OSC applications](https://github.com/brunoherbelin/vimix/wiki/OSC-Mapping). ## About -vimix is free and open source (GPL3+). +Vimix is free and open source (GPL3+). -vimix is the successor of [GLMixer](https://sourceforge.net/projects/glmixer/), benefiting +Vimix is the successor of [GLMixer](https://sourceforge.net/projects/glmixer/), benefiting from 10 years of refinement of User-Experience design since its [first draft](https://sourceforge.net/p/glmixer/wiki/GLMixer%20History/). -vimix is open to [feature requests and bug reports](https://github.com/brunoherbelin/vimix/issues). - -vimix welcomes contributions and support: check the [wiki](https://github.com/brunoherbelin/vimix/wiki) for more info. +Vimix is open to [feature requests and bug reports](https://github.com/brunoherbelin/vimix/issues). +Vimix welcomes contributions and support: check the [wiki](https://github.com/brunoherbelin/vimix/wiki) for more info.