├── lib ├── .clang-format └── CMakeLists.txt ├── .gitignore ├── res ├── fonts │ ├── Inter-Bold.ttf │ ├── Inter-Italic.ttf │ ├── Inter-Regular.ttf │ ├── Inter-BoldItalic.ttf │ ├── JetBrainsMono-Bold.ttf │ ├── JetBrainsMono-Italic.ttf │ ├── JetBrainsMono-Regular.ttf │ └── JetBrainsMono-BoldItalic.ttf └── pitch_shifter.dsp ├── screenshots └── FaustGraphLayoutAndSelectedCode.png ├── src ├── Core │ ├── TextEditor │ │ ├── LanguageID.h │ │ ├── README.md │ │ ├── TextBufferPalette.h │ │ ├── TextBufferStyle.h │ │ ├── TextEditor.h │ │ ├── queries │ │ │ ├── README.md │ │ │ └── json │ │ │ │ └── highlights.scm │ │ ├── TextEditor.cpp │ │ ├── TextInputEdit.h │ │ ├── LineChar.h │ │ ├── TextBuffer.h │ │ └── TextBufferAction.h │ ├── FileDialog │ │ ├── FileDialogManager.h │ │ ├── FileDialogDataJson.h │ │ ├── FileDialogAction.h │ │ ├── FileDialogDemo.h │ │ ├── FileDialog.h │ │ └── FileDialogData.h │ ├── Project │ │ ├── ProjectSettings.cpp │ │ ├── ProjectSettings.h │ │ ├── Preferences.cpp │ │ ├── ProjectAction.h │ │ ├── Preferences.h │ │ └── ProjectContext.h │ ├── MenuItemDrawable.h │ ├── Info │ │ ├── Info.h │ │ └── Info.cpp │ ├── UI │ │ ├── HelpMarker.h │ │ ├── NamesAndValues.h │ │ ├── UIContext.h │ │ ├── HelpMarker.cpp │ │ ├── InvisibleButton.h │ │ ├── JsonTree.h │ │ ├── InvisibleButton.cpp │ │ ├── Styling.cpp │ │ ├── Styling.h │ │ ├── JsonTree.cpp │ │ ├── Fonts.h │ │ ├── Fonts.cpp │ │ └── Widgets.h │ ├── Store │ │ ├── StorePatch.h │ │ ├── StoreAction.h │ │ ├── Patch │ │ │ ├── PatchJson.h │ │ │ ├── Patch.h │ │ │ ├── PatchJson.cpp │ │ │ ├── PatchOp.h │ │ │ └── Patch.cpp │ │ ├── StoreAction.cpp │ │ ├── IdPairs.cpp │ │ ├── IdPairs.h │ │ ├── Store.h │ │ ├── StoreHistory.h │ │ └── StoreBase.h │ ├── Action │ │ ├── ActionMoment.h │ │ ├── Actionable.h │ │ ├── ActionableProducer.h │ │ ├── Action.cpp │ │ ├── ActionProducer.h │ │ ├── ActionMenuItem.h │ │ └── DefineAction.h │ ├── Primitive │ │ ├── BoolAction.h │ │ ├── EnumAction.h │ │ ├── IntAction.h │ │ ├── FloatAction.h │ │ ├── UIntAction.h │ │ ├── StringAction.h │ │ ├── FlagsAction.h │ │ ├── Bool.h │ │ ├── Int.h │ │ ├── Float.h │ │ ├── Enum.h │ │ ├── Flags.h │ │ ├── Primitive.h │ │ └── UInt.h │ ├── ProducerComponentArgs.h │ ├── ComponentArgs.h │ ├── Helper │ │ ├── Path.h │ │ ├── Variant.h │ │ ├── File.h │ │ ├── Time.h │ │ ├── Hex.h │ │ ├── Color.h │ │ ├── String.h │ │ ├── File.cpp │ │ └── String.cpp │ ├── HelpInfo.cpp │ ├── Container │ │ ├── AdjacencyListAction.h │ │ ├── SetAction.h │ │ ├── Set.h │ │ ├── NavigableAction.h │ │ ├── Vec2Action.h │ │ ├── Vector.h │ │ ├── AdjacencyList.h │ │ ├── Colors.h │ │ ├── Navigable.h │ │ ├── Vec2.h │ │ ├── VectorAction.h │ │ └── Optional.h │ ├── CoreActionProducer.h │ ├── ChangeListener.h │ ├── WindowsAction.h │ ├── CoreActionHandler.h │ ├── Demo │ │ ├── Demo.cpp │ │ └── Demo.h │ ├── ActionProducerComponent.h │ ├── Style │ │ ├── StyleAction.h │ │ └── ProjectStyle.h │ ├── HelpInfo.h │ ├── ActionableComponent.h │ ├── Windows.h │ ├── String.h │ ├── CoreAction.h │ ├── ID.h │ ├── Scalar.h │ ├── Windows.cpp │ ├── Json.h │ ├── ImGuiSettings.h │ └── Log.h ├── Audio │ ├── Sample.h │ ├── Graph │ │ ├── ma_monitor_node │ │ │ ├── fft_data.h │ │ │ ├── ma_monitor_node.h │ │ │ └── window_functions.h │ │ ├── ma_helper.h │ │ ├── AudioGraphAction.h │ │ ├── ma_gainer_node │ │ │ ├── ma_gainer_node.h │ │ │ └── ma_gainer_node.cpp │ │ ├── ma_channel_converter_node │ │ │ ├── ma_channel_converter_node.h │ │ │ └── ma_channel_converter_node.cpp │ │ ├── ma_waveform_node │ │ │ ├── ma_waveform_node.h │ │ │ └── ma_waveform_node.cpp │ │ ├── ma_panner_node │ │ │ ├── ma_panner_node.h │ │ │ └── ma_panner_node.cpp │ │ ├── ma_data_passthrough_node │ │ │ ├── ma_data_passthrough_node.h │ │ │ └── ma_data_passthrough_node.cpp │ │ └── ma_faust_node │ │ │ └── ma_faust_node.h │ ├── AudioAction.h │ ├── Faust │ │ ├── FaustAction.h │ │ ├── FaustDSPAction.h │ │ ├── FaustDSPListener.h │ │ ├── FaustGraphAction.h │ │ ├── FaustGraphStyleAction.h │ │ ├── FaustParamsStyle.cpp │ │ ├── FaustParamType.h │ │ ├── FaustParamsContainer.h │ │ ├── FaustNode.h │ │ ├── FaustParamGroup.h │ │ ├── FaustParam.h │ │ ├── FaustGraph.h │ │ ├── FaustParams.cpp │ │ ├── FaustParamBase.cpp │ │ ├── FaustParams.h │ │ ├── FaustParamBase.h │ │ ├── FaustNode.cpp │ │ ├── FaustParamsUI.h │ │ ├── FaustParamsStyle.h │ │ └── FaustGraphStyle.h │ ├── AudioIO.h │ ├── WaveformNode.h │ ├── Device │ │ ├── DeviceDataFormat.h │ │ ├── DeviceDataFormat.cpp │ │ └── AudioDevice.h │ ├── Audio.h │ ├── WaveformNode.cpp │ └── Audio.cpp ├── FlowGridAction.h ├── FlowGrid.h ├── FlowGrid.cpp └── main.cpp ├── script ├── Format ├── Clean └── Build ├── .clang-format ├── .gitmodules ├── doc └── todo.md └── CMakeLists.txt /lib/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | DisableFormat: true 3 | SortIncludes: Never 4 | ... 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build*/ 3 | cmake-build 4 | cmake-build-debug 5 | .vscode/settings.json 6 | -------------------------------------------------------------------------------- /res/fonts/Inter-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khiner/flowgrid/HEAD/res/fonts/Inter-Bold.ttf -------------------------------------------------------------------------------- /res/fonts/Inter-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khiner/flowgrid/HEAD/res/fonts/Inter-Italic.ttf -------------------------------------------------------------------------------- /res/fonts/Inter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khiner/flowgrid/HEAD/res/fonts/Inter-Regular.ttf -------------------------------------------------------------------------------- /res/fonts/Inter-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khiner/flowgrid/HEAD/res/fonts/Inter-BoldItalic.ttf -------------------------------------------------------------------------------- /res/fonts/JetBrainsMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khiner/flowgrid/HEAD/res/fonts/JetBrainsMono-Bold.ttf -------------------------------------------------------------------------------- /res/fonts/JetBrainsMono-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khiner/flowgrid/HEAD/res/fonts/JetBrainsMono-Italic.ttf -------------------------------------------------------------------------------- /res/fonts/JetBrainsMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khiner/flowgrid/HEAD/res/fonts/JetBrainsMono-Regular.ttf -------------------------------------------------------------------------------- /res/fonts/JetBrainsMono-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khiner/flowgrid/HEAD/res/fonts/JetBrainsMono-BoldItalic.ttf -------------------------------------------------------------------------------- /screenshots/FaustGraphLayoutAndSelectedCode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khiner/flowgrid/HEAD/screenshots/FaustGraphLayoutAndSelectedCode.png -------------------------------------------------------------------------------- /src/Core/TextEditor/LanguageID.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | enum class LanguageID { 4 | None, 5 | Cpp, 6 | Json, 7 | Faust, 8 | }; 9 | -------------------------------------------------------------------------------- /src/Core/FileDialog/FileDialogManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct FileDialogManager { 4 | static void Init(); 5 | static void Uninit(); 6 | }; 7 | -------------------------------------------------------------------------------- /src/Core/Project/ProjectSettings.cpp: -------------------------------------------------------------------------------- 1 | #include "ProjectSettings.h" 2 | 3 | void ProjectSettings::Render() const { 4 | GestureDurationSec.Draw(); 5 | } 6 | -------------------------------------------------------------------------------- /src/Audio/Sample.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | using Sample = float; 4 | using Real = Sample; 5 | 6 | #ifndef FAUSTFLOAT 7 | #define FAUSTFLOAT Sample 8 | #endif 9 | -------------------------------------------------------------------------------- /src/Audio/Graph/ma_monitor_node/fft_data.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct fft_data { 6 | fftwf_plan plan; 7 | fftwf_complex *data; 8 | }; 9 | -------------------------------------------------------------------------------- /src/Core/MenuItemDrawable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct MenuItemDrawable { 4 | virtual ~MenuItemDrawable() = default; 5 | virtual void MenuItem() const = 0; 6 | }; 7 | -------------------------------------------------------------------------------- /src/Core/Info/Info.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Component.h" 4 | 5 | struct Info : Component { 6 | using Component::Component; 7 | 8 | protected: 9 | void Render() const override; 10 | }; 11 | -------------------------------------------------------------------------------- /src/Core/UI/HelpMarker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // Similar to `imgui_demo.cpp`'s `HelpMarker`. 6 | namespace flowgrid { 7 | void HelpMarker(std::string_view); 8 | } // namespace flowgrid 9 | -------------------------------------------------------------------------------- /src/FlowGridAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Audio/AudioAction.h" 4 | 5 | namespace Action { 6 | namespace FlowGrid { 7 | using Any = Audio::Any; 8 | } // namespace FlowGrid 9 | } // namespace Action 10 | -------------------------------------------------------------------------------- /src/Audio/AudioAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Faust/FaustAction.h" 4 | #include "Graph/AudioGraphAction.h" 5 | 6 | DefineActionType( 7 | Audio, 8 | 9 | using Any = Combine; 10 | ); 11 | -------------------------------------------------------------------------------- /src/Core/Store/StorePatch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Patch/Patch.h" 4 | 5 | struct PersistentStore; 6 | 7 | // Create a patch comparing the provided store maps. 8 | Patch CreatePatch(const PersistentStore &, const PersistentStore &, ID base_id); 9 | -------------------------------------------------------------------------------- /src/Core/FileDialog/FileDialogDataJson.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "FileDialogData.h" 4 | 5 | #include "nlohmann/json.hpp" 6 | 7 | NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(FileDialogData, OwnerId, Title, Filters, FilePath, DefaultFileName, SaveMode, MaxNumSelections, Flags); 8 | -------------------------------------------------------------------------------- /src/Core/Action/ActionMoment.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Helper/Time.h" 4 | 5 | // An `ActionMoment` is an action paired with its queue time. 6 | template struct ActionMoment { 7 | ActionType Action; 8 | TimePoint QueueTime; 9 | }; 10 | -------------------------------------------------------------------------------- /src/Core/UI/NamesAndValues.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | struct NamesAndValues { 7 | size_t Size() const { return names.size(); } 8 | 9 | std::vector names{}; 10 | std::vector values{}; 11 | }; 12 | -------------------------------------------------------------------------------- /src/Core/Primitive/BoolAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Action/DefineAction.h" 4 | 5 | DefineActionType( 6 | Bool, 7 | DefineComponentAction(Toggle, Saved, SameIdMerge, ""); 8 | ComponentActionJson(Toggle); 9 | 10 | using Any = ActionVariant; 11 | ); 12 | -------------------------------------------------------------------------------- /src/Audio/Faust/FaustAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "FaustDSPAction.h" 4 | #include "FaustGraphAction.h" 5 | #include "FaustGraphStyleAction.h" 6 | 7 | DefineActionType( 8 | Faust, 9 | using Any = Combine; 10 | ); 11 | -------------------------------------------------------------------------------- /src/Core/ProducerComponentArgs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Action/ActionProducer.h" 4 | #include "ComponentArgs.h" 5 | 6 | template struct ProducerComponentArgs { 7 | ComponentArgs &&Args; 8 | ActionProducer::EnqueueFn Q; 9 | }; 10 | -------------------------------------------------------------------------------- /res/pitch_shifter.dsp: -------------------------------------------------------------------------------- 1 | import("stdfaust.lib"); 2 | pitchshifter = vgroup("Pitch Shifter", ef.transpose( 3 | vslider("window (samples)", 1000, 50, 10000, 1), 4 | vslider("xfade (samples)", 10, 1, 10000, 1), 5 | vslider("shift (semitones)", 0, -24, +24, 0.1) 6 | ) 7 | ); 8 | process = _ : pitchshifter; -------------------------------------------------------------------------------- /src/Core/ComponentArgs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct Component; 6 | struct ComponentArgs { 7 | Component *Parent{nullptr}; 8 | std::string_view PathSegment{""}; 9 | std::string_view MetaStr{""}; 10 | std::string_view PathSegmentPrefix{""}; 11 | }; 12 | -------------------------------------------------------------------------------- /src/Core/Primitive/EnumAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Action/DefineAction.h" 4 | 5 | DefineActionType( 6 | Enum, 7 | DefineComponentAction(Set, Saved, SameIdMerge, "", int value;); 8 | ComponentActionJson(Set, value); 9 | 10 | using Any = ActionVariant; 11 | ); 12 | -------------------------------------------------------------------------------- /src/Core/Primitive/IntAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Action/DefineAction.h" 4 | 5 | DefineActionType( 6 | Int, 7 | DefineComponentAction(Set, Saved, SameIdMerge, "", int value;); 8 | ComponentActionJson(Set, value); 9 | 10 | using Any = ActionVariant; 11 | ); 12 | -------------------------------------------------------------------------------- /src/Core/Action/Actionable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct TransientStore; 4 | 5 | template struct Actionable { 6 | using ActionType = T; 7 | 8 | virtual void Apply(TransientStore &, const ActionType &) const = 0; 9 | virtual bool CanApply(const ActionType &) const = 0; 10 | }; 11 | -------------------------------------------------------------------------------- /src/Core/Primitive/FloatAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Action/DefineAction.h" 4 | 5 | DefineActionType( 6 | Float, 7 | DefineComponentAction(Set, Saved, SameIdMerge, "", float value;); 8 | ComponentActionJson(Set, value); 9 | 10 | using Any = ActionVariant; 11 | ); 12 | -------------------------------------------------------------------------------- /src/Core/Primitive/UIntAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Action/DefineAction.h" 4 | 5 | DefineActionType( 6 | UInt, 7 | DefineComponentAction(Set, Saved, SameIdMerge, "", unsigned int value;); 8 | ComponentActionJson(Set, value); 9 | 10 | using Any = ActionVariant; 11 | ); 12 | -------------------------------------------------------------------------------- /src/Core/Primitive/StringAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Action/DefineAction.h" 4 | 5 | DefineActionType( 6 | String, 7 | DefineComponentAction(Set, Saved, SameIdMerge, "", std::string value;); 8 | ComponentActionJson(Set, value); 9 | 10 | using Any = ActionVariant; 11 | ); 12 | -------------------------------------------------------------------------------- /src/Core/Helper/Path.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace fs = std::filesystem; 6 | 7 | struct PathHash { 8 | auto operator()(const fs::path &p) const noexcept { return fs::hash_value(p); } 9 | }; 10 | 11 | inline static const fs::path RootPath{"/"}; 12 | 13 | using StorePath = fs::path; 14 | -------------------------------------------------------------------------------- /src/Core/Store/StoreAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Action/DefineAction.h" 4 | #include "Patch/PatchJson.h" 5 | 6 | DefineActionType( 7 | Store, 8 | DefineAction(ApplyPatch, Saved, CustomMerge, "", Patch patch;); 9 | Json(ApplyPatch, patch); 10 | 11 | using Any = ActionVariant; 12 | ); 13 | -------------------------------------------------------------------------------- /src/Core/Primitive/FlagsAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Action/DefineAction.h" 4 | 5 | DefineActionType( 6 | Flags, 7 | // todo toggle bit action instead of set 8 | DefineComponentAction(Set, Saved, SameIdMerge, "", int value;); 9 | ComponentActionJson(Set, value); 10 | 11 | using Any = ActionVariant; 12 | ); 13 | -------------------------------------------------------------------------------- /src/Core/TextEditor/README.md: -------------------------------------------------------------------------------- 1 | # Text editor 2 | 3 | This text editor is based on [santaclose's ImGuiColorTextEdit fork](https://github.com/santaclose/ImGuiColorTextEdit). 4 | 5 | I've ported every commit from santaclose since I forked off of it, up to commit [6ee401b](https://github.com/santaclose/ImGuiColorTextEdit/commit/6ee401bd5477da9a894a10d372c8c375dc74a842). 6 | -------------------------------------------------------------------------------- /src/Core/Action/ActionableProducer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ActionProducer.h" 4 | #include "Actionable.h" 5 | 6 | template 7 | struct ActionableProducer : Actionable, ActionProducer { 8 | using ActionProducer::ActionProducer; 9 | }; 10 | -------------------------------------------------------------------------------- /src/Core/Project/ProjectSettings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Primitive/Float.h" 4 | 5 | struct ProjectSettings : Component { 6 | using Component::Component; 7 | 8 | Prop(Float, GestureDurationSec, 0.5, 0, 5); // Merge actions occurring in short succession into a single gesture 9 | 10 | protected: 11 | void Render() const override; 12 | }; 13 | -------------------------------------------------------------------------------- /src/Audio/Faust/FaustDSPAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Action/DefineAction.h" 4 | 5 | DefineNestedActionType( 6 | Faust, DSP, 7 | DefineAction(Create, Saved, NoMerge, ""); 8 | DefineAction(Delete, Saved, NoMerge, "", ID id;); 9 | 10 | Json(Create); 11 | Json(Delete, id); 12 | 13 | using Any = ActionVariant; 14 | ); 15 | -------------------------------------------------------------------------------- /src/Core/HelpInfo.cpp: -------------------------------------------------------------------------------- 1 | #include "HelpInfo.h" 2 | 3 | using std::string, std::string_view; 4 | 5 | HelpInfo HelpInfo::Parse(string_view str) { 6 | const auto help_split = str.find_first_of('?'); 7 | const bool found = help_split != string::npos; 8 | return {string(found ? str.substr(0, help_split) : str), found ? string(str.substr(help_split + 1)) : ""}; 9 | } 10 | -------------------------------------------------------------------------------- /src/Core/Helper/Variant.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // Visit a variant with lambdas, using the "overloaded pattern" described 6 | // [here](https://en.cppreference.com/w/cpp/utility/variant/visit). 7 | template struct Match : Ts... { 8 | using Ts::operator()...; 9 | }; 10 | template Match(Ts...) -> Match; 11 | -------------------------------------------------------------------------------- /src/Audio/Faust/FaustDSPListener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | using ID = unsigned int; 4 | 5 | struct TransientStore; 6 | 7 | class dsp; 8 | struct FaustDSPListener { 9 | virtual void OnFaustDspChanged(TransientStore &, ID, dsp *) = 0; 10 | virtual void OnFaustDspAdded(TransientStore &, ID, dsp *) = 0; 11 | virtual void OnFaustDspRemoved(TransientStore &, ID) = 0; 12 | }; 13 | -------------------------------------------------------------------------------- /src/Core/Container/AdjacencyListAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Action/DefineAction.h" 4 | 5 | DefineActionType( 6 | AdjacencyList, 7 | DefineComponentAction(ToggleConnection, Saved, NoMerge, "", ID source; ID destination;); 8 | 9 | ComponentActionJson(ToggleConnection, source, destination); 10 | 11 | using Any = ActionVariant; 12 | ); 13 | -------------------------------------------------------------------------------- /src/Core/Helper/File.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace fs = std::filesystem; 8 | 9 | namespace FileIO { 10 | std::string read(const fs::path &); 11 | bool write(const fs::path &, const std::string_view contents); 12 | bool write(const fs::path &, const std::vector &contents); 13 | } // namespace FileIO 14 | -------------------------------------------------------------------------------- /src/Core/Store/Patch/PatchJson.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Json.h" 4 | #include "Patch.h" 5 | 6 | namespace nlohmann { 7 | void to_json(json &, const PrimitiveVariant &); 8 | void from_json(const json &, PrimitiveVariant &); 9 | 10 | void to_json(json &, const PatchOp &); 11 | void from_json(const json &, PatchOp &); 12 | 13 | Json(Patch, BaseComponentId, Ops); 14 | } // namespace nlohmann 15 | -------------------------------------------------------------------------------- /src/Audio/Graph/ma_helper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | static MA_INLINE void ma_zero_memory_default(void *p, size_t sz) { 7 | if (p == NULL) { 8 | assert(sz == 0); 9 | return; 10 | } 11 | 12 | if (sz > 0) { 13 | memset(p, 0, sz); 14 | } 15 | } 16 | 17 | #define MA_ZERO_OBJECT(p) ma_zero_memory_default((p), sizeof(*(p))) 18 | -------------------------------------------------------------------------------- /src/Core/CoreActionProducer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "CoreAction.h" 6 | 7 | struct CoreActionProducer { 8 | using EnqueueFn = std::function; 9 | 10 | CoreActionProducer(EnqueueFn q) : Q(std::move(q)) {} 11 | 12 | bool operator()(auto &&action) const { return Q(std::move(action)); } 13 | 14 | private: 15 | EnqueueFn Q; 16 | }; 17 | -------------------------------------------------------------------------------- /script/Format: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # get the script's directory 4 | ScriptDir="$(cd "$(dirname "${0}")" && pwd)" 5 | # get the project root directory, which is the parent directory of the script's directory 6 | ProjectRootDir="$(dirname "${ScriptDir}")" 7 | 8 | # format code files under the project root directory 9 | find -E "${ProjectRootDir}/src" -regex '.*\.(cpp|hpp|c|cc|cxx|h|hh)' -exec clang-format -style=file -i {} \; 10 | -------------------------------------------------------------------------------- /src/Audio/Faust/FaustGraphAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Action/DefineAction.h" 4 | 5 | DefineNestedActionType( 6 | Faust, Graph, 7 | DefineAction(ShowSaveSvgDialog, Saved, Merge, "~Export SVG"); 8 | Json(ShowSaveSvgDialog); 9 | 10 | DefineAction(SaveSvgFile, Unsaved, NoMerge, "", ID dsp_id; fs::path dir_path;); 11 | 12 | using Any = ActionVariant; 13 | ); 14 | -------------------------------------------------------------------------------- /src/Core/ChangeListener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct ChangeListener { 4 | // Called when at least one of the listened components has changed. 5 | // Changed component(s) are not passed to the callback, but it's called while the components are still marked as changed, 6 | // so listeners can use `component.IsChanged()` to check which listened components were changed if they wish. 7 | virtual void OnComponentChanged() = 0; 8 | }; 9 | -------------------------------------------------------------------------------- /src/Core/FileDialog/FileDialogAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Action/DefineAction.h" 4 | 5 | DefineActionType( 6 | FileDialog, 7 | 8 | DefineAction(Open, Unsaved, Merge, "", std::string dialog_json;); 9 | DefineAction(Select, Unsaved, NoMerge, "", fs::path file_path;); 10 | // Cancel action is done by simply toggling the dialog's `Visible` field. 11 | 12 | using Any = ActionVariant; 13 | ); 14 | -------------------------------------------------------------------------------- /src/Core/WindowsAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Action/DefineAction.h" 4 | 5 | DefineActionType( 6 | Windows, 7 | DefineAction(ToggleVisible, Saved, NoMerge, "", ID component_id;); 8 | DefineAction(ToggleDebug, Saved, NoMerge, "", ID component_id;); 9 | 10 | Json(ToggleVisible, component_id); 11 | Json(ToggleDebug, component_id); 12 | 13 | using Any = ActionVariant; 14 | ); 15 | -------------------------------------------------------------------------------- /src/Core/UI/UIContext.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace flowgrid { 6 | struct Style; 7 | } 8 | 9 | struct UIContext { 10 | UIContext(std::function predraw, std::function draw); 11 | ~UIContext(); 12 | 13 | // Main UI tick function 14 | // Returns `true` if the app should continue running. 15 | bool Tick() const; 16 | 17 | const std::function Predraw, Draw; 18 | }; 19 | -------------------------------------------------------------------------------- /src/Audio/Faust/FaustGraphStyleAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Action/DefineAction.h" 4 | 5 | DefineNestedActionType( 6 | Faust, GraphStyle, 7 | DefineAction(ApplyColorPreset, Saved, Merge, "", int id;); 8 | DefineAction(ApplyLayoutPreset, Saved, Merge, "", int id;); 9 | 10 | Json(ApplyColorPreset, id); 11 | Json(ApplyLayoutPreset, id); 12 | 13 | using Any = ActionVariant; 14 | ); 15 | -------------------------------------------------------------------------------- /src/Core/CoreActionHandler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreAction.h" 4 | 5 | #include "Action/Actionable.h" 6 | 7 | struct TransientStore; 8 | 9 | struct CoreActionHandler : Actionable { 10 | CoreActionHandler(TransientStore &); 11 | void Apply(TransientStore &, const ActionType &) const override; 12 | bool CanApply(const ActionType &) const override; 13 | 14 | TransientStore &_S; 15 | const TransientStore &S{_S}; 16 | }; 17 | -------------------------------------------------------------------------------- /src/Core/TextEditor/TextBufferPalette.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | enum class TextBufferPaletteId { 4 | Dark, 5 | Light, 6 | Mariana, 7 | RetroBlue 8 | }; 9 | 10 | enum class PaletteIndex { 11 | TextDefault, 12 | Background, 13 | Cursor, 14 | Selection, 15 | Error, 16 | ControlCharacter, 17 | Breakpoint, 18 | LineNumber, 19 | CurrentLineFill, 20 | CurrentLineFillInactive, 21 | CurrentLineEdge, 22 | Max 23 | }; 24 | -------------------------------------------------------------------------------- /src/Core/Demo/Demo.cpp: -------------------------------------------------------------------------------- 1 | #include "Demo.h" 2 | 3 | #include "imgui.h" 4 | #include "implot.h" 5 | 6 | Demo::Demo(ArgsT &&args) : ActionProducerComponent(std::move(args)) { 7 | WindowFlags |= ImGuiWindowFlags_MenuBar; 8 | } 9 | 10 | void Demo::ImGuiDemo::Render() const { 11 | ImGui::ShowDemoWindow(); 12 | } 13 | 14 | void Demo::ImPlotDemo::Render() const { 15 | ImPlot::ShowDemoWindow(); 16 | } 17 | 18 | void Demo::Render() const { 19 | RenderTabs(); 20 | } 21 | -------------------------------------------------------------------------------- /src/Core/FileDialog/FileDialogDemo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/ActionProducerComponent.h" 4 | 5 | #include "FileDialogAction.h" 6 | 7 | struct FileDialog; 8 | struct FileDialogData; 9 | 10 | struct FileDialogDemo : ActionProducerComponent { 11 | using ActionProducerComponent::ActionProducerComponent; 12 | 13 | protected: 14 | void Render() const override; 15 | 16 | private: 17 | void OpenDialog(const FileDialogData &) const; 18 | }; 19 | -------------------------------------------------------------------------------- /src/Core/UI/HelpMarker.cpp: -------------------------------------------------------------------------------- 1 | #include "HelpMarker.h" 2 | 3 | #include 4 | 5 | #include "imgui.h" 6 | 7 | using namespace ImGui; 8 | 9 | namespace flowgrid { 10 | void HelpMarker(std::string_view help) { 11 | TextDisabled("(?)"); 12 | if (IsItemHovered()) { 13 | BeginTooltip(); 14 | PushTextWrapPos(GetFontSize() * 35); 15 | TextUnformatted(help.data()); 16 | PopTextWrapPos(); 17 | EndTooltip(); 18 | } 19 | } 20 | } // namespace flowgrid 21 | -------------------------------------------------------------------------------- /src/Audio/Faust/FaustParamsStyle.cpp: -------------------------------------------------------------------------------- 1 | #include "FaustParamsStyle.h" 2 | 3 | #include "imgui.h" 4 | 5 | using namespace ImGui; 6 | 7 | void FaustParamsStyle::Render() const { 8 | HeaderTitles.Draw(); 9 | MinHorizontalItemWidth.Draw(); 10 | MaxHorizontalItemWidth.Draw(); 11 | MinVerticalItemHeight.Draw(); 12 | MinKnobItemSize.Draw(); 13 | AlignmentHorizontal.Draw(); 14 | AlignmentVertical.Draw(); 15 | Spacing(); 16 | WidthSizingPolicy.Draw(); 17 | TableFlags.Draw(); 18 | } 19 | -------------------------------------------------------------------------------- /src/Core/TextEditor/TextBufferStyle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | using u32 = unsigned int; 4 | 5 | // For now, this just holds what's needed for column calculation, and temporarily a global singleton. 6 | struct TextBufferStyle { 7 | u32 NumTabSpaces{4}; 8 | 9 | u32 NumTabSpacesAtColumn(u32 column) const { return NumTabSpaces - (column % NumTabSpaces); } 10 | u32 NextTabstop(u32 column) const { return ((column / NumTabSpaces) + 1) * NumTabSpaces; } 11 | }; 12 | 13 | extern TextBufferStyle GTextBufferStyle; 14 | -------------------------------------------------------------------------------- /src/Core/UI/InvisibleButton.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct ImVec2; 4 | 5 | enum InteractionFlags_ { 6 | InteractionFlags_None = 0, 7 | InteractionFlags_Hovered = 1 << 0, 8 | InteractionFlags_Held = 1 << 1, 9 | InteractionFlags_Clicked = 1 << 2, 10 | }; 11 | using InteractionFlags = int; 12 | 13 | // Basically `ImGui::InvisibleButton`, but supports hover/held testing. 14 | namespace flowgrid { 15 | InteractionFlags InvisibleButton(const ImVec2 &size_arg, const char *id); 16 | } // namespace flowgrid 17 | -------------------------------------------------------------------------------- /src/Audio/Faust/FaustParamType.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | enum FaustParamType { 4 | Type_None = 0, 5 | // Containers 6 | Type_HGroup, 7 | Type_VGroup, 8 | Type_TGroup, 9 | 10 | // Widgets 11 | Type_Button, 12 | Type_CheckButton, 13 | Type_VSlider, 14 | Type_HSlider, 15 | Type_NumEntry, 16 | Type_HBargraph, 17 | Type_VBargraph, 18 | 19 | // Types specified with metadata 20 | Type_Knob, 21 | Type_Menu, 22 | Type_VRadioButtons, 23 | Type_HRadioButtons, 24 | }; 25 | -------------------------------------------------------------------------------- /src/Core/Primitive/Bool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Primitive.h" 4 | 5 | struct Bool : Primitive, MenuItemDrawable { 6 | using Primitive::Primitive; 7 | 8 | bool CheckedDraw() const; // Unlike `Draw`, this returns `true` if the value was toggled during the draw. 9 | void MenuItem() const override; 10 | 11 | void Toggle_(TransientStore &); 12 | void IssueToggle() const; 13 | 14 | void Render(std::string_view label) const; 15 | 16 | private: 17 | void Render() const override; 18 | }; 19 | -------------------------------------------------------------------------------- /src/Audio/Faust/FaustParamsContainer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Audio/Sample.h" // Must be included before any Faust includes 4 | 5 | #include "Core/UI/NamesAndValues.h" 6 | #include "FaustParamType.h" 7 | 8 | struct FaustParamsContainer { 9 | virtual void Add(FaustParamType, const char *label, std::string_view short_label, Real *zone = nullptr, Real min = 0, Real max = 0, Real init = 0, Real step = 0, const char *tooltip = nullptr, NamesAndValues names_and_values = {}) = 0; 10 | virtual void PopGroup() = 0; 11 | }; 12 | -------------------------------------------------------------------------------- /src/Core/Store/StoreAction.cpp: -------------------------------------------------------------------------------- 1 | #include "StoreAction.h" 2 | 3 | namespace Action { 4 | std::variant Store::ApplyPatch::Merge(const Store::ApplyPatch &other) const { 5 | // Keep patch actions affecting different components separate. 6 | const auto &ops = ::Merge(patch.Ops, other.patch.Ops); 7 | if (ops.empty()) return true; 8 | if (patch.BaseComponentId == other.patch.BaseComponentId) return Store::ApplyPatch{patch.BaseComponentId, ops}; 9 | return false; 10 | } 11 | } // namespace Action 12 | -------------------------------------------------------------------------------- /src/Core/Primitive/Int.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Primitive.h" 4 | 5 | struct Int : Primitive { 6 | Int(ComponentArgs &&, int value = 0, int min = 0, int max = 100); 7 | 8 | operator bool() const { return Value != 0; } 9 | operator char() const { return Value; }; 10 | operator s8() const { return Value; }; 11 | operator s16() const { return Value; }; 12 | 13 | void Render(const std::vector &options) const; 14 | 15 | const int Min, Max; 16 | 17 | private: 18 | void Render() const override; 19 | }; 20 | -------------------------------------------------------------------------------- /src/Audio/Faust/FaustNode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // An audio graph node that uses Faust to generate audio, not to be confused with Faust's graph UI nodes (in `FaustGraphs`). 4 | 5 | #include "Audio/Graph/AudioGraphNode.h" 6 | 7 | class dsp; 8 | 9 | struct FaustNode : AudioGraphNode { 10 | FaustNode(ComponentArgs &&, ID dsp_id = 0); 11 | 12 | void OnSampleRateChanged() override; 13 | 14 | ID GetDspId() const; 15 | void SetDsp(TransientStore &, ID); 16 | 17 | private: 18 | std::unique_ptr CreateNode(ID dsp_id = 0); 19 | }; 20 | -------------------------------------------------------------------------------- /src/Core/TextEditor/TextEditor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "TextBuffer.h" 4 | 5 | // Will hold multiple text buffers. 6 | struct TextEditor : Component { 7 | TextEditor(ComponentArgs &&, const fs::path &); 8 | ~TextEditor(); 9 | 10 | void RenderDebug() const override; 11 | 12 | bool Empty() const; 13 | std::string GetText() const; 14 | 15 | fs::path _LastOpenedFilePath; 16 | Prop(TextBuffer, Buffer, _LastOpenedFilePath); 17 | 18 | private: 19 | void Render() const override; 20 | void RenderMenu() const; 21 | }; 22 | -------------------------------------------------------------------------------- /src/Core/Store/IdPairs.cpp: -------------------------------------------------------------------------------- 1 | #include "IdPairs.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | IdPair DeserializeIdPair(const std::string &serialized_id_pair) { 8 | IdPair id_pair; 9 | std::stringstream ss(serialized_id_pair); 10 | char comma; 11 | if (!(ss >> id_pair.first >> comma >> id_pair.second)) throw std::invalid_argument("Invalid string format for ID pair."); 12 | 13 | return id_pair; 14 | } 15 | 16 | std::string SerializeIdPair(const IdPair &p) { return std::format("{},{}", p.first, p.second); } 17 | -------------------------------------------------------------------------------- /src/Core/Store/Patch/Patch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "Core/ID.h" 8 | #include "PatchOp.h" 9 | 10 | using PatchOps = std::unordered_map>; 11 | PatchOps Merge(const PatchOps &a, const PatchOps &b); 12 | 13 | struct Patch { 14 | ID BaseComponentId; 15 | PatchOps Ops; 16 | 17 | // Returns a view. 18 | inline auto GetIds() const noexcept { return Ops | std::views::keys; } 19 | 20 | bool Empty() const noexcept { return Ops.empty(); } 21 | }; 22 | -------------------------------------------------------------------------------- /src/Audio/Faust/FaustParamGroup.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Component.h" 4 | #include "FaustParamBase.h" 5 | 6 | struct FaustParamGroup : FaustParamBase, Component { 7 | FaustParamGroup(ComponentArgs &&args, const FaustParamsStyle &style, const FaustParamType type = Type_None, std::string_view label = "") 8 | : FaustParamBase(style, type, label), Component(std::move(args)) {} 9 | 10 | void Render(const float suggested_height, bool no_label = false) const override; 11 | 12 | private: 13 | void Render() const override { Render(0); } 14 | }; 15 | -------------------------------------------------------------------------------- /src/Core/ActionProducerComponent.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Action/ActionProducer.h" 4 | #include "Component.h" 5 | #include "ProducerComponentArgs.h" 6 | 7 | template 8 | struct ActionProducerComponent : Component, ActionProducer { 9 | using ArgsT = ProducerComponentArgs; 10 | 11 | ActionProducerComponent(ArgsT &&args) 12 | : Component(std::move(args.Args)), ActionProducer(std::move(args.Q)) {} 13 | 14 | virtual ~ActionProducerComponent() = default; 15 | }; 16 | -------------------------------------------------------------------------------- /src/Core/TextEditor/queries/README.md: -------------------------------------------------------------------------------- 1 | # Tree-sitter queries 2 | 3 | This directory hold copies of select tree-sitter query files from [the corresponding nvim-treesitter directory](https://github.com/nvim-treesitter/nvim-treesitter/tree/master/queries). 4 | 5 | I maintain the `tree-sitter-faust` queries in the [main parser repo](https://github.com/khiner/tree-sitter-faust) to conform to the nvim-treesitter highlight conventions, so I don't keep a redundant copy here and instead read the `.scm` files directly from the submodule. 6 | (This query directory fallback applies to all languages.) 7 | -------------------------------------------------------------------------------- /src/Core/Style/StyleAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Action/DefineAction.h" 4 | 5 | DefineActionType( 6 | Style, 7 | 8 | DefineAction(SetImGuiColorPreset, Saved, Merge, "", int id;); 9 | DefineAction(SetImPlotColorPreset, Saved, Merge, "", int id;); 10 | DefineAction(SetProjectColorPreset, Saved, Merge, "", int id;); 11 | 12 | Json(SetImGuiColorPreset, id); 13 | Json(SetImPlotColorPreset, id); 14 | Json(SetProjectColorPreset, id); 15 | 16 | using Any = ActionVariant; 17 | ); 18 | -------------------------------------------------------------------------------- /src/FlowGrid.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Audio/Audio.h" 4 | #include "FlowGridAction.h" 5 | 6 | /** 7 | `FlowGrid` fully describes the application-specific (non-core, non-project) state at any point in time. 8 | */ 9 | struct FlowGrid : ActionableComponent { 10 | using ActionableComponent::ActionableComponent; 11 | 12 | void Apply(TransientStore &, const ActionType &) const override; 13 | bool CanApply(const ActionType &) const override; 14 | 15 | void FocusDefault() const override; 16 | 17 | ProducerProp(Audio, Audio); 18 | }; 19 | -------------------------------------------------------------------------------- /src/Core/Helper/Time.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | using namespace std::chrono_literals; // Support literals like `1s` or `500ms` 7 | 8 | // Time declarations inspired by https://stackoverflow.com/a/14391562/780425 9 | using Clock = std::chrono::system_clock; // Main system clock 10 | using fsec = std::chrono::duration; // float seconds as a std::chrono::duration 11 | using TimePoint = Clock::time_point; 12 | 13 | inline std::string FormatMillis(auto duration) { 14 | return std::format("{:.3f}ms", std::chrono::duration(duration).count()); 15 | } 16 | -------------------------------------------------------------------------------- /src/Core/Primitive/Float.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Primitive.h" 4 | 5 | using ImGuiSliderFlags = int; 6 | 7 | struct Float : Primitive { 8 | // `fmt` defaults to ImGui slider default, which is "%.3f" 9 | Float(ComponentArgs &&, float value = 0, float min = 0, float max = 1, const char *fmt = nullptr, ImGuiSliderFlags flags = 0, float drag_speed = 0); 10 | 11 | const float Min, Max, DragSpeed; // If `DragSpeed` is non-zero, this is rendered as an `ImGui::DragFloat`. 12 | const char *Format; 13 | const ImGuiSliderFlags Flags; 14 | 15 | private: 16 | void Render() const override; 17 | }; 18 | -------------------------------------------------------------------------------- /src/Audio/AudioIO.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // Starting at `-1` allows for using `IO` types as array indices. 6 | enum IO_ { 7 | IO_None = -1, 8 | IO_In, 9 | IO_Out 10 | }; 11 | using IO = IO_; 12 | 13 | constexpr IO IO_All[] = {IO_In, IO_Out}; 14 | constexpr int IO_Count = 2; 15 | 16 | constexpr std::string to_string(const IO io, bool shorten = false) { 17 | switch (io) { 18 | case IO_In: return shorten ? "in" : "input"; 19 | case IO_Out: return shorten ? "out" : "output"; 20 | case IO_None: return "none"; 21 | default: return "unknown"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Core/HelpInfo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "ID.h" 8 | 9 | struct HelpInfo { 10 | const std::string Name, Help; 11 | 12 | // Split the string on '?'. 13 | // If there is no '?' in the provided string, the first element will have the full input string and the second element will be an empty string. 14 | // todo don't split on escaped '\?' 15 | static HelpInfo Parse(std::string_view meta_str); 16 | 17 | // Metadata for display in the Info stack. 18 | inline static std::unordered_map ById{}; 19 | }; 20 | -------------------------------------------------------------------------------- /src/Core/TextEditor/queries/json/highlights.scm: -------------------------------------------------------------------------------- 1 | [ 2 | (true) 3 | (false) 4 | ] @boolean 5 | 6 | (null) @constant.builtin 7 | 8 | (number) @number 9 | 10 | (pair 11 | key: (string) @property) 12 | 13 | (pair 14 | value: (string) @string) 15 | 16 | (array 17 | (string) @string) 18 | 19 | [ 20 | "," 21 | ":" 22 | ] @punctuation.delimiter 23 | 24 | [ 25 | "[" 26 | "]" 27 | "{" 28 | "}" 29 | ] @punctuation.bracket 30 | 31 | ("\"" @conceal 32 | (#set! conceal "")) 33 | 34 | (escape_sequence) @string.escape 35 | 36 | ((escape_sequence) @conceal 37 | (#eq? @conceal "\\\"") 38 | (#set! conceal "\"")) 39 | -------------------------------------------------------------------------------- /src/Audio/WaveformNode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Audio/Graph/AudioGraphNode.h" 4 | 5 | struct WaveformNode : AudioGraphNode { 6 | WaveformNode(ComponentArgs &&); 7 | 8 | void OnComponentChanged() override; 9 | void OnSampleRateChanged() override; 10 | 11 | Prop(Float, Frequency, 440.0, 20.0, 16000.0); 12 | Prop(Enum, Type, {"Sine", "Square", "Triangle", "Sawtooth"}, 0); 13 | // Amplitude is controlled by node output level. 14 | 15 | private: 16 | std::unique_ptr CreateNode() const; 17 | 18 | void Render() const override; 19 | 20 | void UpdateFrequency(); 21 | void UpdateType(); 22 | }; 23 | -------------------------------------------------------------------------------- /src/Core/FileDialog/FileDialog.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Action/ActionProducer.h" 4 | #include "Core/Component.h" 5 | 6 | #include "FileDialogAction.h" 7 | #include "FileDialogData.h" 8 | 9 | using ImGuiFileDialogFlags = int; 10 | 11 | struct FileDialog : ActionProducer { 12 | using ActionProducer::ActionProducer; 13 | 14 | void Set(FileDialogData &&) const; 15 | void SetJson(TransientStore &, json &&) const; 16 | 17 | inline static bool Visible; 18 | inline static FileDialogData Data; 19 | inline static std::string SelectedFilePath; 20 | 21 | void Render() const; 22 | }; 23 | -------------------------------------------------------------------------------- /src/Core/Store/IdPairs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // std::hash 4 | #include 5 | #include 6 | 7 | #include "immer/set.hpp" 8 | 9 | using ID = unsigned int; 10 | using IdPair = std::pair; 11 | 12 | IdPair DeserializeIdPair(const std::string &); 13 | std::string SerializeIdPair(const IdPair &); 14 | 15 | struct IdPairHash { 16 | // Common hash shift trick: https://en.cppreference.com/w/cpp/utility/hash 17 | auto operator()(const IdPair &p) const noexcept { return std::hash()(p.first) ^ (std::hash()(p.second) << 1); } 18 | }; 19 | 20 | using IdPairs = immer::set; 21 | -------------------------------------------------------------------------------- /src/Core/Primitive/Enum.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Primitive.h" 4 | 5 | struct Enum : Primitive, MenuItemDrawable { 6 | Enum(ComponentArgs &&, std::vector names, int value = 0); 7 | Enum(ComponentArgs &&, std::function get_name, int value = 0); 8 | 9 | void Render(const std::vector &options) const; 10 | void MenuItem() const override; 11 | 12 | const std::vector Names; 13 | 14 | private: 15 | void Render() const override; 16 | std::string OptionName(const int option) const; 17 | 18 | const std::optional> GetName{}; 19 | }; 20 | -------------------------------------------------------------------------------- /src/Core/Container/SetAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Action/DefineAction.h" 4 | 5 | namespace Action { 6 | template struct Set { 7 | static_assert(always_false_v, "There is no `Set` action type for this type."); 8 | }; 9 | 10 | DefineTemplatedActionType( 11 | Set, UInt, u32, 12 | DefineComponentAction(Insert, Saved, SameIdMerge, "", u32 value;); 13 | DefineComponentAction(Erase, Saved, SameIdMerge, "", u32 value;); 14 | 15 | using Any = ActionVariant; 16 | ); 17 | 18 | ComponentActionJson(Set::Insert, value); 19 | ComponentActionJson(Set::Erase, value); 20 | } // namespace Action 21 | -------------------------------------------------------------------------------- /src/Core/Demo/Demo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/FileDialog/FileDialogDemo.h" 4 | 5 | struct Demo : ActionProducerComponent { 6 | Demo(ArgsT &&); 7 | 8 | struct ImGuiDemo : Component { 9 | using Component::Component; 10 | 11 | protected: 12 | void Render() const override; 13 | }; 14 | struct ImPlotDemo : Component { 15 | using Component::Component; 16 | 17 | protected: 18 | void Render() const override; 19 | }; 20 | 21 | Prop(ImGuiDemo, ImGui); 22 | Prop(ImPlotDemo, ImPlot); 23 | ProducerProp(FileDialogDemo, FileDialog); 24 | 25 | protected: 26 | void Render() const override; 27 | }; 28 | -------------------------------------------------------------------------------- /src/Core/ActionableComponent.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Action/ActionableProducer.h" 4 | #include "Component.h" 5 | #include "ProducerComponentArgs.h" 6 | 7 | // `ActionType` is the type of action that can be _applied_ to the component. 8 | template 9 | struct ActionableComponent : Component, ActionableProducer { 10 | using ArgsT = ProducerComponentArgs; 11 | 12 | ActionableComponent(ArgsT &&args) 13 | : Component(std::move(args.Args)), ActionableProducer(std::move(args.Q)) {} 14 | 15 | virtual ~ActionableComponent() = default; 16 | }; 17 | -------------------------------------------------------------------------------- /src/FlowGrid.cpp: -------------------------------------------------------------------------------- 1 | #include "FlowGrid.h" 2 | 3 | void FlowGrid::Apply(TransientStore &s, const ActionType &action) const { 4 | std::visit( 5 | Match{ 6 | [this, &s](Action::Audio::Any &&a) { Audio.Apply(s, std::move(a)); }, 7 | }, 8 | action 9 | ); 10 | } 11 | 12 | bool FlowGrid::CanApply(const ActionType &action) const { 13 | return std::visit( 14 | Match{ 15 | [this](Action::Audio::Any &&a) { return Audio.CanApply(std::move(a)); }, 16 | }, 17 | action 18 | ); 19 | } 20 | 21 | void FlowGrid::FocusDefault() const { 22 | Audio.Graph.Focus(); 23 | Audio.Faust.Graphs.Focus(); 24 | Audio.Faust.Paramss.Focus(); 25 | } 26 | -------------------------------------------------------------------------------- /src/Core/Container/Set.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "immer/set.hpp" 4 | 5 | #include "Core/Component.h" 6 | 7 | template struct Set : Component { 8 | using Component::Component; 9 | using ContainerT = immer::set; 10 | 11 | ~Set() { 12 | Erase(_S); 13 | } 14 | 15 | void SetJson(TransientStore &, json &&) const override; 16 | json ToJson() const override; 17 | 18 | ContainerT Get() const; 19 | 20 | void Insert(TransientStore &, T) const; 21 | void Erase(TransientStore &, T) const; 22 | void Clear(TransientStore &) const; 23 | void Erase(TransientStore &) const override; 24 | 25 | void RenderValueTree(bool annotate, bool auto_select) const override; 26 | }; 27 | -------------------------------------------------------------------------------- /src/Audio/Device/DeviceDataFormat.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | // Mirrors the anonymous struct in `ma_device_info::nativeDataFormats`, excluding `flags`. 8 | struct DeviceDataFormat { 9 | // Like `ma_get_format_name(ma_format)`, but less verbose. 10 | static const char *GetFormatName(int format); // Convert to `ma_format`. 11 | std::string ToString() const; 12 | 13 | bool operator==(const DeviceDataFormat &other) const { 14 | return SampleFormat == other.SampleFormat && Channels == other.Channels && SampleRate == other.SampleRate; 15 | }; 16 | 17 | int SampleFormat; // Convert to `ma_format`. 18 | u32 Channels; 19 | u32 SampleRate; 20 | }; 21 | -------------------------------------------------------------------------------- /src/Core/Helper/Hex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | // Expecting "#RRGGBB" or "#RRGGBBAA" (lowercase or uppercase). 8 | inline bool IsHex(std::string_view str) noexcept { 9 | return !str.empty() && str[0] == '#' && (str.size() == 7 || str.size() == 9); 10 | } 11 | 12 | inline std::string U32ToHex(unsigned int value, bool is_color = false) noexcept { 13 | if (is_color) return std::format("#{:08X}", value); 14 | return std::format("#{:X}", value); 15 | } 16 | 17 | inline unsigned int HexToU32(std::string_view hex) noexcept { 18 | if (!IsHex(hex)) return 0; 19 | return std::stoul(std::string(hex.substr(1)) + (hex.size() == 7 ? "FF" : ""), nullptr, 16); 20 | } 21 | -------------------------------------------------------------------------------- /src/Core/UI/JsonTree.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "nlohmann/json_fwd.hpp" 6 | 7 | using json = nlohmann::json; 8 | 9 | namespace flowgrid { 10 | // If `label` is empty, `JsonTree` will simply show the provided json `value` (object/array/raw value), with no nesting. 11 | // For a non-empty `label`: 12 | // * If the provided `value` is an array or object, it will show as a nested `TreeNode` with `label` as its parent. 13 | // * If the provided `value` is a raw value (or null), it will show as as '{label}: {value}'. 14 | bool TreeNode(std::string_view label, const char *id = nullptr, const char *value = nullptr); 15 | void JsonTree(std::string_view label, json &&value, const char *id = nullptr); 16 | } // namespace flowgrid 17 | -------------------------------------------------------------------------------- /src/Core/Primitive/Flags.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Primitive.h" 4 | 5 | // todo in state viewer, make `Annotated` label mode expand out each integer flag into a string list 6 | struct Flags : Primitive, MenuItemDrawable { 7 | struct Item { 8 | Item(const char *name_and_help); 9 | std::string Name, Help; 10 | }; 11 | 12 | // All text after an optional '?' character for each name will be interpreted as an item help string. 13 | // E.g. `{"Foo?Does a thing", "Bar?Does a different thing", "Baz"}` 14 | Flags(ComponentArgs &&, std::vector items, int value = 0); 15 | 16 | void MenuItem() const override; 17 | 18 | const std::vector Items; 19 | 20 | private: 21 | void Render() const override; 22 | }; 23 | -------------------------------------------------------------------------------- /src/Audio/Graph/AudioGraphAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Action/DefineAction.h" 4 | 5 | DefineActionType( 6 | AudioGraph, 7 | DefineAction(CreateNode, Saved, NoMerge, "", std::string node_type_id;); 8 | DefineAction(CreateFaustNode, Saved, NoMerge, "", ID dsp_id;); 9 | DefineAction(DeleteNode, Saved, NoMerge, "", ID id;); 10 | DefineAction(SetDeviceDataFormat, Saved, Merge, "", ID id; int sample_format; u32 channels; u32 sample_rate;); 11 | 12 | Json(CreateNode, node_type_id); 13 | Json(CreateFaustNode, dsp_id); 14 | Json(DeleteNode, id); 15 | Json(SetDeviceDataFormat, id, sample_format, channels, sample_rate); 16 | 17 | using Any = ActionVariant; 18 | ); 19 | -------------------------------------------------------------------------------- /src/Core/FileDialog/FileDialogData.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | using ID = unsigned int; 6 | 7 | using ImGuiFileDialogFlags = int; 8 | // Copied from `ImGuiFileDialog` source with a different name to avoid redefinition. Brittle but we can avoid an include this way. 9 | constexpr ImGuiFileDialogFlags FileDialogFlags_ConfirmOverwrite = 1 << 0; 10 | constexpr ImGuiFileDialogFlags FileDialogFlags_Modal = 1 << 9; 11 | 12 | struct FileDialogData { 13 | ID OwnerId; 14 | std::string Title{"Choose file"}; 15 | std::string Filters{".*"}; 16 | std::string FilePath{"."}; 17 | std::string DefaultFileName{""}; 18 | bool SaveMode{false}; 19 | int MaxNumSelections{1}; 20 | ImGuiFileDialogFlags Flags{FileDialogFlags_Modal}; 21 | }; 22 | -------------------------------------------------------------------------------- /src/Audio/Graph/ma_gainer_node/ma_gainer_node.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "miniaudio.h" 4 | 5 | struct ma_gainer_node_config { 6 | ma_node_config node_config; 7 | ma_gainer_config gainer_config; 8 | float gain; 9 | }; 10 | 11 | ma_gainer_node_config ma_gainer_node_config_init(ma_uint32 channels, float gain, ma_uint32 smooth_time_frames); 12 | 13 | struct ma_gainer_node { 14 | ma_node_base base; 15 | ma_gainer_node_config config; 16 | ma_gainer gainer; 17 | }; 18 | 19 | ma_result ma_gainer_node_init(ma_node_graph *, const ma_gainer_node_config *, const ma_allocation_callbacks *, ma_gainer_node *); 20 | void ma_gainer_node_uninit(ma_gainer_node *, const ma_allocation_callbacks *); 21 | 22 | ma_result ma_gainer_node_set_gain(ma_gainer_node *, float gain); 23 | -------------------------------------------------------------------------------- /src/Audio/Device/DeviceDataFormat.cpp: -------------------------------------------------------------------------------- 1 | #include "DeviceDataFormat.h" 2 | 3 | #include 4 | 5 | #include "miniaudio.h" 6 | 7 | const char *DeviceDataFormat::GetFormatName(int format) { 8 | switch (format) { 9 | case ma_format_unknown: return "Unknown"; 10 | case ma_format_u8: return "8-bit Unsigned Int"; 11 | case ma_format_s16: return "16-bit Signed Int"; 12 | case ma_format_s24: return "24-bit Signed Int"; 13 | case ma_format_s32: return "32-bit Signed Int"; 14 | case ma_format_f32: return "32-bit Float"; 15 | default: return "Invalid"; 16 | } 17 | } 18 | 19 | std::string DeviceDataFormat::ToString() const { 20 | return std::format("{} Hz | {} ch | {}", std::to_string(SampleRate), Channels, GetFormatName(SampleFormat)); 21 | } 22 | -------------------------------------------------------------------------------- /src/Core/Container/NavigableAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Action/DefineAction.h" 4 | 5 | namespace Action { 6 | template struct Navigable { 7 | static_assert(always_false_v, "There is no `Navigable` action type for this template type."); 8 | }; 9 | 10 | DefineTemplatedActionType( 11 | Navigable, UInt, u32, 12 | DefineComponentAction(Clear, Saved, NoMerge, ""); 13 | DefineComponentAction(Push, Saved, NoMerge, "", u32 value;); 14 | DefineComponentAction(MoveTo, Saved, SameIdMerge, "", u32 index;); 15 | 16 | using Any = ActionVariant; 17 | ); 18 | 19 | ComponentActionJson(Navigable::Clear); 20 | ComponentActionJson(Navigable::Push, value); 21 | ComponentActionJson(Navigable::MoveTo, index); 22 | } // namespace Action 23 | -------------------------------------------------------------------------------- /src/Audio/Audio.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AudioAction.h" 4 | #include "Core/ActionableComponent.h" 5 | #include "Faust/Faust.h" 6 | #include "Graph/AudioGraph.h" 7 | 8 | struct Audio : ActionableComponent { 9 | Audio(ArgsT &&); 10 | ~Audio(); 11 | 12 | void Apply(TransientStore &, const ActionType &) const override; 13 | bool CanApply(const ActionType &) const override; 14 | 15 | void Dock(ID *) const override; 16 | 17 | struct Style : Component { 18 | using Component::Component; 19 | 20 | void Render() const override; 21 | }; 22 | 23 | ProducerProp_(AudioGraph, Graph, "Audio graph"); 24 | ProducerProp(Faust, Faust); 25 | Prop_(Style, Style, "Audio style"); 26 | 27 | private: 28 | void Render() const override; 29 | }; 30 | -------------------------------------------------------------------------------- /src/Audio/Graph/ma_channel_converter_node/ma_channel_converter_node.h: -------------------------------------------------------------------------------- 1 | #include "miniaudio.h" 2 | 3 | struct ma_channel_converter_node_config { 4 | ma_node_config node_config; 5 | ma_channel_converter_config converter_config; 6 | }; 7 | 8 | ma_channel_converter_node_config ma_channel_converter_node_config_init(ma_uint32 in_channels, ma_uint32 out_channels); 9 | 10 | struct ma_channel_converter_node { 11 | ma_node_base base; 12 | ma_channel_converter_node_config config; 13 | ma_channel_converter converter; 14 | }; 15 | 16 | ma_result ma_channel_converter_node_init(ma_node_graph *, const ma_channel_converter_node_config *, const ma_allocation_callbacks *, ma_channel_converter_node *); 17 | void ma_channel_converter_node_uninit(ma_channel_converter_node *, const ma_allocation_callbacks *); 18 | -------------------------------------------------------------------------------- /src/Core/Windows.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "ActionProducerComponent.h" 6 | #include "Container/Set.h" 7 | #include "WindowsAction.h" 8 | 9 | struct Windows : ActionProducerComponent { 10 | using ActionProducerComponent::ActionProducerComponent; 11 | 12 | void Register(TransientStore &, ID, bool dock = true); 13 | 14 | bool IsDock(ID) const; 15 | 16 | bool IsWindow(ID) const; 17 | bool IsVisible(ID) const; 18 | void ToggleVisible(TransientStore &, ID) const; 19 | 20 | void DrawMenuItem(const Component &) const; 21 | 22 | Prop(Set, VisibleComponentIds); 23 | 24 | protected: 25 | void Render() const override; 26 | 27 | private: 28 | std::set DockComponentIds; 29 | std::set WindowComponentIds; 30 | }; 31 | -------------------------------------------------------------------------------- /src/Audio/Graph/ma_waveform_node/ma_waveform_node.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "miniaudio.h" 4 | 5 | struct ma_waveform_node_config { 6 | ma_node_config node_config; 7 | ma_waveform_config waveform_config; 8 | }; 9 | 10 | ma_waveform_node_config ma_waveform_node_config_init(ma_uint32 sample_rate, ma_waveform_type type, double frequency); 11 | 12 | struct ma_waveform_node { 13 | ma_node_base base; 14 | ma_waveform_node_config config; 15 | ma_waveform waveform; 16 | }; 17 | 18 | ma_result ma_waveform_node_init(ma_node_graph *, const ma_waveform_node_config *, const ma_allocation_callbacks *, ma_waveform_node *); 19 | void ma_waveform_node_uninit(ma_waveform_node *, const ma_allocation_callbacks *); 20 | 21 | ma_result ma_waveform_node_set_sample_rate(ma_waveform_node *, ma_uint32 sample_rate); 22 | -------------------------------------------------------------------------------- /src/Core/TextEditor/TextEditor.cpp: -------------------------------------------------------------------------------- 1 | #include "TextEditor.h" 2 | 3 | #include "imgui.h" 4 | 5 | TextEditor::TextEditor(ComponentArgs &&args, const fs::path &file_path) 6 | : Component(std::move(args)), _LastOpenedFilePath(file_path) { 7 | WindowFlags |= ImGuiWindowFlags_MenuBar; 8 | } 9 | 10 | TextEditor::~TextEditor() {} 11 | 12 | bool TextEditor::Empty() const { return Buffer.Empty(); } 13 | std::string TextEditor::GetText() const { return Buffer.GetText(); } 14 | 15 | using namespace ImGui; 16 | 17 | void TextEditor::RenderMenu() const { 18 | if (BeginMenuBar()) { 19 | Buffer.RenderMenu(); 20 | EndMenuBar(); 21 | } 22 | } 23 | 24 | void TextEditor::Render() const { 25 | RenderMenu(); 26 | Buffer.Render(); 27 | } 28 | 29 | void TextEditor::RenderDebug() const { Buffer.RenderDebug(); } 30 | -------------------------------------------------------------------------------- /src/Core/Container/Vec2Action.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Action/DefineAction.h" 4 | 5 | DefineActionType( 6 | Vec2, 7 | DefineComponentAction(Set, Saved, SameIdMerge, "", std::pair value;); 8 | DefineComponentAction(SetX, Saved, SameIdMerge, "", float value;); 9 | DefineComponentAction(SetY, Saved, SameIdMerge, "", float value;); 10 | DefineComponentAction(SetAll, Saved, SameIdMerge, "", float value;); 11 | DefineComponentAction(ToggleLinked, Saved, SameIdMerge, ""); // No effect for non-linked `Vec2` fields. 12 | 13 | ComponentActionJson(Set, value); 14 | ComponentActionJson(SetX, value); 15 | ComponentActionJson(SetY, value); 16 | ComponentActionJson(SetAll, value); 17 | ComponentActionJson(ToggleLinked); 18 | 19 | using Any = ActionVariant; 20 | ); 21 | -------------------------------------------------------------------------------- /src/Core/Action/Action.cpp: -------------------------------------------------------------------------------- 1 | #include "Action.h" 2 | 3 | #include 4 | 5 | #include "Core/Helper/String.h" 6 | 7 | namespace Action { 8 | Metadata::Parsed Metadata::Parse(string_view meta_str) { 9 | static const std::regex pattern("(~(.*))?"); 10 | string meta_str_std(meta_str); 11 | static std::smatch matches; 12 | if (!meta_str_std.empty() && std::regex_search(meta_str_std, matches, pattern)) { 13 | return {matches[2].str()}; 14 | } 15 | return {""}; 16 | } 17 | Metadata::Metadata(string_view path_leaf, Metadata::Parsed parsed) 18 | : PathLeaf(path_leaf), 19 | Name(StringHelper::PascalToSentenceCase(path_leaf)), 20 | MenuLabel(parsed.MenuLabel.empty() ? Name : parsed.MenuLabel) {} 21 | 22 | Metadata::Metadata(string_view path_leaf, string_view meta_str) 23 | : Metadata(path_leaf, Parse(meta_str)) {} 24 | } // namespace Action 25 | -------------------------------------------------------------------------------- /src/Core/Primitive/Primitive.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Component.h" 4 | 5 | struct TransientStore; 6 | 7 | template struct Primitive : Component { 8 | Primitive(ComponentArgs &&, T value = {}); 9 | virtual ~Primitive(); 10 | 11 | json ToJson() const override; 12 | void SetJson(TransientStore &, json &&) const override; 13 | 14 | void Refresh() override; 15 | 16 | operator T() const { return Value; } 17 | bool operator==(T value) const { return Value == value; } 18 | 19 | void Set(TransientStore &, T) const; // Update the store 20 | void Set_(TransientStore &, T); // Updates both store and cached value. 21 | void IssueSet(T) const; // Queue a set action. 22 | void Erase(TransientStore &) const override; 23 | 24 | void RenderValueTree(bool annotate, bool auto_select) const override; 25 | 26 | protected: 27 | T Value; 28 | }; 29 | -------------------------------------------------------------------------------- /src/Core/String.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Component.h" 4 | 5 | struct String : Component { 6 | String(ComponentArgs &&, std::string_view value = ""); 7 | ~String(); 8 | 9 | std::string_view Get() const; 10 | 11 | operator std::string_view() const { return Get(); } 12 | operator fs::path() const { return std::string(*this); } 13 | operator bool() const { return !Get().empty(); } 14 | 15 | void Render(const std::vector &options) const; 16 | 17 | json ToJson() const override; 18 | void SetJson(TransientStore &, json &&) const override; 19 | 20 | void IssueSet(std::string_view) const; 21 | void Set(TransientStore &, std::string_view) const; 22 | 23 | void RenderValueTree(bool annotate, bool auto_select) const override; 24 | 25 | void Erase(TransientStore &) const override; 26 | 27 | private: 28 | void Render() const override; 29 | }; 30 | -------------------------------------------------------------------------------- /src/Core/Project/Preferences.cpp: -------------------------------------------------------------------------------- 1 | #include "Preferences.h" 2 | 3 | #include "nlohmann/json.hpp" 4 | 5 | #include "Core/Helper/File.h" 6 | 7 | using json = nlohmann::json; 8 | 9 | Preferences::Preferences() { 10 | if (fs::exists(Path)) { 11 | const json js = json::parse(FileIO::read(Path)); 12 | RecentlyOpenedPaths = js["RecentlyOpenedPaths"].get>(); 13 | } else { 14 | Write(); 15 | } 16 | } 17 | 18 | bool Preferences::Write() const { 19 | json js; 20 | js["RecentlyOpenedPaths"] = RecentlyOpenedPaths; 21 | 22 | return FileIO::write(Path, js.dump()); 23 | } 24 | 25 | bool Preferences::Clear() { 26 | RecentlyOpenedPaths.clear(); 27 | return Write(); 28 | } 29 | 30 | void Preferences::OnProjectOpened(const fs::path &path) { 31 | RecentlyOpenedPaths.remove(path); 32 | RecentlyOpenedPaths.emplace_front(path); 33 | Write(); 34 | } 35 | -------------------------------------------------------------------------------- /script/Clean: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # By default, this script deletes both build debug AND release build directories. 4 | # You can call this script from anywhere, and it will delete the build directories in the project root directory. 5 | # Flags: 6 | # * `-d, --debug`: Only delete the debug build directory. 7 | # * `-r, --release`: Only delete the release build directory. 8 | 9 | CleanDebug=false 10 | CleanRelease=false 11 | 12 | if [ $# -eq 0 ] || [ "$1" = "-d" ] || [ "$1" = "--debug" ]; then 13 | CleanDebug=true 14 | fi 15 | if [ $# -eq 0 ] || [ "$1" = "-r" ] || [ "$1" = "--release" ]; then 16 | CleanRelease=true 17 | fi 18 | 19 | ScriptDir="$(cd "$(dirname "${0}")" && pwd)" 20 | ProjectRootDir="$(dirname "${ScriptDir}")" 21 | 22 | if [ "$CleanDebug" = true ]; then 23 | rm -rf "${ProjectRootDir}/build" 24 | fi 25 | if [ "$CleanRelease" = true ]; then 26 | rm -rf "${ProjectRootDir}/build-release" 27 | fi 28 | -------------------------------------------------------------------------------- /src/Core/Primitive/UInt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Primitive.h" 4 | 5 | struct ImColor; 6 | using ImGuiColorEditFlags = int; 7 | 8 | struct UInt : Primitive { 9 | UInt(ComponentArgs &&, u32 value = 0, u32 min = 0, u32 max = 100); 10 | UInt(ComponentArgs &&, std::function get_name, u32 value = 0); 11 | 12 | // `u32` conversion is already provided by `Primitive`. 13 | operator bool() const { return Value != 0; } 14 | operator int() const { return Value; }; 15 | operator size_t() const { return Value; }; 16 | operator float() const { return Value; }; 17 | operator ImColor() const; 18 | 19 | void Render(const std::vector &options) const; 20 | 21 | const u32 Min, Max; 22 | 23 | private: 24 | void Render() const override; 25 | std::string ValueName(u32 value) const; 26 | 27 | const std::optional> GetName{}; 28 | }; 29 | -------------------------------------------------------------------------------- /src/Audio/Graph/ma_panner_node/ma_panner_node.h: -------------------------------------------------------------------------------- 1 | #include "miniaudio.h" 2 | 3 | #include 4 | 5 | struct ma_panner_node_config { 6 | ma_node_config node_config; 7 | ma_panner_config panner_config; 8 | ma_uint32 in_channels; 9 | }; 10 | 11 | ma_panner_node_config ma_panner_node_config_init(ma_uint32 in_channels, ma_pan_mode mode = ma_pan_mode_balance); 12 | 13 | struct ma_panner_node { 14 | ma_node_base base; 15 | ma_panner_node_config config; 16 | ma_panner panner; 17 | std::unique_ptr converter; // Used if `in_channels != 2`. 18 | }; 19 | 20 | ma_result ma_panner_node_init(ma_node_graph *, const ma_panner_node_config *, const ma_allocation_callbacks *, ma_panner_node *); 21 | void ma_panner_node_uninit(ma_panner_node *, const ma_allocation_callbacks *); 22 | 23 | ma_result ma_panner_node_set_pan(ma_panner_node *, float pan); 24 | ma_result ma_panner_node_set_mode(ma_panner_node *, ma_pan_mode); 25 | -------------------------------------------------------------------------------- /src/Core/CoreAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Container/AdjacencyListAction.h" 4 | #include "Container/NavigableAction.h" 5 | #include "Container/SetAction.h" 6 | #include "Container/Vec2Action.h" 7 | #include "Container/VectorAction.h" 8 | #include "Primitive/BoolAction.h" 9 | #include "Primitive/EnumAction.h" 10 | #include "Primitive/FlagsAction.h" 11 | #include "Primitive/FloatAction.h" 12 | #include "Primitive/IntAction.h" 13 | #include "Primitive/StringAction.h" 14 | #include "Primitive/UIntAction.h" 15 | #include "TextEditor/TextBufferAction.h" 16 | 17 | namespace Action { 18 | namespace Core { 19 | using Any = Combine< 20 | Bool::Any, Int::Any, UInt::Any, Float::Any, Enum::Any, Flags::Any, String::Any, 21 | AdjacencyList::Any, Navigable::Any, Vec2::Any, Set::Any, Vector::Any, Vector::Any, Vector::Any, Vector::Any, Vector::Any, 22 | TextBuffer::Any>; 23 | } // namespace Core 24 | } // namespace Action 25 | -------------------------------------------------------------------------------- /src/Core/ID.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | An ID is used to uniquely identify something. 7 | 8 | **Notable usage:** 9 | `Component::Id` reflects the state member's `StorePath Path`, using `ImHashStr` to calculate its own `Id` using its parent's `Id` as a seed. 10 | In the same way, each segment in `Component::Path` is calculated by appending its own `PathSegment` to its parent's `Path`. 11 | This exactly reflects the way ImGui calculates its window/tab/dockspace/etc. ID calculation. 12 | A drawable `Component` uses its `ID` (which is also an `ImGuiID`) as the ID for the top-level `ImGui` widget rendered during its `Draw` call. 13 | This results in the nice property that we can find any `Component` instance by calling `Component::ById.contains(ImGui::GetHoveredID())` any time during a `Draw`. 14 | */ 15 | using ID = unsigned int; // Same type as `ImGuiID` 16 | 17 | ID GenerateId(ID parent_id, ID child_id); 18 | ID GenerateId(ID parent_id, std::string_view child_id); 19 | -------------------------------------------------------------------------------- /src/Core/Container/Vector.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "immer/flex_vector.hpp" 4 | 5 | #include "Core/Component.h" 6 | 7 | template struct Vector : Component { 8 | using Component::Component; 9 | using ContainerT = immer::flex_vector; 10 | 11 | ~Vector() { 12 | Erase(_S); 13 | } 14 | 15 | void SetJson(TransientStore &, json &&) const override; 16 | json ToJson() const override; 17 | 18 | ContainerT Get() const; 19 | T operator[](u32 i) const; 20 | 21 | void Set(TransientStore &, ContainerT) const; 22 | void Set(TransientStore &, size_t i, T) const; 23 | void PushBack(TransientStore &, T) const; 24 | void PopBack(TransientStore &) const; 25 | void Resize(TransientStore &, size_t) const; 26 | void Clear(TransientStore &) const; 27 | void Erase(TransientStore &) const override; 28 | void Erase(TransientStore &, size_t i) const; 29 | 30 | void RenderValueTree(bool annotate, bool auto_select) const override; 31 | }; 32 | -------------------------------------------------------------------------------- /src/Core/UI/InvisibleButton.cpp: -------------------------------------------------------------------------------- 1 | #include "InvisibleButton.h" 2 | 3 | #include "imgui_internal.h" 4 | 5 | using namespace ImGui; 6 | 7 | namespace flowgrid { 8 | InteractionFlags InvisibleButton(const ImVec2 &size_arg, const char *id) { 9 | auto *window = GetCurrentWindow(); 10 | if (window->SkipItems) return false; 11 | 12 | const auto imgui_id = window->GetID(id); 13 | const auto size = CalcItemSize(size_arg, 0.0f, 0.0f); 14 | const auto &cursor = GetCursorScreenPos(); 15 | const ImRect rect{cursor, cursor + size}; 16 | if (!ItemAdd(rect, imgui_id)) return false; 17 | 18 | InteractionFlags flags = InteractionFlags_None; 19 | static bool hovered, held; 20 | if (ButtonBehavior(rect, imgui_id, &hovered, &held, ImGuiButtonFlags_AllowOverlap)) { 21 | flags |= InteractionFlags_Clicked; 22 | } 23 | if (hovered) flags |= InteractionFlags_Hovered; 24 | if (held) flags |= InteractionFlags_Held; 25 | 26 | return flags; 27 | } 28 | } // namespace flowgrid 29 | -------------------------------------------------------------------------------- /src/Audio/Graph/ma_data_passthrough_node/ma_data_passthrough_node.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "miniaudio.h" 4 | 5 | /* Based on `ma_data_source_node`. 1 input bus, 1 output bus. Captures most recently read input buffer in a buffer ref. */ 6 | struct ma_data_passthrough_node_config { 7 | ma_node_config node_config; 8 | ma_uint32 channels; 9 | ma_audio_buffer_ref *buffer_ref; 10 | }; 11 | 12 | // If `buffer_ref` is empty, this will be a passthrough node. Otherwise, the output will be silenced. 13 | ma_data_passthrough_node_config ma_data_passthrough_node_config_init(ma_uint32 channels, ma_audio_buffer_ref *buffer_ref); 14 | 15 | struct ma_data_passthrough_node { 16 | ma_node_base base; 17 | ma_audio_buffer_ref *buffer_ref; 18 | }; 19 | 20 | ma_result ma_data_passthrough_node_init(ma_node_graph *, const ma_data_passthrough_node_config *, const ma_allocation_callbacks *, ma_data_passthrough_node *); 21 | void ma_data_passthrough_node_uninit(ma_data_passthrough_node *, const ma_allocation_callbacks *); 22 | -------------------------------------------------------------------------------- /src/Core/Project/ProjectAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Action/DefineAction.h" 4 | 5 | DefineActionType( 6 | Project, 7 | DefineAction(ShowOpenDialog, Unsaved, Merge, "~Open project"); 8 | DefineAction(ShowSaveDialog, Unsaved, Merge, "~Save project as..."); 9 | 10 | DefineAction(Undo, Unsaved, NoMerge, ""); 11 | DefineAction(Redo, Unsaved, NoMerge, ""); 12 | DefineAction(SetHistoryIndex, Unsaved, NoMerge, "", u32 index;); 13 | DefineAction(Open, Unsaved, NoMerge, "", fs::path file_path;); 14 | DefineAction(OpenEmpty, Unsaved, NoMerge, "~New project"); 15 | DefineAction(OpenDefault, Unsaved, NoMerge, ""); 16 | DefineAction(Save, Unsaved, NoMerge, "", fs::path file_path;); 17 | DefineAction(SaveDefault, Unsaved, NoMerge, ""); 18 | DefineAction(SaveCurrent, Unsaved, NoMerge, "~Save project"); 19 | 20 | using Any = ActionVariant< 21 | Undo, Redo, SetHistoryIndex, 22 | Open, OpenEmpty, OpenDefault, Save, SaveDefault, SaveCurrent, ShowOpenDialog, ShowSaveDialog>; 23 | ); 24 | -------------------------------------------------------------------------------- /src/Core/Container/AdjacencyList.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Component.h" 4 | #include "Core/Store/IdPairs.h" 5 | 6 | struct AdjacencyList : Component { 7 | using Component::Component; 8 | 9 | using Edge = IdPair; // Source, destination 10 | 11 | ~AdjacencyList() { 12 | Erase(_S); 13 | } 14 | 15 | IdPairs Get() const; 16 | 17 | void SetJson(TransientStore &, json &&) const override; 18 | json ToJson() const override; 19 | 20 | void Erase(TransientStore &) const override; 21 | void RenderValueTree(bool annotate, bool auto_select) const override; 22 | 23 | bool HasPath(ID source, ID destination) const; 24 | bool IsConnected(ID source, ID destination) const; 25 | 26 | u32 SourceCount(ID destination) const; 27 | u32 DestinationCount(ID source) const; 28 | 29 | void Add(TransientStore &, IdPair &&) const; 30 | void Connect(TransientStore &, ID source, ID destination) const; 31 | void Disconnect(TransientStore &, ID source, ID destination) const; 32 | void DisconnectOutput(TransientStore &, ID id) const; 33 | }; 34 | -------------------------------------------------------------------------------- /src/Core/Project/Preferences.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "Core/Helper/Path.h" 7 | 8 | struct Preferences { 9 | inline static const std::string FileExtension = ".flp"; 10 | inline static const fs::path 11 | Path = fs::path(".flowgrid") / ("Preferences" + FileExtension), 12 | // todo thinking of digging into grammars' `config.json` files to automatically find the supported file extensions... 13 | TreeSitterGrammarsPath = fs::path("..") / "lib" / "tree-sitter-grammars", 14 | // todo recursively copy `queries` dir to build dir in CMake. 15 | TreeSitterQueriesPath = fs::path("..") / "src" / "FlowGrid" / "TextEditor" / "queries"; 16 | 17 | Preferences(); 18 | 19 | bool Write() const; 20 | bool Clear(); // Clear and re-save default preferences. 21 | 22 | void OnProjectOpened(const fs::path &); 23 | 24 | // Saved fields: 25 | std::list RecentlyOpenedPaths{}; 26 | fs::path TreeSitterConfigPath{fs::path{"~"} / "Library" / "Application Support" / "tree-sitter" / "config.json"}; 27 | }; 28 | -------------------------------------------------------------------------------- /src/Core/Style/ProjectStyle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/ActionProducerComponent.h" 4 | #include "Core/Container/Colors.h" 5 | #include "Core/Primitive/Float.h" 6 | #include "StyleAction.h" // todo just ProjectStyle action 7 | 8 | struct ImVec4; 9 | 10 | enum ProjectCol_ { 11 | ProjectCol_GestureIndicator, // 2nd series in ImPlot color map (same in all 3 styles for now): `ImPlot::GetColormapColor(1, 0)` 12 | ProjectCol_HighlightText, // ImGuiCol_PlotHistogramHovered 13 | ProjectCol_Flash, // ImGuiCol_FrameBgActive 14 | ProjectCol_COUNT 15 | }; 16 | using ProjectCol = int; 17 | 18 | struct ProjectStyle : ActionProducerComponent { 19 | ProjectStyle(ArgsT &&); 20 | 21 | static std::unordered_map ColorsDark, ColorsLight, ColorsClassic; 22 | static const char *GetColorName(ProjectCol idx); 23 | 24 | Prop_(Float, FlashDurationSec, "?Duration (sec) of short flashes to visually notify on events.", 0.2, 0.1, 1); 25 | Prop(Colors, Colors, ProjectCol_COUNT, GetColorName); 26 | 27 | protected: 28 | void Render() const override; 29 | }; 30 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AccessModifierOffset: -4 3 | AlignAfterOpenBracket: BlockIndent 4 | AlignOperands: DontAlign 5 | BreakBeforeTernaryOperators: false 6 | AlignTrailingComments: false 7 | AllowShortBlocksOnASingleLine: Always 8 | AllowShortCaseLabelsOnASingleLine: true 9 | AllowShortEnumsOnASingleLine: true 10 | AllowShortLoopsOnASingleLine: true 11 | AllowShortIfStatementsOnASingleLine: AllIfsAndElse 12 | AllowShortFunctionsOnASingleLine: true 13 | AlwaysBreakTemplateDeclarations: No 14 | AllowAllArgumentsOnNextLine: true 15 | AllowAllConstructorInitializersOnNextLine: true 16 | AllowAllParametersOfDeclarationOnNextLine: true 17 | BinPackArguments: true 18 | BinPackParameters: true 19 | BreakStringLiterals: true 20 | ColumnLimit: 0 21 | EmptyLineAfterAccessModifier: Never 22 | EmptyLineBeforeAccessModifier: Always 23 | IndentCaseLabels: true 24 | IndentWidth: 4 25 | KeepEmptyLinesAtTheStartOfBlocks: false 26 | Language: Cpp 27 | MaxEmptyLinesToKeep: 1 28 | NamespaceIndentation: None 29 | SortIncludes: true 30 | SortUsingDeclarations: true 31 | SpaceAfterTemplateKeyword: false 32 | SpaceBeforeParens: ControlStatements 33 | TabWidth: 4 34 | UseTab: Never 35 | -------------------------------------------------------------------------------- /src/Core/Container/Colors.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Container/Vector.h" 4 | 5 | struct ImVec4; 6 | 7 | struct Colors : Vector { 8 | Colors(ComponentArgs &&, u32 size, std::function get_name, const bool allow_auto = false); 9 | 10 | static u32 Float4ToU32(const ImVec4 &); 11 | static ImVec4 U32ToFloat4(u32); 12 | 13 | // An arbitrary transparent color is used to mark colors as "auto". 14 | // Using a the unique bit pattern `010101` for the RGB components so as not to confuse it with black/white-transparent. 15 | // Similar to ImPlot's usage of [`IMPLOT_AUTO_COL = ImVec4(0,0,0,-1)`](https://github.com/epezent/implot/blob/master/implot.h#L67). 16 | static constexpr u32 AutoColor = 0X00010101; 17 | 18 | void Set(TransientStore &, const std::vector &) const; 19 | void Set(TransientStore &, const std::unordered_map &) const; 20 | 21 | void RenderValueTree(bool annotate, bool auto_select) const override; 22 | 23 | protected: 24 | void Render() const override; 25 | 26 | private: 27 | std::function GetName; 28 | bool AllowAuto; 29 | }; 30 | -------------------------------------------------------------------------------- /src/Core/Store/Store.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "StoreBase.h" 4 | 5 | #include "Core/Scalar.h" 6 | #include "Core/TextEditor/TextBufferData.h" 7 | #include "IdPairs.h" 8 | 9 | // Define persistent and transient maps types without needing to repeat the value types. 10 | template struct StoreTypesBase { 11 | using Persistent = StoreMaps; 12 | using Transient = TransientStoreMaps; 13 | }; 14 | 15 | using StoreTypes = StoreTypesBase< 16 | bool, u32, s32, float, std::string, IdPairs, TextBufferData, 17 | immer::set, immer::flex_vector, immer::flex_vector, 18 | immer::flex_vector, immer::flex_vector, immer::flex_vector>; 19 | 20 | // Support forward declaration of store types. 21 | struct PersistentStore : StoreTypes::Persistent { 22 | PersistentStore() = default; 23 | PersistentStore(StoreTypes::Persistent &&persistent) : StoreTypes::Persistent(std::move(persistent)) {} 24 | }; 25 | struct TransientStore : StoreTypes::Transient { 26 | TransientStore() = default; 27 | TransientStore(StoreTypes::Transient &&transient) : StoreTypes::Transient(std::move(transient)) {} 28 | }; 29 | -------------------------------------------------------------------------------- /src/Core/Action/ActionProducer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | template struct ActionProducer { 7 | using ProducedActionType = T; 8 | using EnqueueFn = std::function; 9 | 10 | ActionProducer(EnqueueFn &&owned_q) : Q(std::move(owned_q)) {} 11 | virtual ~ActionProducer() = default; 12 | 13 | // `SubProducer` supports action producers that only know about a subset action type (an action variant composed 14 | // only of members also in `ActionType`) to queue their actions into this superset-producer's queue. 15 | // An instance of `SubProducer` can be used as an `ActionProducer::EnqueueFn`. 16 | template struct SubProducer { 17 | SubProducer(const ActionProducer &producer) : Producer(producer) {} 18 | 19 | bool operator()(ActionSubType &&action) { 20 | return std::visit([this](auto &&a) -> bool { return Producer.Q(std::move(a)); }, std::move(action)); 21 | } 22 | 23 | const ActionProducer &Producer; 24 | }; 25 | 26 | EnqueueFn Q; 27 | }; 28 | -------------------------------------------------------------------------------- /src/Core/Container/Navigable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Container/Vector.h" 4 | #include "Core/Primitive/UInt.h" 5 | 6 | template struct Navigable : Component { 7 | Navigable(ComponentArgs &&args) : Component(std::move(args)) { 8 | Refresh(); 9 | } 10 | ~Navigable() { 11 | Erase(_S); 12 | } 13 | 14 | void IssueClear() const; 15 | void IssuePush(T) const; 16 | void IssueMoveTo(u32 index) const; 17 | void IssueStepForward() const; 18 | void IssueStepBackward() const; 19 | 20 | bool Empty() const { return Value.Get().empty(); } 21 | bool CanStepBackward() const { return u32(Cursor) > 0; } 22 | bool CanStepForward() const { 23 | const auto value = Value.Get(); 24 | return !value.empty() && u32(Cursor) < value.size() - 1u; 25 | } 26 | 27 | auto operator[](u32 index) { return Value[index]; } 28 | T operator*() const { return Value[Cursor]; } 29 | 30 | void RenderValueTree(bool annotate, bool auto_select) const override { 31 | Component::RenderValueTree(annotate, auto_select); // todo 32 | } 33 | 34 | Prop(Vector, Value); 35 | Prop(UInt, Cursor); 36 | }; 37 | -------------------------------------------------------------------------------- /src/Core/UI/Styling.cpp: -------------------------------------------------------------------------------- 1 | #include "Styling.h" 2 | 3 | #include "imgui_internal.h" 4 | 5 | using std::string, std::string_view; 6 | using namespace ImGui; 7 | 8 | float CalcAlignedX(HJustify h_justify, float inner_w, float outer_w, bool is_label) { 9 | if (h_justify == HJustify_Middle || (is_label && inner_w < outer_w)) return (outer_w - inner_w) / 2; 10 | if (h_justify == HJustify_Left) return 0; 11 | return outer_w - inner_w; 12 | } 13 | float CalcAlignedY(VJustify v_justify, float inner_h, float outer_h) { 14 | if (v_justify == VJustify_Middle) return (outer_h - inner_h) / 2; 15 | if (v_justify == VJustify_Top) return 0; 16 | return outer_h - inner_h; 17 | } 18 | 19 | ImVec2 CalcTextSize(string_view text) { return CalcTextSize(text.data()); } 20 | 21 | // todo very inefficient 22 | void Ellipsify(string &str, float max_width) { 23 | while (CalcTextSize(str).x > max_width && str.length() > 4) str.replace(str.end() - 4, str.end(), "..."); 24 | } 25 | 26 | void FillRowItemBg(u32 color) { 27 | const ImVec2 row_min{GetWindowPos().x, GetCursorScreenPos().y}; 28 | GetWindowDrawList()->AddRectFilled(row_min, row_min + ImVec2{GetWindowWidth(), GetFontSize()}, color); 29 | } 30 | -------------------------------------------------------------------------------- /src/Audio/Faust/FaustParam.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Audio/Sample.h" 4 | 5 | #include "Core/Primitive/Float.h" 6 | #include "Core/UI/NamesAndValues.h" 7 | #include "FaustParamBase.h" 8 | 9 | struct FaustParam : FaustParamBase, Float { 10 | FaustParam(ComponentArgs &&, const FaustParamsStyle &style, const FaustParamType type = Type_None, std::string_view label = "", Real *zone = nullptr, Real min = 0, Real max = 0, Real init = 0, Real step = 0, const char *tooltip = nullptr, NamesAndValues names_and_values = {}); 11 | 12 | void Render(const float suggested_height, bool no_label = false) const override; 13 | 14 | Real *Zone; // Only meaningful for widget params (not groups). 15 | const Real Min, Max; // Only meaningful for sliders, num-entries, and bar graphs. 16 | const Real Init, Step; // Only meaningful for sliders and num-entries. 17 | const char *Tooltip; // Only populated for params (not groups). 18 | const NamesAndValues names_and_values; // Only nonempty for menus and radio buttons. 19 | 20 | float CalcWidth(bool include_label) const override; 21 | 22 | void Refresh() override; 23 | 24 | private: 25 | void Render() const override { Render(0); } 26 | }; 27 | -------------------------------------------------------------------------------- /src/Audio/Faust/FaustGraph.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/ActionProducerComponent.h" 4 | #include "Core/Container/Navigable.h" 5 | #include "FaustGraphAction.h" 6 | 7 | class CTreeBase; 8 | typedef CTreeBase *Box; 9 | 10 | namespace flowgrid { 11 | struct Node; 12 | } 13 | 14 | struct FaustGraphStyle; 15 | struct FaustGraphSettings; 16 | 17 | struct FaustGraph : ActionProducerComponent { 18 | FaustGraph(ArgsT &&, const FaustGraphStyle &, const FaustGraphSettings &); 19 | ~FaustGraph(); 20 | 21 | float GetScale() const; 22 | 23 | void SaveBoxSvg(const fs::path &dir_path) const; 24 | void SetBox(Box); 25 | void ResetBox(); // Set to the box of the current root node. 26 | 27 | Prop(UInt, DspId); 28 | Prop(Navigable, NodeNavigationHistory); 29 | 30 | const FaustGraphStyle &Style; 31 | const FaustGraphSettings &Settings; 32 | 33 | Box _Box; 34 | mutable std::unordered_map NodeByImGuiId; 35 | std::unique_ptr RootNode{}; 36 | 37 | private: 38 | void Render() const override; 39 | 40 | flowgrid::Node *Tree2Node(Box) const; 41 | flowgrid::Node *Tree2NodeInner(Box) const; 42 | }; 43 | -------------------------------------------------------------------------------- /src/Audio/Faust/FaustParams.cpp: -------------------------------------------------------------------------------- 1 | #include "FaustParams.h" 2 | #include "FaustParamsUI.h" 3 | 4 | #include "Audio/Sample.h" // Must be included before any Faust includes. 5 | #include "faust/dsp/dsp.h" 6 | 7 | #include 8 | 9 | FaustParams::FaustParams(ComponentArgs &&args, const FaustParamsStyle &style) 10 | : Component(std::move(args)), Style(style) {} 11 | 12 | FaustParams::~FaustParams() { 13 | if (Dsp) Dsp->instanceResetUserInterface(); 14 | } 15 | 16 | void FaustParams::SetDsp(dsp *dsp) { 17 | if (Dsp) { 18 | Dsp->instanceResetUserInterface(); 19 | if (!dsp) Impl.reset(); 20 | } 21 | Dsp = dsp; 22 | if (Dsp) { 23 | Impl = std::make_unique(*this); 24 | Dsp->buildUserInterface(Impl.get()); 25 | } 26 | } 27 | 28 | void FaustParams::Render() const { 29 | if (!Impl) return; 30 | 31 | RootGroup.Render(ImGui::GetContentRegionAvail().y, true); 32 | 33 | // if (hovered_node) { 34 | // const string label = GetUiLabel(hovered_node->tree); 35 | // if (!label.empty()) { 36 | // const auto *widget = GetWidget(label); 37 | // if (widget) cout << "Found widget: " << label << '\n'; 38 | // } 39 | // } 40 | } 41 | -------------------------------------------------------------------------------- /src/Core/Container/Vec2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Component.h" 4 | #include "Core/Primitive/Bool.h" 5 | #include "Core/Primitive/Float.h" 6 | 7 | struct ImVec2; 8 | 9 | struct Vec2 : Component { 10 | // `fmt` defaults to ImGui slider default, which is "%.3f" 11 | Vec2(ComponentArgs &&, std::pair &&value = {0, 0}, float min = 0, float max = 1, const char *fmt = nullptr); 12 | ~Vec2() = default; 13 | 14 | operator ImVec2() const; 15 | 16 | void Set(TransientStore &, std::pair) const; 17 | 18 | Float X, Y; 19 | 20 | protected: 21 | virtual void Render(ImGuiSliderFlags) const; 22 | void Render() const override; 23 | 24 | std::pair Value; 25 | }; 26 | 27 | struct Vec2Linked : Vec2 { 28 | // Defaults to linked. 29 | Vec2Linked(ComponentArgs &&, std::pair &&value = {0, 0}, float min = 0, float max = 1, const char *fmt = nullptr); 30 | Vec2Linked(ComponentArgs &&, std::pair &&value, float min, float max, bool linked, const char *fmt = nullptr); 31 | ~Vec2Linked() = default; 32 | 33 | Bool Linked; 34 | 35 | protected: 36 | void Render(ImGuiSliderFlags) const override; 37 | void Render() const override; 38 | }; 39 | -------------------------------------------------------------------------------- /src/Audio/Graph/ma_faust_node/ma_faust_node.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "miniaudio.h" 4 | 5 | class dsp; 6 | 7 | struct ma_faust_node_config { 8 | ma_node_config node_config; 9 | dsp *faust_dsp; 10 | ma_uint32 sample_rate; 11 | ma_uint32 buffer_frames; 12 | }; 13 | 14 | ma_faust_node_config ma_faust_node_config_init(dsp *, ma_uint32 sample_rate, ma_uint32 buffer_frames); 15 | 16 | struct ma_faust_node { 17 | ma_node_base base; 18 | ma_faust_node_config config; 19 | // These deinterleaved buffers are only created if the respective direction of the Faust node is multi-channel. 20 | float **in_buffer; 21 | float **out_buffer; 22 | }; 23 | 24 | ma_result ma_faust_node_init(ma_node_graph *, const ma_faust_node_config *, const ma_allocation_callbacks *, ma_faust_node *); 25 | void ma_faust_node_uninit(ma_faust_node *, const ma_allocation_callbacks *); 26 | 27 | ma_uint32 ma_faust_dsp_get_in_channels(dsp *); 28 | ma_uint32 ma_faust_dsp_get_out_channels(dsp *); 29 | ma_uint32 ma_faust_node_get_in_channels(ma_faust_node *); 30 | ma_uint32 ma_faust_node_get_out_channels(ma_faust_node *); 31 | 32 | ma_uint32 ma_faust_node_get_sample_rate(ma_faust_node *); 33 | dsp *ma_faust_node_get_dsp(ma_faust_node *); 34 | 35 | ma_result ma_faust_node_set_sample_rate(ma_faust_node *, ma_uint32 sample_rate); 36 | ma_result ma_faust_node_set_dsp(ma_faust_node *, dsp *); 37 | -------------------------------------------------------------------------------- /src/Core/TextEditor/TextInputEdit.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | using u32 = unsigned int; 4 | 5 | /** 6 | Holds the byte parts of `TSInputEdit` (not the points). 7 | tree-sitter API functions generally handle only having bytes populated. 8 | (E.g. see https://github.com/tree-sitter/tree-sitter/issues/445) 9 | `StartByte`: Start position of the text change. 10 | `OldEndByte`: End position of the original text before the change. 11 | - For insertion, same as `start`. 12 | - For replacement, where the replaced text ended. 13 | - For deletion, where the deleted text ended. 14 | `NewEndByte`: End position of the new text after the change. 15 | - For insertion or replacement, where the new text ends. 16 | - For deletion, same as `start`. 17 | **/ 18 | struct TextInputEdit { 19 | u32 StartByte{0}, OldEndByte{0}, NewEndByte{0}; 20 | 21 | TextInputEdit Invert() const { return TextInputEdit{StartByte, NewEndByte, OldEndByte}; } 22 | bool IsInsert() const { return StartByte == OldEndByte; } 23 | bool IsDelete() const { return StartByte == NewEndByte; } 24 | 25 | bool operator==(const TextInputEdit &) const = default; 26 | bool operator!=(const TextInputEdit &) const = default; 27 | auto operator<=>(const TextInputEdit &o) const { 28 | if (auto cmp = StartByte <=> o.StartByte; cmp != 0) return cmp; 29 | if (auto cmp = OldEndByte <=> o.OldEndByte; cmp != 0) return cmp; 30 | return NewEndByte <=> o.NewEndByte; 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/Core/Scalar.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | Redefining [ImGui's scalar data types](https://github.com/ocornut/imgui/blob/master/imgui.h#L223-L232) 5 | 6 | This is done in order to: 7 | * clarify & document the actual meanings of the FlowGrid integer type aliases below, and 8 | * emphasize the importance of FlowGrid integer types reflecting ImGui types. 9 | 10 | If it wasn't important to keep FlowGrid's integer types mapped 1:1 to ImGui's, we would be using 11 | [C++11's fixed width integer types](https://en.cppreference.com/w/cpp/types/integer) instead. 12 | 13 | Make sure to double check once in a blue moon that the ImGui types have not changed! 14 | */ 15 | using ImGuiID = unsigned int; 16 | using ImS8 = signed char; // 8-bit signed integer 17 | using ImU8 = unsigned char; // 8-bit unsigned integer 18 | using ImS16 = signed short; // 16-bit signed integer 19 | using ImU16 = unsigned short; // 16-bit unsigned integer 20 | using ImS32 = signed int; // 32-bit signed integer == int 21 | using ImU32 = unsigned int; // 32-bit unsigned integer (used to store packed colors & positions) 22 | using ImS64 = signed long long; // 64-bit signed integer 23 | using ImU64 = unsigned long long; // 64-bit unsigned integer 24 | 25 | // Scalar data types, pointing to ImGui scalar types, with `{typename} = Im{Typename}`. 26 | using s8 = ImS8; 27 | using u8 = ImU8; 28 | using s16 = ImS16; 29 | using u16 = ImU16; 30 | using s32 = ImS32; 31 | using u32 = ImU32; 32 | using s64 = ImS64; 33 | using u64 = ImU64; 34 | -------------------------------------------------------------------------------- /src/Core/Helper/Color.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | using u32 = unsigned int; 7 | 8 | constexpr u32 ColShiftA = 24, ColShiftR = 0, ColShiftG = 8, ColShiftB = 16; 9 | 10 | // Copy of `IM_COL32` logic (doesn't respect `IMGUI_USE_BGRA_PACKED_COLOR` since we don't use that). 11 | constexpr u32 Col32(u32 r, u32 g, u32 b, u32 a = 255) { 12 | return (a << ColShiftA) | (b << ColShiftB) | (g << ColShiftG) | (r << ColShiftR); 13 | } 14 | constexpr u32 HexToCol32(const std::string_view hex) { 15 | if (hex.empty() || hex.front() != '#' || (hex.size() != 7 && hex.size() != 9)) return Col32(255, 255, 255); 16 | 17 | const u32 c = std::stoul(std::string{hex.substr(1)}, nullptr, 16); 18 | // Assume full opacity if alpha is not specified. 19 | return Col32((c >> 16) & 0xFF, (c >> 8) & 0xFF, (c >> 0) & 0xFF, hex.length() == 7 ? 0xFF : ((c >> 24) & 0xFF)); 20 | } 21 | 22 | constexpr u32 GetRed(u32 c) { return (c >> ColShiftR) & 0xFF; } 23 | constexpr u32 GetGreen(u32 c) { return (c >> ColShiftG) & 0xFF; } 24 | constexpr u32 GetBlue(u32 c) { return (c >> ColShiftB) & 0xFF; } 25 | constexpr u32 GetAlpha(u32 c) { return c >> ColShiftA; } 26 | 27 | constexpr u32 SetRed(u32 c, u32 r) { return (c & 0xFF00FFFF) | (r << 0); } 28 | constexpr u32 SetGreen(u32 c, u32 g) { return (c & 0xFFFF00FF) | (g << 8); } 29 | constexpr u32 SetBlue(u32 c, u32 b) { return (c & 0xFFFFFF00) | (b << 16); } 30 | constexpr u32 SetAlpha(u32 c, u32 a) { return (c & 0x00FFFFFF) | (a << 24); } 31 | -------------------------------------------------------------------------------- /src/Core/Container/VectorAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Action/DefineAction.h" 4 | 5 | namespace Action { 6 | template struct Vector { 7 | static_assert(always_false_v, "There is no `Vector` action type for this type."); 8 | }; 9 | 10 | DefineTemplatedActionType( 11 | Vector, Bool, bool, 12 | DefineComponentAction(Set, Saved, SameIdMerge, "", u32 i; bool value;); 13 | 14 | using Any = ActionVariant; 15 | ); 16 | 17 | DefineTemplatedActionType( 18 | Vector, Int, int, 19 | DefineComponentAction(Set, Saved, SameIdMerge, "", u32 i; int value;); 20 | 21 | using Any = ActionVariant; 22 | ); 23 | 24 | DefineTemplatedActionType( 25 | Vector, UInt, u32, 26 | DefineComponentAction(Set, Saved, SameIdMerge, "", u32 i; u32 value;); 27 | 28 | using Any = ActionVariant; 29 | ); 30 | 31 | DefineTemplatedActionType( 32 | Vector, Float, float, 33 | DefineComponentAction(Set, Saved, SameIdMerge, "", u32 i; float value;); 34 | 35 | using Any = ActionVariant; 36 | ); 37 | 38 | DefineTemplatedActionType( 39 | Vector, String, std::string, 40 | DefineComponentAction(Set, Saved, SameIdMerge, "", u32 i; std::string value;); 41 | 42 | using Any = ActionVariant; 43 | ); 44 | 45 | ComponentActionJson(Vector::Set, i, value); 46 | ComponentActionJson(Vector::Set, i, value); 47 | ComponentActionJson(Vector::Set, i, value); 48 | ComponentActionJson(Vector::Set, i, value); 49 | ComponentActionJson(Vector::Set, i, value); 50 | } // namespace Action 51 | -------------------------------------------------------------------------------- /src/Core/Windows.cpp: -------------------------------------------------------------------------------- 1 | #include "Windows.h" 2 | 3 | #include "imgui_internal.h" 4 | 5 | bool Windows::IsDock(ID id) const { return DockComponentIds.contains(id); } 6 | 7 | void Windows::Register(TransientStore &s, ID id, bool dock) { 8 | WindowComponentIds.insert(id); 9 | VisibleComponentIds.Insert(s, id); 10 | if (dock) DockComponentIds.insert(id); 11 | } 12 | bool Windows::IsWindow(ID id) const { return WindowComponentIds.contains(id); } 13 | bool Windows::IsVisible(ID id) const { return VisibleComponentIds.Get().count(id); } 14 | void Windows::ToggleVisible(TransientStore &s, ID id) const { 15 | if (!VisibleComponentIds.Get().count(id)) VisibleComponentIds.Insert(s, id); 16 | else VisibleComponentIds.Erase(s, id); 17 | } 18 | 19 | using namespace ImGui; 20 | 21 | void Windows::DrawMenuItem(const Component &c) const { 22 | if (MenuItem(c.ImGuiLabel.c_str(), nullptr, IsVisible(c.Id))) { 23 | Q(Action::Windows::ToggleVisible{c.Id}); 24 | } 25 | }; 26 | 27 | void Windows::Render() const { 28 | for (const ID id : VisibleComponentIds.Get()) { 29 | const auto *component = Component::ById.at(id); 30 | auto flags = component->WindowFlags; 31 | if (!component->WindowMenu.Items.empty()) flags |= ImGuiWindowFlags_MenuBar; 32 | 33 | bool open = true; 34 | if (Begin(component->ImGuiLabel.c_str(), &open, flags)) { 35 | component->WindowMenu.Draw(); 36 | component->Draw(); 37 | } 38 | End(); 39 | 40 | if (!open) Q(Action::Windows::ToggleVisible{id}); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Core/UI/Styling.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Fonts.h" 6 | 7 | using u32 = unsigned int; 8 | 9 | struct ImVec2; 10 | 11 | enum Dir_ { 12 | Dir_None = -1, 13 | Dir_Left = 0, 14 | Dir_Right = 1, 15 | Dir_Up = 2, 16 | Dir_Down = 3, 17 | Dir_COUNT 18 | }; 19 | 20 | // Uses same argument ordering as CSS. 21 | struct Padding { 22 | const float Top, Right, Bottom, Left; 23 | 24 | Padding(float top, float right, float bottom, float left) : Top(top), Right(right), Bottom(bottom), Left(left) {} 25 | Padding(float top, float x, float bottom) : Padding(top, x, bottom, x) {} 26 | Padding(float y, float x) : Padding(y, x, y, x) {} 27 | Padding(float all) : Padding(all, all, all, all) {} 28 | Padding() : Padding(0, 0, 0, 0) {} 29 | }; 30 | 31 | enum HJustify_ { 32 | HJustify_Left, 33 | HJustify_Middle, 34 | HJustify_Right, 35 | }; 36 | enum VJustify_ { 37 | VJustify_Top, 38 | VJustify_Middle, 39 | VJustify_Bottom, 40 | }; 41 | using HJustify = int; 42 | using VJustify = int; 43 | 44 | struct Justify { 45 | HJustify h; 46 | VJustify v; 47 | }; 48 | 49 | float CalcAlignedX(HJustify h_justify, float inner_w, float outer_w, bool is_label = false); // todo better name than `is_label` 50 | float CalcAlignedY(VJustify v_justify, float inner_h, float outer_h); 51 | 52 | ImVec2 CalcTextSize(const std::string_view); 53 | 54 | // There's `RenderTextEllipsis` in `imgui_internal`, but it's way too complex and scary. 55 | void Ellipsify(std::string &, float max_width); 56 | 57 | void FillRowItemBg(u32 color); 58 | -------------------------------------------------------------------------------- /src/Core/Action/ActionMenuItem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ActionableProducer.h" 4 | #include "Core/Helper/Variant.h" 5 | #include "Core/MenuItemDrawable.h" 6 | 7 | #include "imgui.h" 8 | 9 | template struct ActionMenuItem : MenuItemDrawable { 10 | using EnqueueFn = ActionProducer::EnqueueFn; 11 | using ProducerOrQ = std::variant>, EnqueueFn>; 12 | 13 | ActionMenuItem(const Actionable &actionable, EnqueueFn q, ActionType &&action = {}, std::string_view shortcut = "") 14 | : Actionable(actionable), Q(std::move(q)), Action(std::move(action)), Shortcut(shortcut) {} 15 | ActionMenuItem(const ActionableProducer &actionable, ActionType &&action = {}, std::string_view shortcut = "") 16 | : Actionable(actionable), Q(actionable), Action(std::move(action)), Shortcut(shortcut) {} 17 | ~ActionMenuItem() override = default; 18 | 19 | void MenuItem() const override { 20 | if (ImGui::MenuItem(Action.GetMenuLabel().c_str(), Shortcut.c_str(), false, Actionable.CanApply(Action))) { 21 | std::visit( 22 | Match{ 23 | [this](const ActionProducer &producer) { producer.Q(Action); }, 24 | [this](const EnqueueFn &q) { q(ActionType{Action}); }, 25 | }, 26 | Q 27 | ); 28 | } 29 | } 30 | 31 | const Actionable &Actionable; 32 | ProducerOrQ Q; 33 | const ActionType Action{}; 34 | std::string Shortcut; 35 | }; 36 | -------------------------------------------------------------------------------- /src/Core/Store/Patch/PatchJson.cpp: -------------------------------------------------------------------------------- 1 | #include "PatchJson.h" 2 | 3 | #include 4 | 5 | namespace nlohmann { 6 | void to_json(json &j, const PrimitiveVariant &value) { 7 | if (std::holds_alternative(value)) { 8 | j = std::format("{:#08X}", std::get(value)); 9 | } else if (std::holds_alternative(value) && std::isnan(std::get(value))) { 10 | j = "NaN"; 11 | } else { 12 | std::visit([&](auto &&inner_value) { j = std::forward(inner_value); }, value); 13 | } 14 | } 15 | void from_json(const json &j, PrimitiveVariant &field) { 16 | if (j.is_boolean()) field = j.get(); 17 | else if (j.is_number_integer()) field = j.get(); 18 | else if (j.is_number_float()) field = j.get(); 19 | else if (j.is_string()) { 20 | const auto str = j.get(); 21 | if (str == "NaN") field = NAN; 22 | else if (str.starts_with("0X")) field = u32(std::stoul(str, nullptr, 0)); 23 | else field = str; 24 | } else throw std::runtime_error(std::format("Could not parse Primitive JSON value: {}", j.dump())); 25 | } 26 | 27 | void to_json(json &j, const PatchOp &op) { 28 | j = json{{"op", ToString(op.Op)}}; 29 | optional_to_json(j, "value", op.Value); 30 | optional_to_json(j, "old", op.Old); 31 | } 32 | void from_json(const json &j, PatchOp &op) { 33 | op.Op = ToPatchOpType(j.at("op").get()); 34 | if (j.contains("value")) optional_from_json(j, "value", op.Value); 35 | if (j.contains("old")) optional_from_json(j, "old", op.Old); 36 | } 37 | } // namespace nlohmann 38 | -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #=================== SDL3 =================== 2 | 3 | set(SDL3_DIR ${CMAKE_CURRENT_SOURCE_DIR}/SDL) 4 | add_subdirectory(${SDL3_DIR}) 5 | 6 | #=================== json =================== 7 | 8 | add_subdirectory(json) 9 | 10 | #=================== faust =================== 11 | # See [the docs](https://faustdoc.grame.fr/manual/embedding/#using-libfaust-with-the-llvm-backend) for general help. 12 | 13 | set(INCLUDE_EXECUTABLE off CACHE BOOL "Include runtime executable" FORCE) 14 | set(INCLUDE_OSC off CACHE BOOL "Include Faust OSC library" FORCE) 15 | set(INCLUDE_HTTP off CACHE BOOL "Include Faust HTTPD library" FORCE) 16 | set(INCLUDE_WASM_GLUE off CACHE BOOL "Include wasm glue targets" FORCE) 17 | set(INCLUDE_EMCC off CACHE BOOL "Include emcc targets" FORCE) 18 | 19 | option(STATIC_FAUST "Build static Faust library (`off` to build dynamic library)" on) 20 | if(STATIC_FAUST) 21 | message(STATUS "Building static faustlib") 22 | set(INCLUDE_STATIC on CACHE BOOL "Include static Faust library" FORCE) 23 | else() 24 | message(STATUS "Building dynamic faustlib") 25 | set(INCLUDE_DYNAMIC on CACHE BOOL "Include dynamic Faust library" FORCE) 26 | endif() 27 | 28 | set(LLVM_BACKEND COMPILER STATIC DYNAMIC CACHE STRING "Include LLVM backend" FORCE) 29 | 30 | add_subdirectory(faust/build EXCLUDE_FROM_ALL) 31 | 32 | # Create the `faustlib` target. 33 | if(STATIC_FAUST) 34 | set(FAUST_TARGET_LIB "staticlib") 35 | else() 36 | set(FAUST_TARGET_LIB "dynamiclib") 37 | endif() 38 | target_compile_definitions(${FAUST_TARGET_LIB} PUBLIC LLVM_BUILD_UNIVERSAL=1) 39 | add_library(faustlib ALIAS ${FAUST_TARGET_LIB}) 40 | -------------------------------------------------------------------------------- /src/Core/Store/StoreHistory.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "nlohmann/json_fwd.hpp" 7 | 8 | #include "Core/Scalar.h" 9 | 10 | using json = nlohmann::json; 11 | 12 | struct PersistentStore; 13 | struct Gesture; 14 | using Gestures = std::vector; 15 | 16 | enum Direction { 17 | Forward, 18 | Reverse 19 | }; 20 | 21 | struct StoreHistory { 22 | struct Records; 23 | struct Metrics; 24 | 25 | struct ReferenceRecord { 26 | const PersistentStore &Store; // Reference to the store as it was at `GestureCommitTime`. 27 | const Gesture &Gesture; // Reference to the (compressed) gesture that caused the store change. 28 | }; 29 | 30 | StoreHistory(const PersistentStore &); 31 | ~StoreHistory(); 32 | 33 | u32 Size() const; 34 | bool Empty() const { return Size() <= 1; } // There is always an initial store in the history records. 35 | bool CanUndo() const { return Index > 0; } 36 | bool CanRedo() const { return Index < Size() - 1; } 37 | 38 | void AddGesture(PersistentStore, Gesture &&, ID component_id); 39 | void Clear(const PersistentStore &); 40 | void SetIndex(u32); 41 | 42 | const PersistentStore &CurrentStore() const; 43 | const PersistentStore &PrevStore() const; 44 | 45 | ReferenceRecord At(u32 index) const; 46 | Gestures GetGestures() const; 47 | 48 | std::map GetChangeCountById() const; // Ordered by path. 49 | u32 GetChangedPathsCount() const; 50 | 51 | u32 Index{0}; 52 | 53 | private: 54 | std::unique_ptr _Records; 55 | std::unique_ptr _Metrics; 56 | }; 57 | -------------------------------------------------------------------------------- /script/Build: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # By default, this script produces a debug build in `./build`, relative to the project root directory. 4 | # The project is built in the root directory regardless of where it's called from. 5 | # Flags: 6 | # * `-r, --release`: Build a release build instead of a debug build, in `./build-release` relative to the project root directory. 7 | # * `-t, --trace`: Build a traced debug build 8 | 9 | IsRelease=false 10 | IsTracing=false 11 | 12 | if [ "$1" = "-r" ] || [ "$1" = "--release" ]; then 13 | IsRelease=true 14 | elif [ "$1" = "-t" ] || [ "$1" = "--trace" ]; then 15 | IsTracing=true 16 | fi 17 | 18 | ScriptDir="$(cd "$(dirname "${0}")" && pwd)" 19 | ProjectRootDir="$(dirname "${ScriptDir}")" 20 | BuildDir="${ProjectRootDir}/build" 21 | if [ "$IsRelease" = true ]; then 22 | BuildDir="${BuildDir}-release" 23 | elif [ "$IsTracing" = true ]; then 24 | BuildDir="${BuildDir}-tracing" 25 | fi 26 | 27 | # Common part of cmake configure command 28 | # See readme for details on the choice to specify clang compiler. 29 | # CMakeConfig="CC=clang CXX=clang++ cmake "-DCMAKE_CXX_INCLUDE_WHAT_YOU_USE=/Users/khiner/Development/iwyu/build/bin/include-what-you-use" -B \"${BuildDir}\" -S \"${ProjectRootDir}\"" 30 | CMakeConfig="CC=clang CXX=clang++ cmake -B \"${BuildDir}\" -S \"${ProjectRootDir}\"" 31 | 32 | # Configure. 33 | if [ "$IsRelease" = true ]; then 34 | eval "${CMakeConfig} -D CMAKE_BUILD_TYPE=Release" 35 | elif [ "$IsTracing" = true ]; then 36 | eval "${CMakeConfig} -D TRACING_ENABLED=ON" 37 | else 38 | eval "${CMakeConfig}" 39 | fi 40 | 41 | # Build. 42 | CC=clang CXX=clang++ cmake --build "${BuildDir}" --target FlowGrid -- -j 8 43 | -------------------------------------------------------------------------------- /src/Core/TextEditor/LineChar.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | using u32 = unsigned int; 4 | 5 | struct LineChar { 6 | u32 L{0}, C{0}; 7 | 8 | auto operator<=>(const LineChar &o) const { 9 | if (auto cmp = L <=> o.L; cmp != 0) return cmp; 10 | return C <=> o.C; 11 | } 12 | bool operator==(const LineChar &) const = default; 13 | bool operator!=(const LineChar &) const = default; 14 | }; 15 | 16 | struct LineCharRange { 17 | // `Start` and `End` are the the first and second coordinate _set in an interaction_. 18 | // Use `Min()` and `Max()` for positional ordering. 19 | LineChar Start, End{Start}; 20 | 21 | LineCharRange() = default; 22 | LineCharRange(LineChar start, LineChar end) : Start(std::move(start)), End(std::move(end)) {} 23 | LineCharRange(LineChar lc) : Start(lc), End(lc) {} 24 | 25 | bool operator==(const LineCharRange &) const = default; 26 | bool operator!=(const LineCharRange &) const = default; 27 | auto operator<=>(const LineCharRange &o) const { return Min() <=> o.Min(); } 28 | 29 | LineChar Min() const { return std::min(Start, End); } 30 | LineChar Max() const { return std::max(Start, End); } 31 | 32 | LineCharRange To(LineChar lc, bool extend = false) const { return {extend ? Start : lc, lc}; } 33 | 34 | u32 Line() const { return End.L; } 35 | u32 CharIndex() const { return End.C; } 36 | LineChar LC() const { return End; } // Be careful if this is a multiline cursor! 37 | 38 | bool IsRange() const { return Start != End; } 39 | bool IsMultiline() const { return Start.L != End.L; } 40 | bool IsRightOf(LineChar lc) const { return End.L == lc.L && End.C > lc.C; } 41 | }; 42 | -------------------------------------------------------------------------------- /src/Core/Helper/String.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using std::string, std::string_view; 10 | using namespace std::string_literals; 11 | 12 | namespace StringHelper { 13 | constexpr string Capitalize(string_view str) { 14 | string copy{str}; 15 | if (copy.empty()) return ""; 16 | 17 | copy[0] = toupper(copy[0], std::locale()); 18 | return copy; 19 | } 20 | 21 | constexpr void Replace(string &subject, const char search, string_view replace) { 22 | size_t pos = 0; 23 | while ((pos = subject.find(search, pos)) != string::npos) { 24 | subject.replace(pos, 1, replace); 25 | pos += replace.length(); 26 | } 27 | } 28 | 29 | std::vector Split(const string_view &text, const char *delims); 30 | 31 | // Doesn't change contiguous capital ranges like "ID". 32 | // Doesn't modify the first occurrences of any words in the provided `skip_words` list. 33 | // Uppercases the first occurrences of all words in the provided `all_caps_words`. 34 | // E.g. if "FlowGrid" is in `skip_words`: 'FooBarFlowGridId' => 'Foo bar FlowGrid ID' 35 | string PascalToSentenceCase(string_view str, const std::vector &skip_words, const std::vector &all_caps_words); 36 | 37 | // Use the default `skip_word`s and `all_caps_words`. 38 | inline string PascalToSentenceCase(string_view str) { 39 | static const std::vector SkipWords{"FlowGrid", "ImGui", "ImPlot", "Faust"}; 40 | static const std::vector AllCapsWords{"Id", "Svg", "Dsp"}; 41 | return PascalToSentenceCase(str, SkipWords, AllCapsWords); 42 | } 43 | } // namespace StringHelper 44 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "imgui_internal.h" 2 | #include "implot.h" 3 | 4 | #include "Core/FileDialog/FileDialogManager.h" 5 | #include "Core/Project/Project.h" 6 | #include "Core/UI/Fonts.h" 7 | #include "Core/UI/UIContext.h" 8 | 9 | #include "FlowGrid.h" 10 | 11 | int main() { 12 | Project project{[](auto app_args) { return std::make_unique(std::move(app_args)); }}; 13 | const auto &core = project.Core; 14 | 15 | auto predraw = [&core]() { 16 | // Check if new UI settings need to be applied. 17 | auto &style = core.Style; 18 | core.ImGuiSettings.UpdateIfChanged(ImGui::GetCurrentContext()); 19 | style.ImGui.UpdateIfChanged(ImGui::GetCurrentContext()); 20 | style.ImPlot.UpdateIfChanged(ImPlot::GetCurrentContext()); 21 | 22 | Fonts::Tick(style.ImGui.FontScale, style.ImGui.FontIndex); 23 | }; 24 | 25 | auto draw = [&project]() { project.Draw(); }; 26 | const UIContext ui{std::move(predraw), std::move(draw)}; 27 | Fonts::Init(core.Style.ImGui.FontScale); 28 | FileDialogManager::Init(); 29 | 30 | // Initial rendering has state-modifying (action-producing) side effects. 31 | { 32 | // First frame creates dockspaces & windows. 33 | ui.Tick(); 34 | // After creating the windows, another frame is needed for ImGui to update its Window->DockNode relationships. 35 | ImGui::GetIO().WantSaveIniSettings = true; // Ensure project state reflects the fully-initialized ImGui state at the end of the next frame. 36 | ui.Tick(); 37 | } 38 | 39 | project.Init(); 40 | while (ui.Tick()) { 41 | project.Tick(); 42 | } 43 | 44 | FileDialogManager::Uninit(); 45 | 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/imgui"] 2 | path = lib/imgui 3 | url = git@github.com:khiner/imgui.git 4 | branch = docking 5 | [submodule "lib/SDL"] 6 | path = lib/SDL 7 | url = git@github.com:libsdl-org/SDL.git 8 | [submodule "lib/json"] 9 | path = lib/json 10 | url = git@github.com:nlohmann/json.git 11 | [submodule "lib/faust"] 12 | path = lib/faust 13 | url = git@github.com:grame-cncm/faust.git 14 | [submodule "lib/concurrentqueue"] 15 | path = lib/concurrentqueue 16 | url = git@github.com:cameron314/concurrentqueue.git 17 | [submodule "lib/ImGuiFileDialog"] 18 | path = lib/ImGuiFileDialog 19 | url = git@github.com:aiekick/ImGuiFileDialog.git 20 | branch = master 21 | [submodule "lib/tracy"] 22 | path = lib/tracy 23 | url = git@github.com:wolfpld/tracy.git 24 | [submodule "lib/implot"] 25 | path = lib/implot 26 | url = git@github.com:khiner/implot.git 27 | branch = master 28 | [submodule "lib/immer"] 29 | path = lib/immer 30 | url = git@github.com:arximboldi/immer.git 31 | [submodule "lib/miniaudio"] 32 | path = lib/miniaudio 33 | url = git@github.com:khiner/miniaudio.git 34 | branch = dev 35 | [submodule "lib/tree-sitter"] 36 | path = lib/tree-sitter 37 | url = git@github.com:tree-sitter/tree-sitter.git 38 | [submodule "lib/tree-sitter-parsers/tree-sitter-json"] 39 | path = lib/tree-sitter-grammars/tree-sitter-json 40 | url = git@github.com:tree-sitter/tree-sitter-json.git 41 | [submodule "lib/tree-sitter-parsers/tree-sitter-cpp"] 42 | path = lib/tree-sitter-grammars/tree-sitter-cpp 43 | url = git@github.com:tree-sitter/tree-sitter-cpp.git 44 | [submodule "lib/tree-sitter-grammars/tree-sitter-faust"] 45 | path = lib/tree-sitter-grammars/tree-sitter-faust 46 | url = git@github.com:khiner/tree-sitter-faust.git 47 | -------------------------------------------------------------------------------- /doc/todo.md: -------------------------------------------------------------------------------- 1 | ## Extract Faust graph & params UI to separate `FaustImGui` library repo 2 | 3 | Going to use this for [Mesh2Audio project](https://github.com/GATech-CSE-6730-Spring-2023-Project/mesh2audio). 4 | 5 | Done when both FlowGrid and Mesh2Audio both render faust graph & params UI using this new lib. 6 | 7 | ## Audio invariants & current audio graph connection matrix issues 8 | 9 | * When audio device is not running, but Faust code is valid, all visual Faust elements should be present (graph, params, code). 10 | * Whenever audio device is running and Faust code is valid, Faust should reflect the current device sample rate. 11 | * Num rows & cols of audio connection matrix always reflect the number of output/input busses respectively 12 | 13 | ### Problems with current matrix connections representation: 14 | 15 | * disabling and reenabling a node will not remember its previous connections 16 | * changing IO devices will not remember connections 17 | 18 | ### Solutions 19 | 20 | * keep list of all seen node source/dest names in preferences, ordered by node init time 21 | - instead of storing connections as a matrix, store as a map from source node index to list of enabled dest node indices 22 | - only save source nodes with at least one dest node enabled 23 | - render all enabled dest-rows/source-cols in connection matrix 24 | - when disabling a node (removing it from the graph), connection state doesn't change - only the bool enabled state 25 | - no more `SetMatrix` action. Use `struct SetConnectionEnabled { Count source_node_id; Count dest_node_id;, bool enabled; }`, where node IDs are corresponding index into preferences source/dest node name list. 26 | - during initialization, issue a `SetConnectionEnabled` for each default connection. 27 | -------------------------------------------------------------------------------- /src/Audio/Faust/FaustParamBase.cpp: -------------------------------------------------------------------------------- 1 | #include "FaustParamBase.h" 2 | 3 | #include 4 | 5 | #include "FaustParamsStyle.h" 6 | 7 | using namespace ImGui; 8 | 9 | using enum FaustParamType; 10 | using std::min, std::max; 11 | 12 | float FaustParamBase::CalcWidth(bool include_label) const { 13 | (void)include_label; // Unused. 14 | return GetContentRegionAvail().x; 15 | } 16 | 17 | float FaustParamBase::CalcHeight() const { 18 | const float frame_height = GetFrameHeight(); 19 | switch (Type) { 20 | case Type_VBargraph: 21 | case Type_VSlider: 22 | case Type_VRadioButtons: return Style.MinVerticalItemHeight * frame_height; 23 | case Type_HSlider: 24 | case Type_NumEntry: 25 | case Type_HBargraph: 26 | case Type_Button: 27 | case Type_CheckButton: 28 | case Type_HRadioButtons: 29 | case Type_Menu: return frame_height; 30 | case Type_Knob: return Style.MinKnobItemSize * frame_height + frame_height + ImGui::GetStyle().ItemSpacing.y; 31 | default: return 0; 32 | } 33 | } 34 | 35 | // Returns _additional_ height needed to accommodate a label for the param 36 | float FaustParamBase::CalcLabelHeight() const { 37 | switch (Type) { 38 | case Type_VBargraph: 39 | case Type_VSlider: 40 | case Type_VRadioButtons: 41 | case Type_Knob: 42 | case Type_HGroup: 43 | case Type_VGroup: 44 | case Type_TGroup: return GetTextLineHeightWithSpacing(); 45 | case Type_Button: 46 | case Type_HSlider: 47 | case Type_NumEntry: 48 | case Type_HBargraph: 49 | case Type_CheckButton: 50 | case Type_HRadioButtons: 51 | case Type_Menu: 52 | case Type_None: return 0; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Core/UI/JsonTree.cpp: -------------------------------------------------------------------------------- 1 | #include "JsonTree.h" 2 | 3 | #include "imgui_internal.h" 4 | #include "nlohmann/json.hpp" 5 | 6 | using std::string, std::string_view; 7 | using namespace ImGui; 8 | using json = nlohmann::json; 9 | 10 | namespace flowgrid { 11 | bool TreeNode(string_view label_view, const char *id, const char *value) { 12 | bool is_open = false; 13 | if (value == nullptr) { 14 | const auto label = string(label_view); 15 | is_open = id ? TreeNodeEx(id, ImGuiTreeNodeFlags_None, "%s", label.c_str()) : TreeNodeEx(label.c_str(), ImGuiTreeNodeFlags_None); 16 | } else if (!label_view.empty()) { 17 | Text("%s: ", string(label_view).c_str()); // Render leaf label/value as raw text. 18 | } 19 | 20 | if (value != nullptr) { 21 | SameLine(); 22 | TextUnformatted(value); 23 | } 24 | return is_open; 25 | } 26 | 27 | void JsonTree(string_view label, json &&value, const char *id) { 28 | if (value.is_null()) { 29 | TextUnformatted(label.empty() ? "(null)" : label.data()); 30 | } else if (value.is_object()) { 31 | if (label.empty() || TreeNode(label, id)) { 32 | for (auto it : value.items()) { 33 | JsonTree(it.key(), std::move(it.value())); 34 | } 35 | if (!label.empty()) TreePop(); 36 | } 37 | } else if (value.is_array()) { 38 | if (label.empty() || TreeNode(label, id)) { 39 | unsigned int i = 0; 40 | for (auto it : value) { 41 | JsonTree(std::to_string(i), std::move(it)); 42 | i++; 43 | } 44 | if (!label.empty()) TreePop(); 45 | } 46 | } else { 47 | TreeNode(label, id, std::move(value).dump().c_str()); 48 | } 49 | } 50 | } // namespace flowgrid 51 | -------------------------------------------------------------------------------- /src/Core/Project/ProjectContext.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "nlohmann/json_fwd.hpp" 6 | 7 | #include "Core/Helper/Time.h" 8 | #include "Core/ID.h" 9 | 10 | enum class ProjectFormat { 11 | State, 12 | Action 13 | }; 14 | 15 | struct Preferences; 16 | struct ProjectStyle; 17 | struct FileDialog; 18 | struct CoreActionProducer; 19 | 20 | struct Component; 21 | struct ChangeListener; 22 | 23 | /* 24 | `ProjectContext` is essentially the public slice of a `Project`. 25 | Every component under (and including) the project's root `ProjectState` has access to it. 26 | It doesn't know about any specific `Component` or `Store` (but it may be templated on them in the future). 27 | */ 28 | struct ProjectContext { 29 | const Preferences &Preferences; 30 | const FileDialog &FileDialog; 31 | const CoreActionProducer &Q; 32 | 33 | const std::function RegisterWindow; 34 | const std::function IsDock; 35 | const std::function IsWindow; 36 | const std::function IsWindowVisible; 37 | const std::function DrawMenuItem; 38 | const std::function ToggleDemoWindow; 39 | 40 | const std::function GetProjectJson; 41 | const std::function GetProjectStyle; 42 | 43 | const std::function RenderMetrics; 44 | const std::function RenderPathChangeFrequency; 45 | 46 | const std::function UpdateWidgetGesturing; 47 | const std::function(ID, std::optional relative_path)> LatestUpdateTime; 48 | const std::function IsChanged; 49 | const std::function IsDescendentChanged; 50 | 51 | const std::function RegisterChangeListener; 52 | const std::function UnregisterChangeListener; 53 | }; 54 | -------------------------------------------------------------------------------- /src/Core/UI/Fonts.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | enum FontStyle_ { 4 | FontStyle_Regular = 0, 5 | FontStyle_Bold = 1 << 1, 6 | FontStyle_Italic = 1 << 2, 7 | }; 8 | using FontStyle = int; 9 | 10 | enum class FontFamily { 11 | Main, 12 | Monospace, 13 | }; 14 | 15 | using u32 = unsigned int; 16 | 17 | struct ImFont; 18 | 19 | struct Fonts { 20 | static constexpr float AtlasScale = 2; // Rasterize to a scaled-up texture and scale down the font size globally, for sharper text. 21 | 22 | inline static ImFont *Main{nullptr}, *MainBold{nullptr}, *MainItalic{nullptr}, *MainBoldItalic{nullptr}; 23 | inline static ImFont *Monospace{nullptr}, *MonospaceBold{nullptr}, *MonospaceItalic{nullptr}, *MonospaceBoldItalic{nullptr}; 24 | 25 | static void Init(float scale); // Call after creating ImGui context. 26 | static void Tick(float scale, u32 index); // Check if new font settings need to be applied. 27 | 28 | static ImFont *Get(FontFamily family, FontStyle style = FontStyle_Regular) { 29 | if (family == FontFamily::Main) { 30 | if (style == FontStyle_Regular) return Main; 31 | if (style == FontStyle_Bold) return MainBold; 32 | if (style == FontStyle_Italic) return MainItalic; 33 | if (style == (FontStyle_Bold | FontStyle_Italic)) return MainBoldItalic; 34 | } 35 | if (family == FontFamily::Monospace) { 36 | if (style == FontStyle_Regular) return Monospace; 37 | if (style == FontStyle_Bold) return MonospaceBold; 38 | if (style == FontStyle_Italic) return MonospaceItalic; 39 | if (style == (FontStyle_Bold | FontStyle_Italic)) return MonospaceBoldItalic; 40 | } 41 | return Main; 42 | } 43 | 44 | // Returns true if the font was changed. 45 | // **Only call `PopFont` if `PushFont` returns true.** 46 | static bool Push(FontFamily, FontStyle style = FontStyle_Regular); 47 | static void Pop(); 48 | }; 49 | -------------------------------------------------------------------------------- /src/Core/Store/Patch/PatchOp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "Core/Scalar.h" 8 | 9 | enum class PatchOpType { 10 | // Primitive ops 11 | Add, 12 | Remove, 13 | Replace, 14 | // Vector ops 15 | PushBack, 16 | PopBack, 17 | Set, // Different from `Replace` since the op's path includes the index. 18 | // Set ops 19 | Insert, 20 | Erase, 21 | }; 22 | 23 | using PrimitiveVariant = std::variant; 24 | 25 | struct PatchOp { 26 | PatchOpType Op{}; 27 | std::optional Value{}; // Present for add/replace 28 | std::optional Old{}; // Present for remove/replace 29 | std::optional Index{}; // Present for vector set 30 | }; 31 | 32 | inline std::string ToString(PatchOpType type) { 33 | switch (type) { 34 | case PatchOpType::Add: return "Add"; 35 | case PatchOpType::Remove: return "Remove"; 36 | case PatchOpType::Replace: return "Replace"; 37 | case PatchOpType::Set: return "Set"; 38 | case PatchOpType::PushBack: return "PushBack"; 39 | case PatchOpType::PopBack: return "PopBack"; 40 | case PatchOpType::Insert: return "Insert"; 41 | case PatchOpType::Erase: return "Erase"; 42 | } 43 | } 44 | 45 | inline PatchOpType ToPatchOpType(const std::string &str) { 46 | if (str == ToString(PatchOpType::Add)) return PatchOpType::Add; 47 | if (str == ToString(PatchOpType::Remove)) return PatchOpType::Remove; 48 | if (str == ToString(PatchOpType::Replace)) return PatchOpType::Replace; 49 | if (str == ToString(PatchOpType::Set)) return PatchOpType::Set; 50 | if (str == ToString(PatchOpType::PushBack)) return PatchOpType::PushBack; 51 | if (str == ToString(PatchOpType::PopBack)) return PatchOpType::PopBack; 52 | if (str == ToString(PatchOpType::Insert)) return PatchOpType::Insert; 53 | return PatchOpType::Erase; 54 | } 55 | -------------------------------------------------------------------------------- /src/Audio/Graph/ma_monitor_node/ma_monitor_node.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "miniaudio.h" 4 | 5 | struct ma_monitor_node_config { 6 | ma_node_config node_config; 7 | ma_uint32 channels; 8 | ma_uint32 buffer_frames; 9 | }; 10 | 11 | ma_monitor_node_config ma_monitor_node_config_init(ma_uint32 channels, ma_uint32 buffer_frames); 12 | 13 | struct fft_data; // Forward-declare to avoid including fftw header. Include `fft_data.h` for complete definition. 14 | 15 | struct ma_monitor_node { 16 | ma_node_base base; 17 | ma_monitor_node_config config; 18 | fft_data *fft; 19 | // Buffers are guaranteed to be of size `config.buffer_frames * config.channels` if initialized successfully. 20 | // `buffer` always points to a full buffer, using the following double-buffering scheme: 21 | // * `buffer` initially points to (empty) `working_buffer_1` as `working_buffer_0` is filled up. 22 | // * Once `working_buffer_0` is filled up, `buffer` points to `working_buffer_0` and `working_buffer_1` starts to fill. 23 | // * Once `working_buffer_1` is filled up, `buffer` points to `working_buffer_1` and `working_buffer_0` starts to fill, etc. 24 | // At any point, the current working buffer has `working_buffer_cursor` frames written to it. 25 | ma_uint16 working_buffer_cursor{0}; 26 | ma_uint8 working_buffer_index{0}; // 0 or 1. 27 | float *working_buffer_0; 28 | float *working_buffer_1; 29 | float *buffer; // Pointer to a full buffer (either `working_buffer_1` or `working_buffer_2`). 30 | float *window; // The window function frames. 31 | float *windowed_buffer; // The buffer after applying the window function. 32 | }; 33 | 34 | ma_result ma_monitor_node_init(ma_node_graph *, const ma_monitor_node_config *, const ma_allocation_callbacks *, ma_monitor_node *); 35 | void ma_monitor_node_uninit(ma_monitor_node *, const ma_allocation_callbacks *); 36 | 37 | ma_result ma_monitor_apply_window_function(ma_monitor_node *, void (*window_func)(float *, unsigned)); 38 | -------------------------------------------------------------------------------- /src/Audio/Faust/FaustParams.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Primitive/UInt.h" 4 | 5 | #include "FaustParam.h" 6 | #include "FaustParamGroup.h" 7 | #include "FaustParamsContainer.h" 8 | 9 | #include 10 | 11 | class dsp; 12 | class FaustParamsUI; 13 | struct FaustParamsStyle; 14 | struct NamesAndValues; 15 | 16 | // Label, shortname, or complete path (to discriminate between possibly identical labels 17 | // at different locations in the UI hierarchy) can be used to access any created widget. 18 | // See Faust's `APIUI` for possible extensions (response curves, gyro, ...). 19 | struct FaustParams : Component, FaustParamsContainer { 20 | FaustParams(ComponentArgs &&, const FaustParamsStyle &); 21 | ~FaustParams() override; 22 | 23 | void SetDsp(dsp *); 24 | 25 | Prop(UInt, DspId); 26 | 27 | private: 28 | void Render() const override; 29 | 30 | void Add(FaustParamType type, const char *label, std::string_view short_label, Real *zone = nullptr, Real min = 0, Real max = 0, Real init = 0, Real step = 0, const char *tooltip = nullptr, NamesAndValues names_and_values = {}) override { 31 | FaustParamGroup &active_group = Groups.empty() ? RootGroup : *Groups.top(); 32 | if (zone == nullptr) { // Group 33 | AllParams.emplace_back(std::make_unique(ComponentArgs{&active_group, short_label, label}, Style, type, label)); 34 | Groups.push((FaustParamGroup *)AllParams.back().get()); 35 | } else { // Param 36 | AllParams.emplace_back(std::make_unique(ComponentArgs{&active_group, short_label, label}, Style, type, label, zone, min, max, init, step, tooltip, std::move(names_and_values))); 37 | } 38 | } 39 | 40 | void PopGroup() override { Groups.pop(); } 41 | 42 | const FaustParamsStyle &Style; 43 | std::unique_ptr Impl; 44 | 45 | FaustParamGroup RootGroup{ComponentArgs{this, "Param"}, Style}; 46 | std::stack Groups{}; 47 | dsp *Dsp{nullptr}; 48 | 49 | std::vector> AllParams{}; 50 | }; 51 | -------------------------------------------------------------------------------- /src/Core/UI/Fonts.cpp: -------------------------------------------------------------------------------- 1 | #include "Fonts.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "imgui.h" 8 | 9 | namespace fs = std::filesystem; 10 | 11 | static ImFont *AddFont(FontFamily family, const std::string_view font_file) { 12 | static const auto FontsPath = fs::path("./") / "res" / "fonts"; 13 | // These are eyeballed. 14 | static const std::unordered_map PixelsForFamily{ 15 | {FontFamily::Main, 15}, 16 | {FontFamily::Monospace, 17}, 17 | }; 18 | return ImGui::GetIO().Fonts->AddFontFromFileTTF((FontsPath / font_file).c_str(), PixelsForFamily.at(family) * Fonts::AtlasScale); 19 | } 20 | 21 | void Fonts::Init(float scale) { 22 | Main = AddFont(FontFamily::Main, "Inter-Regular.ttf"); 23 | MainBold = AddFont(FontFamily::Main, "Inter-Bold.ttf"); 24 | MainItalic = AddFont(FontFamily::Main, "Inter-Italic.ttf"); 25 | MainBoldItalic = AddFont(FontFamily::Main, "Inter-BoldItalic.ttf"); 26 | 27 | Monospace = AddFont(FontFamily::Monospace, "JetBrainsMono-Regular.ttf"); 28 | MonospaceBold = AddFont(FontFamily::Monospace, "JetBrainsMono-Bold.ttf"); 29 | MonospaceItalic = AddFont(FontFamily::Monospace, "JetBrainsMono-Italic.ttf"); 30 | MonospaceBoldItalic = AddFont(FontFamily::Monospace, "JetBrainsMono-BoldItalic.ttf"); 31 | 32 | ImGui::GetIO().FontGlobalScale = scale / AtlasScale; 33 | } 34 | 35 | void Fonts::Tick(float scale, u32 index) { 36 | static float PrevScale = 1.0; 37 | if (PrevScale != scale) { 38 | ImGui::GetIO().FontGlobalScale = scale / Fonts::AtlasScale; 39 | PrevScale = scale; 40 | } 41 | static int PrevIndex = 0; 42 | if (PrevIndex != index) { 43 | ImGui::GetIO().FontDefault = ImGui::GetIO().Fonts->Fonts[index]; 44 | PrevIndex = index; 45 | } 46 | } 47 | 48 | bool Fonts::Push(FontFamily family, FontStyle style) { 49 | auto *new_font = Get(family, style); 50 | if (ImGui::GetFont() == new_font) return false; 51 | 52 | ImGui::PushFont(new_font); 53 | return true; 54 | } 55 | void Fonts::Pop() { ImGui::PopFont(); } 56 | -------------------------------------------------------------------------------- /src/Audio/Faust/FaustParamBase.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "FaustParamType.h" 4 | 5 | #include 6 | #include 7 | 8 | struct FaustParamsStyle; 9 | 10 | struct FaustParamBase { 11 | FaustParamBase(const FaustParamsStyle &style, const FaustParamType type = Type_None, std::string_view label = "") 12 | : Style(style), Type(type), ParamId(label), Label(label == "0x00" ? "" : label) {} 13 | virtual ~FaustParamBase() = default; 14 | 15 | /** 16 | * `suggested_height == 0` means no height suggestion. 17 | * For params (as opposed to groups), the suggested height is the expected _available_ height in the group 18 | (which is relevant for aligning params relative to other params in the same group). 19 | * Items/groups are allowed to extend beyond this height to fit its contents, if necessary. 20 | * The cursor position is expected to be set appropriately below the drawn contents. 21 | */ 22 | virtual void Render(const float suggested_height, bool no_label = false) const = 0; 23 | 24 | bool IsGroup() const { 25 | return Type == Type_None || Type == Type_TGroup || Type == Type_HGroup || Type == Type_VGroup; 26 | } 27 | bool IsWidthExpandable() const { 28 | return Type == Type_HGroup || Type == Type_VGroup || Type == Type_TGroup || Type == Type_NumEntry || Type == Type_HSlider || Type == Type_HBargraph; 29 | } 30 | bool IsHeightExpandable() const { 31 | return Type == Type_VBargraph || Type == Type_VSlider || Type == Type_CheckButton; 32 | } 33 | bool IsLabelSameLine() const { 34 | return Type == Type_NumEntry || Type == Type_HSlider || Type == Type_HBargraph || Type == Type_HRadioButtons || Type == Type_Menu || Type == Type_CheckButton; 35 | } 36 | 37 | const FaustParamsStyle &Style; 38 | const FaustParamType Type; 39 | const std::string ParamId, Label; // `id` will be the same as `label` unless it's the special empty group label of '0x00', in which case `label` will be empty. 40 | 41 | virtual float CalcWidth(bool include_label) const; 42 | float CalcHeight() const; 43 | float CalcLabelHeight() const; 44 | }; 45 | -------------------------------------------------------------------------------- /src/Audio/WaveformNode.cpp: -------------------------------------------------------------------------------- 1 | #include "WaveformNode.h" 2 | 3 | #include "Audio/Graph/AudioGraph.h" 4 | 5 | #include "Graph/ma_waveform_node/ma_waveform_node.h" 6 | 7 | #include "imgui.h" 8 | 9 | struct WaveformMaNode : MaNode { 10 | WaveformMaNode(ma_node_graph *graph, u32 sample_rate, ma_waveform_type type, float frequency) { 11 | auto config = ma_waveform_node_config_init(sample_rate, type, frequency); 12 | if (ma_result result = ma_waveform_node_init(graph, &config, nullptr, &_Node); result != MA_SUCCESS) { 13 | throw std::runtime_error(std::format("Failed to initialize the waveform node: {}", int(result))); 14 | } 15 | Node = &_Node; 16 | } 17 | ~WaveformMaNode() { 18 | ma_waveform_node_uninit(&_Node, nullptr); 19 | } 20 | 21 | ma_waveform_node _Node; 22 | }; 23 | 24 | WaveformNode::WaveformNode(ComponentArgs &&args) : AudioGraphNode(std::move(args), [this] { return CreateNode(); }) { 25 | UpdateFrequency(); 26 | UpdateType(); 27 | Frequency.RegisterChangeListener(this); 28 | Type.RegisterChangeListener(this); 29 | } 30 | 31 | std::unique_ptr WaveformNode::CreateNode() const { 32 | return std::make_unique(Graph->Get(), Graph->SampleRate, ma_waveform_type(int(Type)), Frequency); 33 | } 34 | 35 | void WaveformNode::UpdateFrequency() { 36 | ma_waveform_set_frequency(&((ma_waveform_node *)Get())->waveform, Frequency); 37 | } 38 | 39 | void WaveformNode::UpdateType() { 40 | ma_waveform_set_type(&((ma_waveform_node *)Get())->waveform, ma_waveform_type(int(Type))); 41 | } 42 | 43 | void WaveformNode::OnComponentChanged() { 44 | AudioGraphNode::OnComponentChanged(); 45 | 46 | if (Frequency.IsChanged()) UpdateFrequency(); 47 | if (Type.IsChanged()) UpdateType(); 48 | } 49 | 50 | void WaveformNode::OnSampleRateChanged() { 51 | AudioGraphNode::OnSampleRateChanged(); 52 | ma_waveform_node_set_sample_rate(((ma_waveform_node *)Get()), Graph->SampleRate); 53 | } 54 | 55 | void WaveformNode::Render() const { 56 | Frequency.Draw(); 57 | Type.Draw(); 58 | ImGui::Spacing(); 59 | AudioGraphNode::Render(); 60 | } 61 | -------------------------------------------------------------------------------- /src/Core/Container/Optional.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Core/Primitive/Bool.h" 6 | 7 | /* 8 | A component that is created/destroyed dynamically. 9 | Think of it like a store-backed `std::unique_ptr`. 10 | */ 11 | template struct Optional : Component { 12 | Optional(ComponentArgs &&args) : Component(std::move(args)) { 13 | ContainerIds.insert(Id); 14 | ContainerAuxiliaryIds.insert(HasValue.Id); 15 | Refresh(); 16 | } 17 | ~Optional() { 18 | ContainerAuxiliaryIds.erase(HasValue.Id); 19 | ContainerIds.erase(Id); 20 | } 21 | 22 | operator bool() const { return bool(Value); } 23 | auto operator->() const { return Value.get(); } 24 | auto Get() const { return Value.get(); } 25 | 26 | void Refresh() override { 27 | HasValue.Refresh(); 28 | if (HasValue && !Value) { 29 | Value = std::make_unique(ComponentArgs{this, "Value"}); 30 | Value->Refresh(); 31 | } else if (!HasValue && Value) { 32 | Reset(); 33 | } 34 | } 35 | 36 | void Toggle_(TransientStore &s) { 37 | HasValue.Toggle_(s); 38 | Refresh(); 39 | } 40 | 41 | void IssueToggle() const { 42 | HasValue.IssueToggle(); 43 | } 44 | 45 | void Erase(TransientStore &s) const override { 46 | HasValue.Set(s, false); 47 | if (Value) Value->Erase(s); 48 | } 49 | 50 | void Reset() { Value.reset(); } 51 | 52 | void RenderValueTree(bool annotate, bool auto_select) const override { 53 | if (!Value || Value->Children.empty()) { 54 | if (auto_select) ScrollToChanged(); 55 | TextUnformatted(std::format("{} (empty)", Name)); 56 | return; 57 | } 58 | 59 | if (TreeNode(Name, false, nullptr, false, auto_select)) { 60 | for (const auto *value_child : Value->Children) { 61 | value_child->RenderValueTree(annotate, auto_select); 62 | } 63 | TreePop(); 64 | } 65 | } 66 | 67 | Prop(Bool, HasValue, false); 68 | 69 | private: 70 | std::unique_ptr Value; 71 | }; 72 | -------------------------------------------------------------------------------- /src/Core/Store/StoreBase.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "immer/map.hpp" 6 | #include "immer/map_transient.hpp" 7 | 8 | #include "Core/ID.h" 9 | 10 | // Utility to transform a tuple into another tuple, applying a function to each element. 11 | auto TransformTuple(auto &&in, auto &&func) noexcept { 12 | return std::apply( 13 | [&func](auto &&...elements) { 14 | return std::make_tuple(func(std::forward(elements))...); 15 | }, 16 | std::forward(in) 17 | ); 18 | } 19 | 20 | template using StoreMap = immer::map; 21 | template using TransientStoreMap = immer::map_transient; 22 | 23 | template struct TransientStoreMaps; 24 | 25 | template struct StoreMaps { 26 | using MapsT = std::tuple...>; 27 | using ValuesT = std::tuple; 28 | 29 | template const T &Get(ID id) const { return GetMap()[id]; } 30 | 31 | template decltype(auto) GetMap() const { return std::get>(Maps); } 32 | 33 | TransientStoreMaps Transient() const { 34 | return {TransformTuple(Maps, [](auto &&map) { return map.transient(); })}; 35 | } 36 | 37 | MapsT Maps; 38 | }; 39 | 40 | template struct TransientStoreMaps { 41 | using MapsT = std::tuple...>; 42 | 43 | template const T &Get(ID id) const { return GetMap()[id]; } 44 | template size_t Count(ID id) const { return GetMap().count(id); } 45 | 46 | template void Set(ID id, T value) { GetMap().set(id, std::move(value)); } 47 | template void Clear(ID id) { Set(id, T{}); } 48 | template void Erase(ID id) { GetMap().erase(id); } 49 | 50 | // Deduced-this for const/non-const overloads. 51 | template decltype(auto) GetMap(this auto &&self) { 52 | return std::get>(self.Maps); 53 | } 54 | 55 | StoreMaps Persistent() { 56 | return {TransformTuple(Maps, [](auto &&map) { return map.persistent(); })}; 57 | } 58 | 59 | MapsT Maps; 60 | }; 61 | -------------------------------------------------------------------------------- /src/Core/Store/Patch/Patch.cpp: -------------------------------------------------------------------------------- 1 | #include "Patch.h" 2 | 3 | #include 4 | 5 | PatchOps Merge(const PatchOps &a, const PatchOps &b) { 6 | static constexpr auto AddOp = PatchOpType::Add, RemoveOp = PatchOpType::Remove, ReplaceOp = PatchOpType::Replace; 7 | 8 | PatchOps merged = a; 9 | for (const auto &[id, ops] : b) { 10 | if (!merged.contains(id)) { 11 | merged[id] = ops; 12 | continue; 13 | } 14 | 15 | auto &old_ops = merged.at(id); 16 | if (old_ops.size() > 1) { 17 | for (const auto &op : ops) old_ops.push_back(op); 18 | continue; 19 | } 20 | 21 | const auto &old_op = old_ops.front(); 22 | const auto &op = ops.front(); 23 | // Strictly, two consecutive patches that both add or both remove the same key should throw an exception, 24 | // but I'm being lax here to allow for merging multiple patches by only looking at neighbors. 25 | // For example, if the first patch removes a component, and the second one adds the same component, 26 | // we can't know from only looking at the pair whether the added value was the same as it was before the remove 27 | // (in which case it should just be `Remove` during merge) or if it was different (in which case the merged action should be a `Replace`). 28 | if (old_op.Op == AddOp) { 29 | if (op.Op == RemoveOp || ((op.Op == AddOp || op.Op == ReplaceOp) && old_op.Value == op.Value)) merged.erase(id); // Cancel out 30 | else merged[id] = {{AddOp, op.Value, {}}}; 31 | } else if (old_op.Op == RemoveOp) { 32 | if (op.Op == AddOp || op.Op == ReplaceOp) { 33 | if (old_op.Value == op.Value) merged.erase(id); // Cancel out 34 | else merged[id] = {{ReplaceOp, op.Value, old_op.Old}}; 35 | } else { 36 | merged[id] = {{RemoveOp, {}, old_op.Old}}; 37 | } 38 | } else if (old_op.Op == ReplaceOp) { 39 | if (op.Op == AddOp || op.Op == ReplaceOp) merged[id] = {{ReplaceOp, op.Value, old_op.Old}}; 40 | else merged[id] = {{RemoveOp, {}, old_op.Old}}; 41 | } 42 | } 43 | 44 | return merged; 45 | } 46 | -------------------------------------------------------------------------------- /src/Core/Helper/File.cpp: -------------------------------------------------------------------------------- 1 | #include "File.h" 2 | 3 | #include 4 | #include 5 | 6 | #ifdef _WIN32 7 | #include // for SHGetFolderPathW 8 | #include 9 | #else 10 | #include 11 | #include 12 | #include 13 | #endif 14 | 15 | static std::optional GetHomeDir() { 16 | #ifdef _WIN32 17 | wchar_t path[MAX_PATH]; 18 | if (SHGetFolderPathW(nullptr, CSIDL_PROFILE, nullptr, 0, path) == S_OK) return fs::path(path); 19 | return std::nullopt; 20 | #else 21 | const char *home_dir; 22 | if ((home_dir = getenv("HOME")) == nullptr) home_dir = getpwuid(getuid())->pw_dir; 23 | if (home_dir != nullptr) return fs::path(home_dir); 24 | return std::nullopt; 25 | #endif 26 | } 27 | 28 | static fs::path ExpandPath(const fs::path &path) { 29 | if (*path.begin() != "~") return path; 30 | 31 | if (const auto home_dir = GetHomeDir()) { 32 | // Create a relative path, skipping the first element ("~"). 33 | fs::path relative_path; 34 | for (auto it = ++path.begin(); it != path.end(); ++it) relative_path /= *it; 35 | return *home_dir / relative_path; 36 | } 37 | 38 | throw std::runtime_error("Unable to find the home directory."); 39 | } 40 | 41 | std::string FileIO::read(const fs::path &path) { 42 | const fs::path full_path = ExpandPath(path); 43 | std::ifstream f(full_path, std::ios::in | std::ios::binary); 44 | const auto size = fs::file_size(full_path); 45 | std::string result(size, '\0'); 46 | f.read(result.data(), long(size)); 47 | return result; 48 | } 49 | 50 | bool FileIO::write(const fs::path &path, std::string_view contents) { 51 | std::fstream out_file; 52 | out_file.open(path, std::ios::out); 53 | if (out_file) { 54 | out_file << contents; 55 | out_file.close(); 56 | return true; 57 | } 58 | return false; 59 | } 60 | 61 | bool FileIO::write(const fs::path &path, const std::vector &contents) { 62 | if (std::fstream out_file(path, std::ios::out | std::ios::binary); out_file) { 63 | out_file.write(reinterpret_cast(contents.data()), std::streamsize(contents.size())); 64 | out_file.close(); 65 | return true; 66 | } 67 | return false; 68 | } 69 | -------------------------------------------------------------------------------- /src/Audio/Graph/ma_waveform_node/ma_waveform_node.cpp: -------------------------------------------------------------------------------- 1 | #include "ma_waveform_node.h" 2 | 3 | #include "../ma_helper.h" 4 | 5 | ma_waveform_node_config ma_waveform_node_config_init(ma_uint32 sample_rate, ma_waveform_type type, double frequency) { 6 | ma_waveform_node_config config; 7 | config.node_config = ma_node_config_init(); 8 | config.waveform_config = ma_waveform_config_init(ma_format_f32, 1, sample_rate, type, 1, frequency); 9 | 10 | return config; 11 | } 12 | 13 | ma_result ma_waveform_node_set_sample_rate(ma_waveform_node *waveform_node, ma_uint32 sample_rate) { 14 | if (waveform_node == nullptr) return MA_INVALID_ARGS; 15 | 16 | return ma_waveform_set_sample_rate(&waveform_node->waveform, sample_rate); 17 | } 18 | 19 | static void ma_waveform_node_process_pcm_frames(ma_node *node, const float **frames_in, ma_uint32 *frame_count_in, float **frames_out, ma_uint32 *frame_count_out) { 20 | ma_waveform_node *waveform_node = (ma_waveform_node *)node; 21 | ma_waveform_read_pcm_frames(&waveform_node->waveform, frames_out[0], frame_count_out[0], nullptr); 22 | 23 | (void)frame_count_in; 24 | (void)frames_in; 25 | } 26 | 27 | ma_result ma_waveform_node_init(ma_node_graph *node_graph, const ma_waveform_node_config *config, const ma_allocation_callbacks *allocation_callbacks, ma_waveform_node *waveform_node) { 28 | if (waveform_node == nullptr || config == nullptr) return MA_INVALID_ARGS; 29 | 30 | MA_ZERO_OBJECT(waveform_node); 31 | waveform_node->config = *config; 32 | if (ma_result result = ma_waveform_init(&config->waveform_config, &waveform_node->waveform); result != MA_SUCCESS) return result; 33 | 34 | static ma_node_vtable vtable = {ma_waveform_node_process_pcm_frames, nullptr, 0, 1, 0}; 35 | ma_node_config base_config = config->node_config; 36 | base_config.vtable = &vtable; 37 | static ma_uint32 in_channels = 0; 38 | base_config.pInputChannels = &in_channels; 39 | base_config.pOutputChannels = &config->waveform_config.channels; 40 | 41 | return ma_node_init(node_graph, &base_config, allocation_callbacks, &waveform_node->base); 42 | } 43 | 44 | void ma_waveform_node_uninit(ma_waveform_node *waveform_node, const ma_allocation_callbacks *allocation_callbacks) { 45 | ma_node_uninit(&waveform_node->base, allocation_callbacks); 46 | } 47 | -------------------------------------------------------------------------------- /src/Audio/Graph/ma_monitor_node/window_functions.h: -------------------------------------------------------------------------------- 1 | // Copied from https://github.com/sidneycadot/WindowFunctions with only minor modifications, 2 | // excluding the FFT and Chebyshev implementation, using configurable `real` type instead of only `real`, 3 | // and some minor cleanup & performance improvements. 4 | 5 | #pragma once 6 | 7 | typedef float real; 8 | 9 | // COSINE WINDOWS 10 | 11 | // The cosine window functions have an `sflag` parameter that specifies whether the generated window should be 12 | // 'symmetric' (sflag == true), or 'periodic' (sflag == false). 13 | // The symmetric choice returns a perfectly symmetric window. 14 | // The periodic choice works as if a window with (size + 1) elements is calculated, after which the last element is dropped. 15 | // A symmetric window is preferred in FIR filter design, while a periodic window is preferred in spectral analysis. 16 | 17 | // Generic cosine window 18 | void cosine_window(real *w, unsigned n, const real *coeff, unsigned ncoeff, bool sflag); 19 | 20 | // Specific cosine windows 21 | void rectwin(real *w, unsigned n); // order == 1 22 | void hann(real *w, unsigned n, bool sflag = false); // order == 2 23 | void hamming(real *w, unsigned n, bool sflag = false); // order == 2 24 | void blackman(real *w, unsigned n, bool sflag = false); // order == 3 25 | void blackmanharris(real *w, unsigned n, bool sflag = false); // order == 4 26 | void nuttallwin(real *w, unsigned n, bool sflag = false); // order == 4 27 | void flattopwin(real *w, unsigned n, bool sflag = false); // order == 5 28 | 29 | // Periodic defaults for cosine windows 30 | void hann_periodic(real *w, unsigned n); 31 | void hamming_periodic(real *w, unsigned n); 32 | void blackman_periodic(real *w, unsigned n); 33 | void blackmanharris_periodic(real *w, unsigned n); 34 | void nuttallwin_periodic(real *w, unsigned n); 35 | void flattopwin_periodic(real *w, unsigned n); 36 | 37 | // OTHER WINDOWS, NOT PARAMETERIZED 38 | void triang(real *w, unsigned n); 39 | void bartlett(real *w, unsigned n); 40 | void barthannwin(real *w, unsigned n); 41 | void bohmanwin(real *w, unsigned n); 42 | void parzenwin(real *w, unsigned n); 43 | 44 | // OTHER WINDOWS, PARAMETERIZED 45 | void gausswin(real *w, unsigned n, real alpha); 46 | void tukeywin(real *w, unsigned n, real r); 47 | void taylorwin(real *w, unsigned n, unsigned nbar, real sll); 48 | void kaiser(real *w, unsigned n, real beta); 49 | -------------------------------------------------------------------------------- /src/Audio/Graph/ma_gainer_node/ma_gainer_node.cpp: -------------------------------------------------------------------------------- 1 | #include "ma_gainer_node.h" 2 | 3 | #include "../ma_helper.h" 4 | 5 | ma_gainer_node_config ma_gainer_node_config_init(ma_uint32 channels, float gain, ma_uint32 smooth_time_frames) { 6 | ma_gainer_node_config config; 7 | config.node_config = ma_node_config_init(); 8 | config.gainer_config = ma_gainer_config_init(channels, smooth_time_frames); 9 | config.gain = gain; 10 | 11 | return config; 12 | } 13 | 14 | ma_result ma_gainer_node_set_gain(ma_gainer_node *gainer_node, float gain) { 15 | if (gainer_node == nullptr) return MA_INVALID_ARGS; 16 | 17 | return ma_gainer_set_gain(&gainer_node->gainer, gain); 18 | } 19 | 20 | static void ma_gainer_node_process_pcm_frames(ma_node *node, const float **frames_in, ma_uint32 *frame_count_in, float **frames_out, ma_uint32 *frame_count_out) { 21 | ma_gainer_node *gainer_node = (ma_gainer_node *)node; 22 | ma_gainer_process_pcm_frames(&gainer_node->gainer, frames_out[0], frames_in[0], *frame_count_out); 23 | 24 | (void)frame_count_in; 25 | } 26 | 27 | ma_result ma_gainer_node_init(ma_node_graph *node_graph, const ma_gainer_node_config *config, const ma_allocation_callbacks *allocation_callbacks, ma_gainer_node *gainer_node) { 28 | if (gainer_node == nullptr || config == nullptr) return MA_INVALID_ARGS; 29 | 30 | MA_ZERO_OBJECT(gainer_node); 31 | gainer_node->config = *config; 32 | 33 | ma_result result = ma_gainer_init(&config->gainer_config, allocation_callbacks, &gainer_node->gainer); 34 | if (result != MA_SUCCESS) return result; 35 | 36 | static ma_node_vtable vtable = {ma_gainer_node_process_pcm_frames, nullptr, 1, 1, 0}; 37 | ma_node_config base_config = config->node_config; 38 | base_config.vtable = &vtable; 39 | base_config.pInputChannels = &config->gainer_config.channels; 40 | base_config.pOutputChannels = &config->gainer_config.channels; 41 | 42 | result = ma_node_init(node_graph, &base_config, allocation_callbacks, gainer_node); 43 | ma_gainer_set_gain(&gainer_node->gainer, gainer_node->config.gain); 44 | return result; 45 | } 46 | 47 | void ma_gainer_node_uninit(ma_gainer_node *gainer_node, const ma_allocation_callbacks *allocation_callbacks) { 48 | if (gainer_node == nullptr) return; 49 | 50 | ma_gainer_uninit(&gainer_node->gainer, allocation_callbacks); 51 | ma_node_uninit(gainer_node, allocation_callbacks); 52 | } 53 | -------------------------------------------------------------------------------- /src/Audio/Graph/ma_data_passthrough_node/ma_data_passthrough_node.cpp: -------------------------------------------------------------------------------- 1 | #include "ma_data_passthrough_node.h" 2 | 3 | #include "../ma_helper.h" 4 | 5 | ma_data_passthrough_node_config ma_data_passthrough_node_config_init(ma_uint32 channels, ma_audio_buffer_ref *buffer_ref) { 6 | ma_data_passthrough_node_config config; 7 | 8 | MA_ZERO_OBJECT(&config); 9 | config.node_config = ma_node_config_init(); 10 | config.channels = channels; 11 | config.buffer_ref = buffer_ref; 12 | 13 | return config; 14 | } 15 | 16 | static void ma_data_passthrough_node_process_pcm_frames(ma_node *node, const float **frames_in, ma_uint32 *frame_count_in, float **frames_out, ma_uint32 *frame_count_out) { 17 | if (auto *passthrough = (ma_data_passthrough_node *)node; passthrough->buffer_ref) { 18 | ma_audio_buffer_ref_set_data(passthrough->buffer_ref, frames_in[0], *frame_count_out); 19 | } 20 | 21 | (void)frames_out; 22 | (void)frame_count_in; 23 | } 24 | 25 | ma_result ma_data_passthrough_node_init(ma_node_graph *pNodeGraph, const ma_data_passthrough_node_config *config, const ma_allocation_callbacks *allocation_callbacks, ma_data_passthrough_node *passthrough) { 26 | if (passthrough == nullptr || config == nullptr) return MA_INVALID_ARGS; 27 | if (config->buffer_ref && config->buffer_ref->channels != config->channels) return MA_INVALID_ARGS; 28 | 29 | MA_ZERO_OBJECT(passthrough); 30 | 31 | static ma_node_vtable passthrough_vtable = {ma_data_passthrough_node_process_pcm_frames, nullptr, 1, 1, MA_NODE_FLAG_PASSTHROUGH}; 32 | static ma_node_vtable silenced_vtable = {ma_data_passthrough_node_process_pcm_frames, nullptr, 1, 1, MA_NODE_FLAG_SILENT_OUTPUT}; 33 | 34 | ma_uint32 channels = config->channels; 35 | ma_node_config base_config = config->node_config; 36 | base_config.vtable = config->buffer_ref ? &silenced_vtable : &passthrough_vtable; 37 | base_config.pInputChannels = &channels; 38 | base_config.pOutputChannels = &channels; 39 | 40 | if (ma_result result = ma_node_init(pNodeGraph, &base_config, allocation_callbacks, &passthrough->base); result != MA_SUCCESS) { 41 | return result; 42 | } 43 | 44 | passthrough->buffer_ref = config->buffer_ref; 45 | 46 | return MA_SUCCESS; 47 | } 48 | 49 | void ma_data_passthrough_node_uninit(ma_data_passthrough_node *passthrough, const ma_allocation_callbacks *allocation_callbacks) { 50 | ma_node_uninit(&passthrough->base, allocation_callbacks); 51 | } 52 | -------------------------------------------------------------------------------- /src/Core/Helper/String.cpp: -------------------------------------------------------------------------------- 1 | #include "String.h" 2 | 3 | namespace StringHelper { 4 | std::vector Split(const string_view &text, const char *delims) { 5 | std::vector tokens; 6 | size_t start = text.find_first_not_of(delims), end; 7 | while ((end = text.find_first_of(delims, start)) != string::npos) { 8 | tokens.emplace_back(text.substr(start, end - start)); 9 | start = text.find_first_not_of(delims, end); 10 | } 11 | if (start != string::npos) tokens.emplace_back(text.substr(start)); 12 | return tokens; 13 | } 14 | 15 | // Only matches first occurrence (assumes at most one match per match word). 16 | std::vector> FindRangesMatching(string_view str, const std::vector &match_words) { 17 | std::vector> matching_ranges; 18 | for (const auto &match_word : match_words) { 19 | const size_t pos = str.find(match_word); 20 | if (pos != string::npos) matching_ranges.emplace_back(pos, pos + match_word.size()); 21 | } 22 | return matching_ranges; 23 | } 24 | 25 | string PascalToSentenceCase(string_view str, const std::vector &skip_words, const std::vector &all_caps_words) { 26 | const auto skip_ranges = FindRangesMatching(str, skip_words); 27 | const auto all_caps_ranges = FindRangesMatching(str, all_caps_words); 28 | 29 | // Mutable vars: 30 | auto skip_range_it = skip_ranges.begin(); 31 | auto caps_range_it = all_caps_ranges.begin(); 32 | 33 | string sentence_case; 34 | for (size_t index = 0; index < str.size(); index++) { 35 | if (skip_range_it != skip_ranges.end() && index > (*skip_range_it).second) skip_range_it++; 36 | if (caps_range_it != all_caps_ranges.end() && index > (*caps_range_it).second) caps_range_it++; 37 | 38 | const char ch = str[index]; 39 | if (index > 0 && isupper(ch) && islower(str[index - 1]) && 40 | (skip_range_it == skip_ranges.end() || index == (*skip_range_it).first || index == (*skip_range_it).second)) { 41 | sentence_case += ' '; 42 | } 43 | 44 | const bool in_skip_range = skip_range_it != skip_ranges.end() && index >= (*skip_range_it).first && index < (*skip_range_it).second; 45 | const bool in_caps_range = caps_range_it != all_caps_ranges.end() && index >= (*caps_range_it).first && index < (*caps_range_it).second; 46 | sentence_case += in_caps_range ? toupper(ch) : ((index > 0 && !in_skip_range) ? tolower(ch) : ch); 47 | } 48 | return sentence_case; 49 | } 50 | } // namespace StringHelper 51 | -------------------------------------------------------------------------------- /src/Audio/Graph/ma_channel_converter_node/ma_channel_converter_node.cpp: -------------------------------------------------------------------------------- 1 | #include "ma_channel_converter_node.h" 2 | 3 | #include "../ma_helper.h" 4 | 5 | ma_channel_converter_node_config ma_channel_converter_node_config_init(ma_uint32 in_channels, ma_uint32 out_channels) { 6 | ma_channel_converter_node_config config; 7 | config.node_config = ma_node_config_init(); 8 | config.converter_config = ma_channel_converter_config_init(ma_format_f32, in_channels, nullptr, out_channels, nullptr, ma_channel_mix_mode_default); 9 | 10 | return config; 11 | } 12 | 13 | static void ma_channel_converter_node_process_pcm_frames(ma_node *node, const float **frames_in, ma_uint32 *frame_count_in, float **frames_out, ma_uint32 *frame_count_out) { 14 | auto *converter_node = (ma_channel_converter_node *)node; 15 | ma_channel_converter_process_pcm_frames(&converter_node->converter, frames_out[0], frames_in[0], *frame_count_out); 16 | (void)frame_count_in; 17 | } 18 | 19 | ma_result ma_channel_converter_node_init(ma_node_graph *graph, const ma_channel_converter_node_config *config, const ma_allocation_callbacks *allocation_callbacks, ma_channel_converter_node *converter_node) { 20 | if (converter_node == nullptr || config == nullptr) return MA_INVALID_ARGS; 21 | 22 | MA_ZERO_OBJECT(converter_node); 23 | converter_node->config = *config; 24 | 25 | if (ma_result result = ma_channel_converter_init(&config->converter_config, allocation_callbacks, &converter_node->converter); result != MA_SUCCESS) { 26 | return result; 27 | } 28 | 29 | static const ma_node_vtable vtable = {ma_channel_converter_node_process_pcm_frames, nullptr, 1, 1, 0}; 30 | ma_node_config base_config; 31 | 32 | base_config = config->node_config; 33 | base_config.vtable = &vtable; 34 | ma_uint32 input_channels[1] = {config->converter_config.channelsIn}; 35 | ma_uint32 output_channels[1] = {config->converter_config.channelsOut}; 36 | base_config.pInputChannels = input_channels; 37 | base_config.pOutputChannels = output_channels; 38 | 39 | if (ma_result result = ma_node_init(graph, &base_config, allocation_callbacks, converter_node); result != MA_SUCCESS) { 40 | ma_channel_converter_uninit(&converter_node->converter, allocation_callbacks); 41 | return result; 42 | } 43 | 44 | return MA_SUCCESS; 45 | } 46 | 47 | void ma_channel_converter_node_uninit(ma_channel_converter_node *converter_node, const ma_allocation_callbacks *allocation_callbacks) { 48 | if (converter_node == nullptr) return; 49 | 50 | ma_channel_converter_uninit(&converter_node->converter, allocation_callbacks); 51 | ma_node_uninit(converter_node, allocation_callbacks); 52 | } 53 | -------------------------------------------------------------------------------- /src/Core/Json.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "nlohmann/json.hpp" 6 | 7 | namespace nlohmann { 8 | template 9 | struct adl_serializer> { 10 | // Convert `std::chrono::time_point`s to/from JSON. 11 | // From https://github.com/nlohmann/json/issues/2159#issuecomment-638104529 12 | static void to_json(json &j, const std::chrono::time_point &tp) { 13 | j = tp.time_since_epoch().count(); 14 | } 15 | static void from_json(const json &j, std::chrono::time_point &tp) { 16 | tp = std::chrono::time_point{Duration{j}}; 17 | } 18 | }; 19 | 20 | // This boilerplate is for handling `std::optional` values. 21 | // From https://github.com/nlohmann/json/issues/1749#issuecomment-1099890282 22 | template constexpr void optional_to_json(J &j, const char *name, const std::optional &value) { 23 | if (value) j[name] = *value; 24 | } 25 | template constexpr void optional_from_json(const J &j, const char *name, std::optional &value) { 26 | if (const auto it = j.find(name); it != j.end()) value = it->template get(); 27 | else value = std::nullopt; 28 | } 29 | 30 | template constexpr bool is_optional = false; 31 | template constexpr bool is_optional> = true; 32 | template constexpr void extended_to_json(const char *key, json &j, const T &value) { 33 | if constexpr (is_optional) optional_to_json(j, key, value); 34 | else j[key] = value; 35 | } 36 | template constexpr void extended_from_json(const char *key, const json &j, T &value) { 37 | if constexpr (is_optional) optional_from_json(j, key, value); 38 | else j.at(key).get_to(value); 39 | } 40 | 41 | #define ExtendedToJson(v1) extended_to_json(#v1, nlohmann_json_j, nlohmann_json_t.v1); 42 | #define ExtendedFromJson(v1) extended_from_json(#v1, nlohmann_json_j, nlohmann_json_t.v1); 43 | 44 | #define DeclareJson(Type) \ 45 | void to_json(json &, const Type &); \ 46 | void from_json(const json &, Type &); 47 | 48 | #define Json(Type, ...) \ 49 | inline void to_json(json &__VA_OPT__(nlohmann_json_j), const Type &__VA_OPT__(nlohmann_json_t)) { __VA_OPT__(NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(ExtendedToJson, __VA_ARGS__))) } \ 50 | inline void from_json(const json &__VA_OPT__(nlohmann_json_j), Type &__VA_OPT__(nlohmann_json_t)) { __VA_OPT__(NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(ExtendedFromJson, __VA_ARGS__))) } 51 | } // namespace nlohmann 52 | -------------------------------------------------------------------------------- /src/Audio/Device/AudioDevice.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "Audio/AudioIO.h" 8 | #include "DeviceDataFormat.h" 9 | 10 | #include "miniaudio.h" 11 | 12 | struct AudioDevice { 13 | using AudioCallback = void (*)(ma_device *, void *, const void *, u32); 14 | 15 | struct UserData { 16 | AudioDevice *FlowGridDevice; // The FlowGrid device is added to the user data for every device. 17 | const void *User; // Arbitrary user data. 18 | }; 19 | 20 | struct TargetConfig { 21 | std::optional ClientFormat; 22 | std::optional NativeFormat; 23 | std::string_view DeviceName; 24 | }; 25 | 26 | struct Config { 27 | Config(IO, TargetConfig &&target); 28 | 29 | bool operator==(const Config &other) const { 30 | return ClientFormat == other.ClientFormat && NativeFormat == other.NativeFormat && DeviceName == other.DeviceName; 31 | } 32 | 33 | DeviceDataFormat ClientFormat; 34 | DeviceDataFormat NativeFormat; 35 | std::string DeviceName; 36 | }; 37 | 38 | AudioDevice(IO, AudioCallback, TargetConfig &&target_config = {}, const void *client_user_data = nullptr); 39 | virtual ~AudioDevice(); 40 | 41 | void SetConfig(TargetConfig &&config = {}); 42 | 43 | static const std::vector PrioritizedSampleRates; 44 | static void ScanDevices(); 45 | 46 | ma_device *Get() const { return Device.get(); } 47 | const ma_device_info *GetInfo() const { return &Info; } 48 | std::string GetName() const; 49 | bool IsDefault() const; 50 | 51 | bool IsInput() const { return Type == IO_In; } 52 | bool IsOutput() const { return Type == IO_Out; } 53 | 54 | bool IsStarted() const; 55 | bool IsNativeSampleRate(u32) const; 56 | 57 | const std::vector &GetNativeFormats() const; 58 | const std::vector &GetAllInfos() const; 59 | 60 | ma_format GetNativeSampleFormat() const; 61 | u32 GetNativeChannels() const; 62 | u32 GetNativeSampleRate() const; 63 | DeviceDataFormat GetNativeFormat() const; 64 | 65 | u32 GetBufferFrames() const; 66 | 67 | const DeviceDataFormat &GetClientFormat() const { return _Config.ClientFormat; } 68 | 69 | void RenderInfo() const; 70 | 71 | void Stop(); 72 | 73 | IO Type; 74 | AudioCallback Callback; 75 | UserData _UserData; 76 | 77 | private: 78 | void Init(); 79 | void Uninit(); 80 | 81 | // The concrete computed config used to instantiate the device. 82 | // Should always mirror the MA device, with no default values except an emty name to indicate the devault device 83 | Config _Config; 84 | std::unique_ptr Device; 85 | 86 | ma_device_info Info; 87 | }; 88 | -------------------------------------------------------------------------------- /src/Core/TextEditor/TextBuffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Core/Action/ActionMenuItem.h" 6 | #include "Core/Action/Actionable.h" 7 | #include "Core/Primitive/Bool.h" 8 | #include "Core/Primitive/Enum.h" 9 | #include "Core/Primitive/Float.h" 10 | #include "Core/String.h" 11 | 12 | #include "TextBufferAction.h" 13 | #include "TextBufferData.h" 14 | #include "TextBufferPalette.h" 15 | 16 | struct TextBufferState; 17 | 18 | struct TextBuffer : Component, Actionable { 19 | using Line = TextBufferLine; 20 | using Lines = TextBufferLines; 21 | using Cursor = LineCharRange; 22 | 23 | TextBuffer(ComponentArgs &&, const fs::path &); 24 | ~TextBuffer(); 25 | 26 | void Apply(TransientStore &, const ActionType &) const override; 27 | bool CanApply(const ActionType &) const override; 28 | 29 | TextBufferData GetBuffer() const; 30 | std::string GetText() const; 31 | bool Empty() const; 32 | 33 | void Refresh() override; 34 | void Render() const override; 35 | void RenderMenu() const; 36 | void RenderDebug() const override; 37 | 38 | std::optional ProduceKeyboardAction() const; 39 | 40 | fs::path _LastOpenedFilePath; 41 | Prop(String, LastOpenedFilePath, _LastOpenedFilePath.c_str()); 42 | Prop_(DebugComponent, Debug, "Editor debug"); 43 | 44 | Prop(Bool, ReadOnly, false); 45 | Prop(Bool, Overwrite, false); 46 | Prop(Bool, AutoIndent, true); 47 | Prop(Bool, ShowWhitespaces, true); 48 | Prop(Bool, ShowLineNumbers, true); 49 | Prop(Bool, ShowStyleTransitionPoints, false); 50 | Prop(Bool, ShowChangedCaptureRanges, false); 51 | Prop(Bool, ShortTabs, true); 52 | Prop(Float, LineSpacing, 1); 53 | Prop(Enum, PaletteId, {"Dark", "Light", "Mariana", "RetroBlue"}, int(TextBufferPaletteId::Dark)); 54 | 55 | private: 56 | void Commit(TransientStore &, TextBufferData) const; 57 | 58 | std::optional Render(const TextBufferData &, bool is_focused) const; 59 | std::optional HandleMouseInputs(const TextBufferData &, ImVec2 char_advance, float text_start_x) const; 60 | 61 | // Returns the range of all edited cursor starts/ends since cursor edits were last cleared. 62 | // Used for updating the scroll range. 63 | std::optional GetEditedCursor(TextBufferCursors) const; 64 | 65 | u32 GetColor(PaletteIndex) const; 66 | void SetFilePath(const fs::path &) const; 67 | 68 | void CreateHoveredNode(u32 byte_index) const; 69 | void DestroyHoveredNode() const; 70 | 71 | std::unique_ptr State; // PIMPL for rendering state. 72 | 73 | ActionProducer::EnqueueFn Q; 74 | 75 | ActionMenuItem 76 | ShowOpenDialogMenuItem{*this, Q, Action::TextBuffer::ShowOpenDialog{Id}}, 77 | ShowSaveDialogMenuItem{*this, Q, Action::TextBuffer::ShowSaveDialog{Id}}; 78 | 79 | const Menu FileMenu{ 80 | "File", 81 | { 82 | ShowOpenDialogMenuItem, 83 | ShowSaveDialogMenuItem, 84 | } 85 | }; 86 | }; 87 | -------------------------------------------------------------------------------- /src/Audio/Faust/FaustNode.cpp: -------------------------------------------------------------------------------- 1 | #include "FaustNode.h" 2 | 3 | #include "Audio/Graph/AudioGraph.h" 4 | 5 | #include "Faust.h" 6 | 7 | #include "Audio/Graph/ma_faust_node/ma_faust_node.h" 8 | 9 | struct FaustMaNode : MaNode, Component, ChangeListener { 10 | FaustMaNode(ComponentArgs &&args, AudioGraph *graph, ID dsp_id = 0) 11 | : MaNode(), Component(std::move(args)), Graph(graph), ParentNode(static_cast(Parent)) { 12 | if (dsp_id != 0 && DspId == 0u) DspId.Set_(_S, dsp_id); 13 | Init(Graph->GetFaustDsp(DspId), Graph->SampleRate); 14 | DspId.RegisterChangeListener(this); 15 | } 16 | ~FaustMaNode() { 17 | UnregisterChangeListener(this); 18 | Uninit(); 19 | } 20 | 21 | void OnComponentChanged() override { 22 | if (DspId.IsChanged()) UpdateDsp(); 23 | } 24 | 25 | void Init(dsp *dsp, u32 sample_rate) { 26 | auto config = ma_faust_node_config_init(dsp, sample_rate, Graph->GetBufferFrames()); 27 | ma_result result = ma_faust_node_init(Graph->Get(), &config, nullptr, &_Node); 28 | if (result != MA_SUCCESS) throw std::runtime_error(std::format("Failed to initialize the Faust audio graph node: {}", int(result))); 29 | 30 | Node = &_Node; 31 | } 32 | void Uninit() { 33 | ma_faust_node_uninit(&_Node, nullptr); 34 | } 35 | 36 | void UpdateDsp() { 37 | auto *new_dsp = Graph->GetFaustDsp(DspId); 38 | auto *current_dsp = ma_faust_node_get_dsp(&_Node); 39 | if (!new_dsp && !current_dsp) return; 40 | 41 | auto new_in_channels = ma_faust_dsp_get_in_channels(new_dsp); 42 | auto new_out_channels = ma_faust_dsp_get_out_channels(new_dsp); 43 | auto current_in_channels = ma_faust_node_get_in_channels(&_Node); 44 | auto current_out_channels = ma_faust_node_get_out_channels(&_Node); 45 | if ((new_dsp && !current_dsp) || (!new_dsp && current_dsp) || current_in_channels != new_in_channels || current_out_channels != new_out_channels) { 46 | Uninit(); 47 | Init(new_dsp, ma_faust_node_get_sample_rate(&_Node)); 48 | ParentNode->NotifyConnectionsChanged(); 49 | } else { 50 | ma_faust_node_set_dsp(&_Node, new_dsp); 51 | } 52 | } 53 | 54 | void SetDsp(TransientStore &s, ID dsp_id) { 55 | DspId.Set_(s, dsp_id); 56 | UpdateDsp(); 57 | } 58 | 59 | AudioGraph *Graph; 60 | AudioGraphNode *ParentNode; // Type-casted parent, for convenience. 61 | 62 | Prop(UInt, DspId); 63 | 64 | ma_faust_node _Node; 65 | }; 66 | 67 | FaustNode::FaustNode(ComponentArgs &&args, ID dsp_id) : AudioGraphNode(std::move(args), [this, dsp_id] { return CreateNode(dsp_id); }) {} 68 | 69 | std::unique_ptr FaustNode::CreateNode(ID dsp_id) { return std::make_unique(ComponentArgs{this, "Node"}, Graph, dsp_id); } 70 | 71 | void FaustNode::OnSampleRateChanged() { 72 | AudioGraphNode::OnSampleRateChanged(); 73 | ma_faust_node_set_sample_rate((ma_faust_node *)Get(), Graph->SampleRate); 74 | } 75 | 76 | ID FaustNode::GetDspId() const { return reinterpret_cast(Node.get())->DspId; } 77 | void FaustNode::SetDsp(TransientStore &s, ID id) { reinterpret_cast(Node.get())->SetDsp(s, id); } 78 | -------------------------------------------------------------------------------- /src/Core/ImGuiSettings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Container/ComponentVector.h" 4 | #include "Container/Vector.h" 5 | 6 | struct Patch; 7 | 8 | template struct ImChunkStream; 9 | template struct ImVector; 10 | 11 | struct ImGuiContext; 12 | struct ImGuiDockNodeSettings; 13 | struct ImGuiWindowSettings; 14 | struct ImGuiTableSettings; 15 | 16 | // These Dock/Window/Table settings are `Component` duplicates of those in `imgui.cpp`. 17 | // They are stored here a structs-of-arrays (vs. arrays-of-structs) 18 | // todo Use Raw/Formatted settings in state viewers to unpack positions/sizes 19 | struct DockNodeSettings : Component { 20 | using Component::Component; 21 | 22 | void Set(TransientStore &, const ImVector &) const; 23 | void Update(ImGuiContext *) const; 24 | 25 | Prop(Vector, NodeId); 26 | Prop(Vector, ParentNodeId); 27 | Prop(Vector, ParentWindowId); 28 | Prop(Vector, SelectedTabId); 29 | Prop(Vector, SplitAxis); 30 | Prop(Vector, Depth); 31 | Prop(Vector, Flags); 32 | Prop(Vector, Pos); // Packed ImVec2ih 33 | Prop(Vector, Size); // Packed ImVec2ih 34 | Prop(Vector, SizeRef); // Packed ImVec2ih 35 | }; 36 | 37 | struct WindowSettings : Component { 38 | using Component::Component; 39 | 40 | void Set(TransientStore &, ImChunkStream &) const; 41 | void Update(ImGuiContext *) const; 42 | 43 | Prop(Vector, Id); 44 | Prop(Vector, ClassId); 45 | Prop(Vector, ViewportId); 46 | Prop(Vector, DockId); 47 | Prop(Vector, DockOrder); 48 | Prop(Vector, Pos); // Packed ImVec2ih 49 | Prop(Vector, Size); // Packed ImVec2ih 50 | Prop(Vector, ViewportPos); // Packed ImVec2ih 51 | Prop(Vector, Collapsed); 52 | }; 53 | 54 | struct TableColumnSettings : Component { 55 | using Component::Component; 56 | 57 | Prop(Vector, WidthOrWeight); 58 | Prop(Vector, UserID); 59 | Prop(Vector, Index); 60 | Prop(Vector, DisplayOrder); 61 | Prop(Vector, SortOrder); 62 | Prop(Vector, SortDirection); 63 | Prop(Vector, IsEnabled); // "Visible" in ini file 64 | Prop(Vector, IsStretch); 65 | }; 66 | 67 | struct TableSettings : Component { 68 | using Component::Component; 69 | 70 | void Set(TransientStore &, ImChunkStream &); 71 | void Update(ImGuiContext *) const; 72 | 73 | Prop(Vector<::ID>, ID); 74 | Prop(Vector, SaveFlags); 75 | Prop(Vector, RefScale); 76 | Prop(Vector, ColumnsCount); 77 | Prop(Vector, ColumnsCountMax); 78 | Prop(Vector, WantApply); 79 | Prop(ComponentVector, Columns); 80 | }; 81 | 82 | struct ImGuiSettings : Component { 83 | using Component::Component; 84 | 85 | inline static bool IsChanged{false}; 86 | 87 | // Basically `imgui_context.settings = this`. 88 | // Behaves just like `ImGui::LoadIniSettingsFromMemory`, but using the structured `...Settings` members 89 | // in this struct instead of the serialized `.ini` text format. 90 | void UpdateIfChanged(ImGuiContext *) const; 91 | // Basically `this = imgui_context.settings`. 92 | void Set(TransientStore &, ImGuiContext *); 93 | 94 | Prop(DockNodeSettings, Nodes); 95 | Prop(WindowSettings, Windows); 96 | Prop(TableSettings, Tables); 97 | }; 98 | -------------------------------------------------------------------------------- /src/Core/UI/Widgets.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Styling.h" 4 | 5 | struct ImVec2; 6 | struct NamesAndValues; 7 | 8 | enum KnobFlags_ { 9 | KnobFlags_None = 0, 10 | KnobFlags_NoTitle = 1 << 0, 11 | KnobFlags_NoInput = 1 << 1, 12 | KnobFlags_ValueTooltip = 1 << 2, 13 | KnobFlags_DragHorizontal = 1 << 3, 14 | }; 15 | using KnobFlags = int; 16 | 17 | enum KnobType_ { 18 | KnobType_Tick = 1 << 0, 19 | KnobType_Dot = 1 << 1, 20 | KnobType_Wiper = 1 << 2, 21 | KnobType_WiperOnly = 1 << 3, 22 | KnobType_WiperDot = 1 << 4, 23 | KnobType_Stepped = 1 << 5, 24 | KnobType_Space = 1 << 6, 25 | }; 26 | using KnobType = int; 27 | 28 | enum ValueBarFlags_ { 29 | // todo flag for value text to follow the value like `ImGui::ProgressBar` 30 | ValueBarFlags_None = 0, 31 | ValueBarFlags_Vertical = 1 << 0, 32 | ValueBarFlags_ReadOnly = 1 << 1, 33 | ValueBarFlags_NoTitle = 1 << 2, 34 | }; 35 | using ValueBarFlags = int; 36 | 37 | enum RadioButtonsFlags_ { 38 | RadioButtonsFlags_None = 0, 39 | RadioButtonsFlags_Vertical = 1 << 0, 40 | RadioButtonsFlags_NoTitle = 1 << 1, 41 | }; 42 | using RadioButtonsFlags = int; 43 | 44 | namespace flowgrid { 45 | bool Knob(const char *label, float *p_value, float v_min, float v_max, float speed = 0, const char *format = nullptr, HJustify h_justify = HJustify_Middle, KnobType variant = KnobType_Tick, KnobFlags flags = KnobFlags_None, int steps = 10); 46 | bool KnobInt(const char *label, int *p_value, int v_min, int v_max, float speed = 0, const char *format = nullptr, HJustify h_justify = HJustify_Middle, KnobType variant = KnobType_Tick, KnobFlags flags = KnobFlags_None, int steps = 10); 47 | 48 | // When `ReadOnly` is set, this is similar to `ImGui::ProgressBar`, but it has a horizontal/vertical switch, 49 | // and the value text doesn't follow the value position (it stays in the middle). 50 | // If `ReadOnly` is not set, this delegates to `SliderFloat`/`VSliderFloat`, but renders the value & label independently. 51 | // Horizontal labels are placed to the right of the rect. 52 | // Vertical labels are placed below the rect, respecting the passed in alignment. 53 | // `size` is the rectangle size. 54 | // Assumes the current cursor position is at the desired top-left of the rectangle. 55 | // Assumes the current item width has been set to the desired rectangle width (not including label width). 56 | bool ValueBar(const char *label, float *value, const float rect_height, const float min_value = 0, const float max_value = 1, const ValueBarFlags flags = ValueBarFlags_None, const HJustify h_justify = HJustify_Middle); 57 | 58 | // When `ReadOnly` is set, this is similar to `ImGui::ProgressBar`, but it has a horizontal/vertical switch, 59 | // and the value text doesn't follow the value position (it stays in the middle). 60 | // If `ReadOnly` is not set, this delegates to `SliderFloat`/`VSliderFloat`, but renders the value & label independently. 61 | // Horizontal labels are placed to the right of the rect. 62 | // Vertical labels are placed below the rect, respecting the passed in alignment. 63 | // `size` is the rectangle size. 64 | // Assumes the current cursor position is either the desired top-left of the rectangle (or the beginning of the label for a vertical bar with a title). 65 | // Assumes the current item width has been set to the desired rectangle width (not including label width). 66 | bool RadioButtons(const char *label, float *value, const NamesAndValues &names_and_values, const RadioButtonsFlags flags = RadioButtonsFlags_None, const Justify justify = {HJustify_Middle, VJustify_Middle}); 67 | float CalcRadioChoiceWidth(std::string_view choice_name); 68 | } // namespace flowgrid 69 | -------------------------------------------------------------------------------- /src/Audio/Graph/ma_panner_node/ma_panner_node.cpp: -------------------------------------------------------------------------------- 1 | #include "ma_panner_node.h" 2 | 3 | #include "../ma_helper.h" 4 | 5 | ma_panner_node_config ma_panner_node_config_init(ma_uint32 in_channels, ma_pan_mode mode) { 6 | ma_panner_node_config config; 7 | config.node_config = ma_node_config_init(); 8 | config.panner_config = ma_panner_config_init(ma_format_f32, 2); 9 | config.panner_config.mode = mode; 10 | config.in_channels = in_channels; 11 | return config; 12 | } 13 | 14 | ma_result ma_panner_node_set_pan(ma_panner_node *panner_node, float pan) { 15 | if (panner_node == nullptr) return MA_INVALID_ARGS; 16 | 17 | ma_panner_set_pan(&panner_node->panner, pan); 18 | return MA_SUCCESS; 19 | } 20 | 21 | ma_result ma_panner_node_set_mode(ma_panner_node *panner_node, ma_pan_mode mode) { 22 | if (panner_node == nullptr) return MA_INVALID_ARGS; 23 | 24 | ma_panner_set_mode(&panner_node->panner, mode); 25 | return MA_SUCCESS; 26 | } 27 | 28 | static void ma_panner_node_process_pcm_frames(ma_node *node, const float **frames_in, ma_uint32 *frame_count_in, float **frames_out, ma_uint32 *frame_count_out) { 29 | auto *panner_node = (ma_panner_node *)node; 30 | if (panner_node->converter) { 31 | ma_channel_converter_process_pcm_frames(panner_node->converter.get(), frames_out[0], frames_in[0], *frame_count_out); 32 | ma_panner_process_pcm_frames(&panner_node->panner, frames_out[0], frames_out[0], *frame_count_out); 33 | } else { 34 | ma_panner_process_pcm_frames(&panner_node->panner, frames_out[0], frames_in[0], *frame_count_out); 35 | } 36 | (void)frame_count_in; 37 | } 38 | 39 | ma_result ma_panner_node_init(ma_node_graph *graph, const ma_panner_node_config *config, const ma_allocation_callbacks *allocation_callbacks, ma_panner_node *panner_node) { 40 | if (panner_node == nullptr || config == nullptr) return MA_INVALID_ARGS; 41 | 42 | MA_ZERO_OBJECT(panner_node); 43 | panner_node->config = *config; 44 | if (ma_result result = ma_panner_init(&config->panner_config, &panner_node->panner); result != MA_SUCCESS) return result; 45 | 46 | if (config->in_channels != 2) { 47 | panner_node->converter = std::make_unique(); 48 | auto converter_config = ma_channel_converter_config_init(ma_format_f32, config->in_channels, nullptr, 2, nullptr, ma_channel_mix_mode_default); 49 | // There is no `ma_panner_uninit`. 50 | if (ma_result result = ma_channel_converter_init(&converter_config, allocation_callbacks, panner_node->converter.get()); result != MA_SUCCESS) { 51 | return result; 52 | } 53 | } 54 | 55 | static const ma_node_vtable vtable = {ma_panner_node_process_pcm_frames, nullptr, 1, 1, 0}; 56 | ma_node_config base_config; 57 | 58 | base_config = config->node_config; 59 | base_config.vtable = &vtable; 60 | ma_uint32 input_channels[1] = {config->in_channels}; 61 | ma_uint32 output_channels[1] = {2}; 62 | base_config.pInputChannels = input_channels; 63 | base_config.pOutputChannels = output_channels; 64 | 65 | if (ma_result result = ma_node_init(graph, &base_config, allocation_callbacks, panner_node); result != MA_SUCCESS) { 66 | if (panner_node->converter) ma_channel_converter_uninit(panner_node->converter.get(), allocation_callbacks); 67 | return result; 68 | } 69 | 70 | return MA_SUCCESS; 71 | } 72 | 73 | void ma_panner_node_uninit(ma_panner_node *panner_node, const ma_allocation_callbacks *allocation_callbacks) { 74 | if (panner_node == nullptr) return; 75 | 76 | // There is no `ma_panner_uninit`. 77 | if (panner_node->converter) ma_channel_converter_uninit(panner_node->converter.get(), allocation_callbacks); 78 | ma_node_uninit(panner_node, allocation_callbacks); 79 | } 80 | -------------------------------------------------------------------------------- /src/Core/TextEditor/TextBufferAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Action/DefineAction.h" 4 | #include "LineChar.h" 5 | 6 | Json(LineChar, L, C); 7 | Json(LineCharRange, Start, End); 8 | 9 | DefineActionType( 10 | TextBuffer, 11 | DefineComponentAction(ShowOpenDialog, Unsaved, Merge, "~Open"); 12 | DefineComponentAction(ShowSaveDialog, Unsaved, Merge, "~Save as..."); 13 | DefineComponentAction(Open, Saved, SameIdMerge, "", fs::path file_path;); 14 | DefineComponentAction(Save, Unsaved, NoMerge, "", fs::path file_path;); 15 | 16 | DefineComponentAction(SetCursor, Unsaved, Merge, "", LineChar lc; bool add;); 17 | DefineComponentAction(SetCursorRange, Unsaved, Merge, "", LineCharRange lcr; bool add;); 18 | DefineComponentAction(MoveCursorsLines, Unsaved, Merge, "", int amount; bool select;); 19 | DefineComponentAction(PageCursorsLines, Unsaved, Merge, "", bool up; bool select;); 20 | DefineComponentAction(MoveCursorsChar, Unsaved, Merge, "", bool right; bool select; bool word;); 21 | DefineComponentAction(MoveCursorsTop, Unsaved, Merge, "", bool select;); 22 | DefineComponentAction(MoveCursorsBottom, Unsaved, Merge, "", bool select;); 23 | DefineComponentAction(MoveCursorsStartLine, Unsaved, Merge, "", bool select;); 24 | DefineComponentAction(MoveCursorsEndLine, Unsaved, Merge, "", bool select;); 25 | DefineComponentAction(SelectAll, Unsaved, Merge, ""); 26 | DefineComponentAction(SelectNextOccurrence, Unsaved, Merge, ""); 27 | 28 | DefineComponentAction(SetText, Saved, SameIdMerge, "", std::string value;); 29 | 30 | DefineComponentAction(Copy, Unsaved, NoMerge, ""); 31 | DefineComponentAction(Cut, Saved, NoMerge, ""); 32 | DefineComponentAction(Paste, Saved, NoMerge, ""); 33 | DefineComponentAction(Delete, Saved, NoMerge, "", bool word;); 34 | DefineComponentAction(Backspace, Saved, NoMerge, "", bool word;); 35 | DefineComponentAction(DeleteCurrentLines, Saved, NoMerge, ""); 36 | DefineComponentAction(ChangeCurrentLinesIndentation, Saved, NoMerge, "", bool increase;); 37 | DefineComponentAction(MoveCurrentLines, Saved, NoMerge, "", bool up;); 38 | DefineComponentAction(ToggleLineComment, Saved, NoMerge, ""); 39 | DefineComponentAction(EnterChar, Saved, NoMerge, "", unsigned short value;); // Corresponds to `ImWchar` 40 | 41 | ComponentActionJson(Open, file_path); 42 | ComponentActionJson(Save, file_path); 43 | 44 | ComponentActionJson(SetCursor, lc, add); 45 | ComponentActionJson(SetCursorRange, lcr, add); 46 | ComponentActionJson(MoveCursorsLines, amount, select); 47 | ComponentActionJson(PageCursorsLines, up, select); 48 | ComponentActionJson(MoveCursorsChar, right, select, word); 49 | ComponentActionJson(MoveCursorsTop, select); 50 | ComponentActionJson(MoveCursorsBottom, select); 51 | ComponentActionJson(MoveCursorsStartLine, select); 52 | ComponentActionJson(MoveCursorsEndLine, select); 53 | ComponentActionJson(SelectAll); 54 | ComponentActionJson(SelectNextOccurrence); 55 | 56 | ComponentActionJson(SetText, value); 57 | 58 | ComponentActionJson(Copy); 59 | ComponentActionJson(Cut); 60 | ComponentActionJson(Paste); 61 | ComponentActionJson(Delete, word); 62 | ComponentActionJson(Backspace, word); 63 | ComponentActionJson(DeleteCurrentLines); 64 | ComponentActionJson(ChangeCurrentLinesIndentation, increase); 65 | ComponentActionJson(MoveCurrentLines, up); 66 | ComponentActionJson(ToggleLineComment); 67 | ComponentActionJson(EnterChar, value); 68 | 69 | using Any = ActionVariant< 70 | ShowOpenDialog, ShowSaveDialog, Save, Open, SetText, 71 | SetCursor, SetCursorRange, MoveCursorsLines, PageCursorsLines, MoveCursorsChar, MoveCursorsTop, MoveCursorsBottom, MoveCursorsStartLine, MoveCursorsEndLine, 72 | SelectAll, SelectNextOccurrence, Copy, Cut, Paste, Delete, Backspace, DeleteCurrentLines, ChangeCurrentLinesIndentation, 73 | MoveCurrentLines, ToggleLineComment, EnterChar>; 74 | ); 75 | -------------------------------------------------------------------------------- /src/Core/Log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Not used yet - placeholder for future use. 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "nlohmann/json.hpp" 12 | 13 | #include "Helper/Time.h" 14 | 15 | using json = nlohmann::json; 16 | using u32 = unsigned int; 17 | 18 | enum class LogLevel { 19 | Trace = 5, 20 | Debug = 10, 21 | Info = 20, 22 | Warning = 30, 23 | Error = 40, 24 | Critical = 50, 25 | }; 26 | 27 | constexpr std::string LogLevelToString(LogLevel level) { 28 | using enum LogLevel; 29 | switch (level) { 30 | case Trace: return "Trace"; 31 | case Debug: return "Debug"; 32 | case Info: return "Info"; 33 | case Warning: return "Warning"; 34 | case Error: return "Error"; 35 | case Critical: return "Critical"; 36 | } 37 | } 38 | 39 | struct LogContext { 40 | std::string File; 41 | u32 Line; 42 | 43 | LogContext(const std::string &file, u32 line) 44 | : File(file), Line(line) {} 45 | 46 | json ToJson() const { 47 | return { 48 | {"File", File}, 49 | {"Line", Line}, 50 | }; 51 | } 52 | }; 53 | 54 | struct MessageMoment { 55 | std::string Message; 56 | LogContext Context; 57 | TimePoint Time; 58 | 59 | MessageMoment(const std::string &message, const LogContext &context, TimePoint time) 60 | : Message(message), Context(context), Time(time) {} 61 | 62 | json ToJson() const { 63 | return { 64 | {"Message", Message}, 65 | {"Context", Context.ToJson()}, 66 | {"Time", std::format("{%Y-%m-%d %T}", Time)}, 67 | }; 68 | } 69 | }; 70 | 71 | struct Log { 72 | Log(LogLevel level) : Level(level) {} 73 | 74 | void LogMessage(LogLevel level, const std::string &message, const std::source_location &location = std::source_location::current()) { 75 | if (level >= Level) { 76 | MessagesByLevel[level].emplace_back(message, LogContext{location.file_name(), location.line()}, std::chrono::system_clock::now()); 77 | } 78 | } 79 | 80 | void LogMessage(LogLevel level, std::function callable, const std::source_location &location = std::source_location::current()) { 81 | if (level >= Level) { 82 | LogMessage(level, callable(), location); 83 | } 84 | } 85 | 86 | json ToJson() const { 87 | json json; 88 | for (const auto &[level, messages] : MessagesByLevel) { 89 | for (const auto &message : messages) { 90 | json[LogLevelToString(level)].push_back(message.ToJson()); 91 | } 92 | } 93 | return json; 94 | } 95 | 96 | LogLevel Level; 97 | 98 | private: 99 | std::map> MessagesByLevel; 100 | }; 101 | 102 | // The following macro assumes that there is a 'Log' instance named 'Logger' in scope. 103 | #define Log(level, message) Logger.LogMessage(level, message) 104 | #define LogIf(level, callable) Logger.LogMessage(level, callable) 105 | 106 | /* 107 | int main() { 108 | Log Logger(LogLevel::Info); // Logger instance is now available in this scope 109 | 110 | Log(LogLevel::Trace, "This is a trace message"); 111 | Log(LogLevel::Debug, "This is a debug message"); 112 | Log(LogLevel::Info, "This is an info message"); 113 | Log(LogLevel::Warning, "This is a warning message"); 114 | Log(LogLevel::Error, "This is an error message"); 115 | Log(LogLevel::Critical, "This is a critical message"); 116 | 117 | // Here is an example usage of LogIf: 118 | // The lambda function only runs when the current log level is Debug or higher 119 | LogIf(LogLevel::Debug, []() { return "This is an expensive debug message"; }); 120 | 121 | std::cout << Logger.ToJson().dump(4); 122 | 123 | return 0; 124 | } 125 | */ 126 | -------------------------------------------------------------------------------- /src/Core/Info/Info.cpp: -------------------------------------------------------------------------------- 1 | #include "Info.h" 2 | 3 | #include "imgui_internal.h" 4 | 5 | using namespace ImGui; 6 | 7 | // Copied from `imgui.cpp`. 8 | // static int StackToolFormatLevelInfo(ImGuiStackTool *tool, int n, bool format_for_ui, char *buf, size_t buf_size) { 9 | // auto *info = &tool->Results[n]; 10 | // auto *window = (info->Desc[0] == 0 && n == 0) ? FindWindowByID(info->ID) : NULL; 11 | // // Source: window name (because the root ID don't call GetID() and so doesn't get hooked) 12 | // if (window) return ImFormatString(buf, buf_size, format_for_ui ? "\"%s\" [window]" : "%s", window->Name); 13 | // // Source: GetID() hooks (prioritize over ItemInfo() because we frequently use patterns like: PushID(str), Button("") where they both have same id) 14 | // if (info->QuerySuccess) return ImFormatString(buf, buf_size, (format_for_ui && info->DataType == ImGuiDataType_String) ? "\"%s\"" : "%s", info->Desc); 15 | // // Only start using fallback below when all queries are done, so during queries we don't flickering ??? markers. 16 | // if (tool->StackLevel < tool->Results.Size) return (*buf = 0); 17 | // return ImFormatString(buf, buf_size, "???"); 18 | // } 19 | 20 | void Info::Render() const { 21 | if (const auto hovered_id = GetHoveredID(); !hovered_id) return; 22 | 23 | auto &g = *GetCurrentContext(); 24 | auto *tool = &g.DebugIDStackTool; 25 | tool->LastActiveFrame = ImGui::GetFrameCount(); 26 | 27 | PushTextWrapPos(0); 28 | static constexpr bool ShowId = false; 29 | static constexpr u32 NumColumns = ShowId ? 3 : 2; 30 | 31 | if (!tool->Results.empty() && BeginTable("##table", NumColumns, ImGuiTableFlags_Borders)) { 32 | const float id_width = CalcTextSize("0xDDDDDDDD").x; 33 | if (ShowId) TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, id_width); 34 | TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); 35 | TableSetupColumn("Help", ImGuiTableColumnFlags_WidthStretch); 36 | TableHeadersRow(); 37 | for (int n = 0; n < tool->Results.Size; n++) { 38 | const auto *info = &tool->Results[n]; 39 | if (ShowId) { 40 | TableNextColumn(); 41 | Text("0x%08X", info->ID); 42 | } 43 | if (const auto it = HelpInfo::ById.find(info->ID); it != HelpInfo::ById.end()) { 44 | const auto &data = it->second; 45 | TableNextColumn(); 46 | TextUnformatted(data.Name); 47 | TableNextColumn(); 48 | TextUnformatted(data.Help.empty() ? "-" : data.Help); 49 | } else { 50 | TableNextColumn(); 51 | Text("-"); 52 | TableNextColumn(); 53 | Text("-"); 54 | } 55 | } 56 | EndTable(); 57 | } 58 | PopTextWrapPos(); 59 | 60 | // Copied from `imgui.cpp`. 61 | // Keeping this around for debugging. 62 | // if (!tool->Results.empty() && BeginTable("##table", 4, ImGuiTableFlags_Borders)) { 63 | // const float id_width = CalcTextSize("0xDDDDDDDD").x; 64 | // TableSetupColumn("Seed", ImGuiTableColumnFlags_WidthFixed, id_width); 65 | // TableSetupColumn("PushID", ImGuiTableColumnFlags_WidthStretch); 66 | // TableSetupColumn("Result", ImGuiTableColumnFlags_WidthFixed, id_width); 67 | // TableHeadersRow(); 68 | // for (int n = 0; n < tool->Results.Size; n++) { 69 | // const auto *info = &tool->Results[n]; 70 | // TableNextColumn(); 71 | // Text("0x%08X", n > 0 ? tool->Results[n - 1].ID : 0); 72 | // TableNextColumn(); 73 | // StackToolFormatLevelInfo(tool, n, true, g.TempBuffer.Data, g.TempBuffer.Size); 74 | // TextUnformatted(g.TempBuffer.Data); 75 | // TableNextColumn(); 76 | // Text("0x%08X", info->ID); 77 | // if (n == tool->Results.Size - 1) TableSetBgColor(ImGuiTableBgTarget_CellBg, GetColorU32(ImGuiCol_Header)); 78 | // } 79 | // EndTable(); 80 | // } 81 | } 82 | -------------------------------------------------------------------------------- /src/Audio/Audio.cpp: -------------------------------------------------------------------------------- 1 | #include "Audio.h" 2 | 3 | #include "imgui_internal.h" 4 | 5 | Audio::Audio(ArgsT &&args) : ActionableComponent(std::move(args)) { 6 | Graph.RegisterWindow(); 7 | Graph.Connections.RegisterWindow(); 8 | Faust.FaustDsps.RegisterWindow(); 9 | Faust.Logs.RegisterWindow(); 10 | Faust.Graphs.RegisterWindow(); 11 | Faust.Paramss.RegisterWindow(); 12 | Style.RegisterWindow(); 13 | 14 | Faust.RegisterDspChangeListener(_S, &Graph); 15 | } 16 | 17 | Audio::~Audio() { 18 | Faust.UnregisterDspChangeListener(&Graph); 19 | } 20 | 21 | void Audio::Apply(TransientStore &s, const ActionType &action) const { 22 | std::visit( 23 | Match{ 24 | [this, &s](const Action::AudioGraph::Any &a) { Graph.Apply(s, a); }, 25 | [this, &s](const Action::Faust::DSP::Create &) { Faust.FaustDsps.EmplaceBack(s, FaustDspPathSegment); }, 26 | [this, &s](const Action::Faust::DSP::Delete &a) { Faust.FaustDsps.EraseId(s, a.id); }, 27 | [this, &s](const Action::Faust::Graph::Any &a) { Faust.Graphs.Apply(s, a); }, 28 | [this, &s](const Action::Faust::GraphStyle::ApplyColorPreset &a) { 29 | const auto &colors = Faust.Graphs.Style.Colors; 30 | switch (a.id) { 31 | case 0: return colors.Set(s, FaustGraphStyle::ColorsDark); 32 | case 1: return colors.Set(s, FaustGraphStyle::ColorsLight); 33 | case 2: return colors.Set(s, FaustGraphStyle::ColorsClassic); 34 | case 3: return colors.Set(s, FaustGraphStyle::ColorsFaust); 35 | } 36 | }, 37 | [this, &s](const Action::Faust::GraphStyle::ApplyLayoutPreset &a) { 38 | const auto &style = Faust.Graphs.Style; 39 | switch (a.id) { 40 | case 0: return style.LayoutFlowGrid(s); 41 | case 1: return style.LayoutFaust(s); 42 | } 43 | }, 44 | 45 | }, 46 | action 47 | ); 48 | } 49 | 50 | bool Audio::CanApply(const ActionType &action) const { 51 | return std::visit( 52 | Match{ 53 | [this](const Action::AudioGraph::Any &a) { return Graph.CanApply(a); }, 54 | [this](const Action::Faust::Graph::Any &a) { return Faust.Graphs.CanApply(a); }, 55 | [](auto &&) { return true; }, 56 | }, 57 | action 58 | ); 59 | } 60 | 61 | void Audio::Render() const { 62 | Faust.Draw(); 63 | } 64 | 65 | using namespace ImGui; 66 | 67 | void Audio::Dock(ID *node_id) const { 68 | auto flowgrid_node_id = DockBuilderSplitNode(*node_id, ImGuiDir_Left, 0.25f, nullptr, node_id); 69 | auto faust_tools_node_id = DockBuilderSplitNode(*node_id, ImGuiDir_Down, 0.5f, nullptr, node_id); 70 | auto faust_graph_node_id = DockBuilderSplitNode(faust_tools_node_id, ImGuiDir_Left, 0.5f, nullptr, &faust_tools_node_id); 71 | DockBuilderSplitNode(*node_id, ImGuiDir_Right, 0.5f, nullptr, node_id); // text editor 72 | 73 | Graph.Dock(&flowgrid_node_id); 74 | Graph.Connections.Dock(&flowgrid_node_id); 75 | Style.Dock(&flowgrid_node_id); 76 | Faust.FaustDsps.Dock(node_id); 77 | Faust.Graphs.Dock(&faust_graph_node_id); 78 | Faust.Paramss.Dock(&faust_tools_node_id); 79 | Faust.Logs.Dock(&faust_tools_node_id); 80 | } 81 | 82 | void Audio::Style::Render() const { 83 | if (BeginTabBar("")) { 84 | const auto &audio = static_cast(*Parent); 85 | if (BeginTabItem("Matrix mixer", nullptr, ImGuiTabItemFlags_NoPushId)) { 86 | audio.Graph.Style.Matrix.Draw(); 87 | EndTabItem(); 88 | } 89 | if (BeginTabItem("Faust graph", nullptr, ImGuiTabItemFlags_NoPushId)) { 90 | audio.Faust.GraphStyle.Draw(); 91 | EndTabItem(); 92 | } 93 | if (BeginTabItem("Faust params", nullptr, ImGuiTabItemFlags_NoPushId)) { 94 | audio.Faust.ParamsStyle.Draw(); 95 | EndTabItem(); 96 | } 97 | EndTabBar(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Core/Action/DefineAction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Action.h" 4 | #include "Core/Json.h" 5 | #include "Core/Scalar.h" // Not actually used in this file, but included as a convenience for action definitions. 6 | 7 | // Component actions hold the `component_id` of the component they act on. 8 | #define ComponentActionJson(ActionType, ...) Json(ActionType, component_id __VA_OPT__(, ) __VA_ARGS__); 9 | 10 | template constexpr bool always_false_v = false; 11 | 12 | #define MergeType_NoMerge(ActionType) \ 13 | inline std::variant Merge(const ActionType &) const { return false; } 14 | #define MergeType_Merge(ActionType) \ 15 | inline std::variant Merge(const ActionType &other) const { return other; } 16 | #define MergeType_CustomMerge(ActionType) std::variant Merge(const ActionType &) const; 17 | #define MergeType_SameIdMerge(ActionType) \ 18 | inline std::variant Merge(const ActionType &other) const { \ 19 | if (this->component_id == other.component_id) return other; \ 20 | return false; \ 21 | } 22 | 23 | /** 24 | * Pass `is_savable = 1` to declare the action as savable (undoable, gesture history, saved in `.fga` projects). 25 | * Merge types: 26 | - `NoMerge`: Cannot be merged with any other action. 27 | - `Merge`: Can be merged with any other action of the same type. 28 | - `CustomMerge`: Override the action type's `Merge` function with a custom implementation. 29 | */ 30 | #define DefineAction(ActionType, is_savable, merge_type, meta_str, ...) \ 31 | struct ActionType { \ 32 | inline static const Metadata _Meta{#ActionType, meta_str}; \ 33 | static constexpr bool IsSaved = is_savable; \ 34 | static void MenuItem(); \ 35 | static fs::path GetPath() { return _TypePath / _Meta.PathLeaf; } \ 36 | static const std::string &GetName() { return _Meta.Name; } \ 37 | static const std::string &GetMenuLabel() { return _Meta.MenuLabel; } \ 38 | MergeType_##merge_type(ActionType); \ 39 | __VA_ARGS__; \ 40 | }; 41 | 42 | inline static constexpr bool Saved = true, Unsaved = false; 43 | 44 | #define DefineComponentAction(ActionType, is_savable, merge_type, meta_str, ...) \ 45 | DefineAction( \ 46 | ActionType, is_savable, merge_type, meta_str, \ 47 | ID component_id; \ 48 | ID GetComponentId() const { return component_id; } __VA_ARGS__ \ 49 | ) 50 | 51 | #define DefineActionType(TypePath, ...) \ 52 | namespace Action { \ 53 | namespace TypePath { \ 54 | inline static const fs::path _TypePath{#TypePath}; \ 55 | __VA_ARGS__; \ 56 | } \ 57 | } 58 | 59 | #define DefineNestedActionType(ParentType, InnerType, ...) \ 60 | namespace Action { \ 61 | namespace ParentType { \ 62 | namespace InnerType { \ 63 | inline static const fs::path _TypePath{fs::path{#ParentType} / #InnerType}; \ 64 | __VA_ARGS__; \ 65 | } \ 66 | } \ 67 | } 68 | 69 | #define DefineTemplatedActionType(ParentType, InnerType, TemplateType, ...) \ 70 | template<> struct ParentType { \ 71 | inline static const fs::path _TypePath{fs::path{#ParentType} / #InnerType}; \ 72 | __VA_ARGS__; \ 73 | } 74 | -------------------------------------------------------------------------------- /src/Audio/Faust/FaustParamsUI.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "FaustParamType.h" 4 | #include "FaustParamsContainer.h" 5 | 6 | #include "faust/gui/MetaDataUI.h" 7 | #include "faust/gui/PathBuilder.h" 8 | #include "faust/gui/UI.h" 9 | 10 | struct FaustParamsStyle; 11 | 12 | class FaustParamsUI : public UI, public MetaDataUI, public PathBuilder { 13 | public: 14 | FaustParamsUI(FaustParamsContainer &container) : Container(container) {} 15 | 16 | void openHorizontalBox(const char *label) override { Add(Type_HGroup, label); } 17 | void openVerticalBox(const char *label) override { Add(Type_VGroup, label); } 18 | void openTabBox(const char *label) override { Add(Type_TGroup, label); } 19 | 20 | void closeBox() override { PopGroup(); } 21 | 22 | // Active widgets 23 | void addButton(const char *label, Real *zone) override { Add(Type_Button, label, zone); } 24 | void addCheckButton(const char *label, Real *zone) override { Add(Type_CheckButton, label, zone); } 25 | void addHorizontalSlider(const char *label, Real *zone, Real init, Real min, Real max, Real step) override { 26 | addSlider(label, zone, init, min, max, step, false); 27 | } 28 | void addVerticalSlider(const char *label, Real *zone, Real init, Real min, Real max, Real step) override { 29 | addSlider(label, zone, init, min, max, step, true); 30 | } 31 | void addNumEntry(const char *label, Real *zone, Real init, Real min, Real max, Real step) override { 32 | Add(Type_NumEntry, label, zone, min, max, init, step); 33 | } 34 | void addSlider(const char *label, Real *zone, Real init, Real min, Real max, Real step, bool is_vertical) { 35 | if (isKnob(zone)) Add(Type_Knob, label, zone, min, max, init, step); 36 | else if (isRadio(zone)) addRadioButtons(label, zone, init, min, max, step, fRadioDescription[zone].c_str(), is_vertical); 37 | else if (isMenu(zone)) addMenu(label, zone, init, min, max, step, fMenuDescription[zone].c_str()); 38 | else Add(is_vertical ? Type_VSlider : Type_HSlider, label, zone, min, max, init, step); 39 | } 40 | void addRadioButtons(const char *label, Real *zone, Real init, Real min, Real max, Real step, const char *text, bool is_vertical) { 41 | NamesAndValues names_and_values; 42 | parseMenuList(text, names_and_values.names, names_and_values.values); 43 | Add(is_vertical ? Type_VRadioButtons : Type_HRadioButtons, label, zone, min, max, init, step, std::move(names_and_values)); 44 | } 45 | void addMenu(const char *label, Real *zone, Real init, Real min, Real max, Real step, const char *text) { 46 | NamesAndValues names_and_values; 47 | parseMenuList(text, names_and_values.names, names_and_values.values); 48 | Add(Type_Menu, label, zone, min, max, init, step, std::move(names_and_values)); 49 | } 50 | 51 | // Passive widgets 52 | void addHorizontalBargraph(const char *label, Real *zone, Real min, Real max) override { Add(Type_HBargraph, label, zone, min, max); } 53 | void addVerticalBargraph(const char *label, Real *zone, Real min, Real max) override { Add(Type_VBargraph, label, zone, min, max); } 54 | 55 | // Soundfile 56 | void addSoundfile(const char *, const char *, Soundfile **) override {} 57 | 58 | // Metadata declaration 59 | void declare(Real *zone, const char *key, const char *value) override { MetaDataUI::declare(zone, key, value); } 60 | 61 | FaustParamsContainer &Container; // Forwarded to params. 62 | 63 | private: 64 | void Add(FaustParamType type, const char *label, Real *zone = nullptr, Real min = 0, Real max = 0, Real init = 0, Real step = 0, NamesAndValues names_and_values = {}) { 65 | if (zone == nullptr) pushLabel(label); 66 | else addFullPath(label); 67 | 68 | // Replace char list copied from `PathBuilder::buildPath`. 69 | // The difference is this is only applied to the new label (leaf segment) rather than the full path. 70 | std::string short_label = replaceCharList(label, {' ', '#', '*', ',', '?', '[', ']', '{', '}', '(', ')'}, '_'); 71 | Container.Add(type, label, short_label, zone, min, max, init, step, zone != nullptr && fTooltip.contains(zone) ? fTooltip.at(zone).c_str() : nullptr, std::move(names_and_values)); 72 | } 73 | 74 | void PopGroup() { 75 | if (popLabel()) computeShortNames(); 76 | 77 | Container.PopGroup(); 78 | } 79 | }; 80 | -------------------------------------------------------------------------------- /src/Audio/Faust/FaustParamsStyle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/Primitive/Bool.h" 4 | #include "Core/Primitive/Enum.h" 5 | #include "Core/Primitive/Flags.h" 6 | #include "Core/Primitive/Float.h" 7 | #include "Core/UI/Styling.h" 8 | 9 | using ImGuiTableFlags = int; 10 | 11 | // Subset of `ImGuiTableFlags`. 12 | // Unlike the enums above, this one is not a copy of an ImGui enum. 13 | // They can be converted between each other with `TableFlagsToImGui`. 14 | // todo 'Condensed' preset, with NoHostExtendX, NoBordersInBody, NoPadOuterX 15 | enum TableFlags_ { 16 | TableFlags_None = 0, 17 | // Features 18 | TableFlags_Resizable = 1 << 0, 19 | TableFlags_Reorderable = 1 << 1, 20 | TableFlags_Hideable = 1 << 2, 21 | TableFlags_Sortable = 1 << 3, 22 | TableFlags_ContextMenuInBody = 1 << 4, 23 | // Borders 24 | TableFlags_BordersInnerH = 1 << 5, 25 | TableFlags_BordersOuterH = 1 << 6, 26 | TableFlags_BordersInnerV = 1 << 7, 27 | TableFlags_BordersOuterV = 1 << 8, 28 | TableFlags_Borders = TableFlags_BordersInnerH | TableFlags_BordersOuterH | TableFlags_BordersInnerV | TableFlags_BordersOuterV, 29 | TableFlags_NoBordersInBody = 1 << 9, 30 | // Padding 31 | TableFlags_PadOuterX = 1 << 10, 32 | TableFlags_NoPadOuterX = 1 << 11, 33 | TableFlags_NoPadInnerX = 1 << 12, 34 | }; 35 | 36 | using TableFlags = int; 37 | 38 | ImGuiTableFlags TableFlagsToImGui(TableFlags); 39 | 40 | inline const std::vector TableFlagItems{ 41 | "Resizable?Enable resizing columns", 42 | "Reorderable?Enable reordering columns in header row", 43 | "Hideable?Enable hiding/disabling columns in context menu", 44 | "Sortable?Enable sorting", 45 | "ContextMenuInBody?Right-click on columns body/contents will display table context menu. By default it is available in headers row.", 46 | "BordersInnerH?Draw horizontal borders between rows", 47 | "BordersOuterH?Draw horizontal borders at the top and bottom", 48 | "BordersInnerV?Draw vertical borders between columns", 49 | "BordersOuterV?Draw vertical borders on the left and right sides", 50 | "NoBordersInBody?Disable vertical borders in columns Body (borders will always appear in Headers)", 51 | "PadOuterX?Default if 'BordersOuterV' is on. Enable outermost padding. Generally desirable if you have headers.", 52 | "NoPadOuterX?Default if 'BordersOuterV' is off. Disable outermost padding.", 53 | "NoPadInnerX?Disable inner padding between columns (double inner padding if 'BordersOuterV' is on, single inner padding if 'BordersOuterV' is off)", 54 | }; 55 | 56 | enum ParamsWidthSizingPolicy_ { 57 | ParamsWidthSizingPolicy_StretchToFill, // If a table contains only fixed-width params, allow columns to stretch to fill available width. 58 | ParamsWidthSizingPolicy_StretchFlexibleOnly, // If a table contains only fixed-width params, it won't stretch to fill available width. 59 | ParamsWidthSizingPolicy_Balanced, // All param types are given flexible-width, weighted by their minimum width. (Looks more balanced, but less expansion room for wide params). 60 | }; 61 | using ParamsWidthSizingPolicy = int; 62 | 63 | struct FaustParamsStyle : Component { 64 | using Component::Component; 65 | 66 | Prop(Bool, HeaderTitles, true); 67 | // In frame-height units: 68 | Prop(Float, MinHorizontalItemWidth, 4, 2, 8); 69 | Prop(Float, MaxHorizontalItemWidth, 16, 10, 24); 70 | Prop(Float, MinVerticalItemHeight, 4, 2, 8); 71 | Prop(Float, MinKnobItemSize, 3, 2, 6); 72 | 73 | Prop(Enum, AlignmentHorizontal, {"Left", "Middle", "Right"}, HJustify_Middle); 74 | Prop(Enum, AlignmentVertical, {"Top", "Middle", "Bottom"}, VJustify_Middle); 75 | Prop(Flags, TableFlags, TableFlagItems, TableFlags_Borders | TableFlags_Reorderable | TableFlags_Hideable); 76 | Prop_( 77 | Enum, WidthSizingPolicy, 78 | "?StretchFlexibleOnly: If a table contains only fixed-width paramc, it won't stretch to fill available width.\n" 79 | "StretchToFill: If a table contains only fixed-width params, allow columns to stretch to fill available width.\n" 80 | "Balanced: All param types are given flexible-width, weighted by their minimum width. (Looks more balanced, but less expansion room for wide params).", 81 | {"StretchToFill", "StretchFlexibleOnly", "Balanced"}, 82 | ParamsWidthSizingPolicy_StretchFlexibleOnly 83 | ); 84 | 85 | protected: 86 | void Render() const override; 87 | }; 88 | -------------------------------------------------------------------------------- /src/Audio/Faust/FaustGraphStyle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core/ActionProducerComponent.h" 4 | #include "Core/Container/Colors.h" 5 | #include "Core/Container/Vec2.h" 6 | #include "Core/Primitive/Bool.h" 7 | #include "Core/Primitive/Enum.h" 8 | #include "Core/Primitive/Flags.h" 9 | #include "Core/Primitive/Float.h" 10 | #include "Core/Primitive/UInt.h" 11 | #include "Core/String.h" 12 | #include "Core/UI/Styling.h" 13 | 14 | #include "FaustGraphStyleAction.h" 15 | 16 | enum FaustGraphHoverFlags_ { 17 | FaustGraphHoverFlags_None = 0, 18 | FaustGraphHoverFlags_ShowRect = 1 << 0, 19 | FaustGraphHoverFlags_ShowType = 1 << 1, 20 | FaustGraphHoverFlags_ShowChannels = 1 << 2, 21 | FaustGraphHoverFlags_ShowChildChannels = 1 << 3, 22 | }; 23 | using FaustGraphHoverFlags = int; 24 | 25 | enum FlowGridGraphCol_ { 26 | FlowGridGraphCol_Bg, // ImGuiCol_WindowBg 27 | FlowGridGraphCol_Text, // ImGuiCol_Text 28 | FlowGridGraphCol_DecorateStroke, // ImGuiCol_Border 29 | FlowGridGraphCol_GroupStroke, // ImGuiCol_Border 30 | FlowGridGraphCol_Line, // ImGuiCol_PlotLines 31 | FlowGridGraphCol_Link, // ImGuiCol_Button 32 | FlowGridGraphCol_Inverter, // ImGuiCol_Text 33 | FlowGridGraphCol_OrientationMark, // ImGuiCol_Text 34 | // Box fill colors of various types. todo design these colors for Dark/Classic/Light profiles 35 | FlowGridGraphCol_Normal, 36 | FlowGridGraphCol_Ui, 37 | FlowGridGraphCol_Slot, 38 | FlowGridGraphCol_Number, 39 | 40 | FlowGridGraphCol_COUNT 41 | }; 42 | using FlowGridGraphCol = int; 43 | 44 | struct FaustGraphStyle : ActionProducerComponent { 45 | FaustGraphStyle(ArgsT &&); 46 | 47 | void LayoutFlowGrid(TransientStore &) const; 48 | void LayoutFaust(TransientStore &) const; // Emulate Faust SVG rendering layout. 49 | 50 | Prop_( 51 | UInt, FoldComplexity, 52 | "?Number of boxes within a graph before folding into a sub-graph.\n" 53 | "Setting to zero disables folding altogether, for a fully-expanded graph.", 54 | 3, 0, 20 55 | ); 56 | Prop_(Bool, ScaleFillHeight, "?Automatically scale to fill the full height of the graph window, keeping the same aspect ratio."); 57 | Prop(Float, Scale, 1, 0.1, 5); 58 | Prop(Enum, Direction, {"Left", "Right"}, Dir_Right); 59 | Prop(Bool, RouteFrame); 60 | Prop(Bool, SequentialConnectionZigzag); // `false` uses diagonal lines instead of zigzags instead of zigzags 61 | Prop(Bool, OrientationMark); 62 | Prop(Float, OrientationMarkRadius, 1.5, 0.5, 3); 63 | 64 | Prop(Bool, DecorateRootNode); 65 | Prop(Vec2Linked, DecorateMargin, {10, 10}, 0, 20); 66 | Prop(Vec2Linked, DecoratePadding, {10, 10}, 0, 20); 67 | Prop(Float, DecorateLineWidth, 1, 1, 4); 68 | Prop(Float, DecorateCornerRadius, 0, 0, 10); 69 | 70 | Prop(Vec2Linked, GroupMargin, {8, 8}, 0, 20); 71 | Prop(Vec2Linked, GroupPadding, {8, 8}, 0, 20); 72 | Prop(Float, GroupLineWidth, 2, 1, 4); 73 | Prop(Float, GroupCornerRadius, 5, 0, 10); 74 | 75 | Prop(Vec2Linked, NodeMargin, {8, 8}, 0, 20); 76 | Prop(Vec2Linked, NodePadding, {8, 0}, 0, 20, false); // todo padding y not actually used yet, since blocks already have a min-height determined by WireGap. 77 | Prop(Vec2Linked, NodeMinSize, {48, 48}, 0, 128); 78 | 79 | Prop(Float, BoxCornerRadius, 4, 0, 10); 80 | Prop(Float, BinaryHorizontalGapRatio, 0.25, 0, 1); 81 | Prop(Float, WireThickness, 1, 0.5, 4); 82 | Prop(Float, WireGap, 16, 4, 20); 83 | Prop(Vec2, ArrowSize, {3, 2}, 1, 10); 84 | Prop(Float, InverterRadius, 3, 1, 5); 85 | 86 | Prop(Colors, Colors, FlowGridGraphCol_COUNT, GetColorName); 87 | 88 | static const char *GetColorName(FlowGridGraphCol); 89 | // `ColorsFaust` Matches Faust SVG rendering. 90 | static std::unordered_map ColorsDark, ColorsClassic, ColorsLight, ColorsFaust; 91 | 92 | private: 93 | void Render() const override; 94 | }; 95 | 96 | struct FaustGraphSettings : Component { 97 | using Component::Component; 98 | 99 | Prop_( 100 | Flags, HoverFlags, 101 | "?Hovering over a node in the graph will display the selected information", 102 | { 103 | "ShowRect?Display the hovered node's bounding rectangle", 104 | "ShowType?Display the hovered node's box type", 105 | "ShowChannels?Display the hovered node's channel points and indices", 106 | "ShowChildChannels?Display the channel points and indices for each of the hovered node's children", 107 | }, 108 | FaustGraphHoverFlags_None 109 | ); 110 | }; 111 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(FlowGrid LANGUAGES C CXX) 4 | 5 | set(CMAKE_CXX_STANDARD 23) 6 | set(CMAKE_CXX_EXTENSIONS OFF) 7 | 8 | # Manually set `export ASAN_OPTIONS=halt_on_error=0` to not stop after first sanitize issue. 9 | # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -fexperimental-library -fsanitize=address") 10 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -fexperimental-library") 11 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/opt/homebrew/opt/llvm/lib/c++ -L/opt/homebrew/opt/llvm/lib -lc++abi") 12 | 13 | set(STATIC_FAUST on CACHE BOOL "Build Static Faust library" FORCE) # Dynamic Faust library causes a bus error on macOS. 14 | set(TRACY_ENABLED off CACHE BOOL "Enable Tracy profiling" FORCE) 15 | 16 | add_subdirectory(lib) 17 | 18 | # Ignore lib warnings 19 | target_compile_options(staticlib PRIVATE -w) # Faust (can't use alias target) 20 | target_compile_options(SDL3-shared PRIVATE -w) 21 | file(GLOB_RECURSE LIB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/lib/*.c ${CMAKE_CURRENT_SOURCE_DIR}/lib/*.cpp) 22 | set_source_files_properties(${LIB_SOURCES} PROPERTIES COMPILE_FLAGS "-w") 23 | 24 | find_package(Freetype REQUIRED) 25 | find_package(Vulkan REQUIRED) 26 | find_package(PkgConfig REQUIRED) 27 | pkg_search_module(FFTW3F REQUIRED fftw3f IMPORTED_TARGET) 28 | 29 | file(GLOB_RECURSE FlowGridSourceFiles CONFIGURE_DEPENDS src/*.cpp) 30 | set(ImGuiDir lib/imgui) 31 | set(ImPlotDir lib/implot) 32 | set(ImGuiFileDialogDir lib/ImGuiFileDialog) 33 | set(MaDir lib/miniaudio) 34 | set(TreeSitterDir lib/tree-sitter) 35 | set(TreeSitterGrammarsDir lib/tree-sitter-grammars) 36 | 37 | add_executable(${PROJECT_NAME} 38 | ${ImGuiDir}/imgui_demo.cpp 39 | ${ImGuiDir}/imgui_draw.cpp 40 | ${ImGuiDir}/imgui_tables.cpp 41 | ${ImGuiDir}/imgui_widgets.cpp 42 | ${ImGuiDir}/imgui.cpp 43 | ${ImGuiDir}/backends/imgui_impl_vulkan.cpp 44 | ${ImGuiDir}/backends/imgui_impl_sdl3.cpp 45 | ${ImGuiDir}/misc/freetype/imgui_freetype.cpp 46 | ${ImPlotDir}/implot.cpp 47 | ${ImPlotDir}/implot_items.cpp 48 | ${ImPlotDir}/implot_demo.cpp 49 | ${ImGuiFileDialogDir}/ImGuiFileDialog.cpp 50 | ${MaDir}/extras/miniaudio_split/miniaudio.c 51 | ${TreeSitterDir}/lib/src/lib.c 52 | ${TreeSitterGrammarsDir}/tree-sitter-cpp/src/parser.c 53 | ${TreeSitterGrammarsDir}/tree-sitter-cpp/src/scanner.c 54 | ${TreeSitterGrammarsDir}/tree-sitter-faust/src/parser.c 55 | ${TreeSitterGrammarsDir}/tree-sitter-json/src/parser.c 56 | ${FlowGridSourceFiles} 57 | src/main.cpp 58 | ) 59 | 60 | include_directories( 61 | src 62 | ${SDL3_DIR}/include 63 | ${FREETYPE_INCLUDE_DIRS} 64 | ${Vulkan_INCLUDE_DIRS} 65 | ${ImGuiDir} 66 | ${ImGuiDir}/backends 67 | ${ImPlotDir} 68 | ${ImGuiFileDialogDir} 69 | ${MaDir}/extras/miniaudio_split 70 | SYSTEM lib/faust/architecture 71 | SYSTEM lib/immer 72 | lib/concurrentqueue 73 | ${TreeSitterDir}/lib/include 74 | ${TreeSitterDir}/lib/src 75 | ${TreeSitterGrammarsDir}/tree-sitter-cpp/src 76 | ${TreeSitterGrammarsDir}/tree-sitter-faust/src 77 | ${TreeSitterGrammarsDir}/tree-sitter-json/src 78 | PkgConfig::FFTW3F 79 | ) 80 | 81 | set(RESOURCE_DIR "${CMAKE_SOURCE_DIR}/res") 82 | set(RESOURCE_DEST "${CMAKE_BINARY_DIR}/res") 83 | 84 | # Copy resources after building the project target. 85 | add_custom_command( 86 | TARGET ${PROJECT_NAME} POST_BUILD 87 | COMMAND ${CMAKE_COMMAND} -E copy_directory 88 | ${RESOURCE_DIR} ${RESOURCE_DEST} 89 | COMMENT "Copying resources to build directory" 90 | ) 91 | 92 | option(TRACING_ENABLED "Enable Tracy profiling" off) 93 | if(TRACING_ENABLED) 94 | set(TracyDir lib/tracy) 95 | set(TRACY_ENABLE on CACHE BOOL "Enable profiling" FORCE) 96 | set(TRACY_ON_DEMAND off CACHE BOOL "On-demand profiling" FORCE) 97 | add_subdirectory(${TracyDir}) 98 | include_directories(${TracyDir}/public/tracy) 99 | target_link_libraries(${PROJECT_NAME} PRIVATE Tracy::TracyClient) 100 | target_compile_definitions(${PROJECT_NAME} PRIVATE TRACING_ENABLED) 101 | endif() 102 | 103 | target_link_libraries(${PROJECT_NAME} PRIVATE ${FREETYPE_LIBRARIES} ${Vulkan_LIBRARIES} SDL3::SDL3 nlohmann_json::nlohmann_json faustlib PkgConfig::FFTW3F) 104 | set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) 105 | target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra) 106 | 107 | add_definitions(-DIMGUI_DEFINE_MATH_OPERATORS) # ImVec2 & ImVec4 math operators 108 | add_definitions(-DIMGUI_ENABLE_FREETYPE) 109 | add_definitions(-DCUSTOM_IMGUIFILEDIALOG_CONFIG="Core/FileDialog/Config.h") 110 | add_definitions(-DMA_NO_ENGINE -DMA_NO_ENCODING -DMA_NO_DECODING) 111 | --------------------------------------------------------------------------------