diff --git a/.github/workflows/build-daw-plugin.yml b/.github/workflows/build-daw-plugin.yml new file mode 100644 index 0000000..3710057 --- /dev/null +++ b/.github/workflows/build-daw-plugin.yml @@ -0,0 +1,61 @@ +name: Build DAW Plugin +on: + push: + branches: + - main + - 'releases/**' + tags: + - 'v**' + pull_request: + workflow_dispatch: + workflow_run: + workflows: ["Update"] + types: + - completed + +defaults: + run: + shell: bash + +jobs: + build_plugin: + name: DAW Build - ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: windows-latest + - os: macos-latest + - os: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Linux Deps; pick GCC9 + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt install libasound2-dev libx11-dev libxcomposite-dev libxcursor-dev libxext-dev libxinerama-dev libxrandr-dev libxrender-dev libfreetype6-dev libglu1-mesa-dev libjack-jackd2-dev + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 12 + sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 12 + + - name: Build project + run: | + cmake -S . -B ./build -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" -DBUILD_JUCE_PLUGIN=TRUE + cmake --build ./build --config Release --target awcons-installer + + - name: Show Build Directory + run: | + ls -l ./build + + - name: Show Installer Directory + run: | + ls -l ./build/installer + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + path: build/installer + name: dawplugin-${{ matrix.os }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c1a858..b604b33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,35 +1,79 @@ cmake_minimum_required(VERSION 3.16) +cmake_policy(SET CMP0091 NEW) set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9" CACHE STRING "Minimum OS X deployment version") +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + project(Airwin2Rack VERSION 1.0 LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_CXX_VISIBILITY_PRESET hidden) +set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) -add_subdirectory(libs/sst-rackhelpers) -set(PLUGIN_NAME ${PROJECT_NAME}) -set(ADDITIONAL_PLUGIN_DISTRIBUTABLES res LICENSE.md README.md) -include(RackSDK.cmake) +option(BUILD_JUCE_PLUGIN "Build a JUCE plugin" OFF) +option(BUILD_RACK_PLUGIN "Build a VCV Rack plugin" OFF) message(STATUS "AirwinRackAdapter for Rack Build Process" ) message(STATUS "Installing into '${CMAKE_INSTALL_PREFIX}'") include(src/autogen_airwin/CMakeLists.txt) -add_compile_options(-fvisibility=hidden -fvisibility-inlines-hidden) -target_include_directories(${RACK_PLUGIN_LIB} PRIVATE src src/autogen_airwin/) -target_sources(${RACK_PLUGIN_LIB} PRIVATE - src/Airwin2Rack.cpp +if(NOT MSVC) + message(STATUS "Adding clang/gcc visibility options") + add_compile_options(-fvisibility=hidden -fvisibility-inlines-hidden) +endif() + +add_library(airwin-registry STATIC src/AirwinRegistry.cpp - src/Module.cpp src/airwin2rackbase.cpp - ${AIRWIN_SOURCES} - ) + ${AIRWIN_SOURCES}) + +target_compile_definitions(airwin-registry PRIVATE _USE_MATH_DEFINES) + +if (NOT MSVC) + # consistent even in warnings that chris is + target_compile_options(airwin-registry PRIVATE + -Wno-unused-function + -Wno-unused-value + -Wno-unused-but-set-variable + -Wno-multichar + ) +endif() +target_include_directories(airwin-registry PUBLIC src) + +if (${BUILD_RACK_PLUGIN}) + set(PLUGIN_NAME ${PROJECT_NAME}) + set(ADDITIONAL_PLUGIN_DISTRIBUTABLES res LICENSE.md README.md) + include(RackSDK.cmake) + + add_subdirectory(libs/sst-rackhelpers) + + target_include_directories(${RACK_PLUGIN_LIB} PRIVATE src src/autogen_airwin/) + target_sources(${RACK_PLUGIN_LIB} PRIVATE + src/Airwin2Rack.cpp + src/Module.cpp + ) -target_compile_options(${RACK_PLUGIN_LIB} PUBLIC -Wno-suggest-override -Wno-multichar -Wno-unused-value -Wno-unused-but-set-variable -Wno-unused-variable ) -target_link_libraries(${RACK_PLUGIN_LIB} PUBLIC sst-rackhelpers) + target_compile_options(${RACK_PLUGIN_LIB} PUBLIC -Wno-suggest-override -Wno-multichar -Wno-unused-value -Wno-unused-but-set-variable -Wno-unused-variable ) + target_link_libraries(${RACK_PLUGIN_LIB} PUBLIC sst-rackhelpers airwin-registry) -if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") - target_compile_options(${RACK_PLUGIN_LIB} PUBLIC -Wno-stringop-truncation) + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + target_compile_options(${RACK_PLUGIN_LIB} PUBLIC -Wno-stringop-truncation) + endif() endif() + +if (${BUILD_JUCE_PLUGIN}) + + file(GLOB AWCON_RESOURCES_GLOB + res/*.ttf + res/clipper.svg + res/awpdoc/*.txt + ) + include(cmake/CmakeRC.cmake) + cmrc_add_resource_library(awconsolidated_resources ${AWCON_RESOURCES_GLOB}) + + add_subdirectory(src-juce) +endif() \ No newline at end of file diff --git a/Makefile b/Makefile index a98d1e4..2568061 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ $(shell touch $(RACK_PLUGIN)) DEPS += $(cmake_rack_plugin) $(cmake_rack_plugin): CMakeLists.txt - $(CMAKE) -B $(CMAKE_BUILD) -DRACK_SDK_DIR=$(RACK_DIR) -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$(CMAKE_BUILD)/dist $(EXTRA_CMAKE) + $(CMAKE) -B $(CMAKE_BUILD) -DRACK_SDK_DIR=$(RACK_DIR) -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$(CMAKE_BUILD)/dist -DBUILD_RACK_PLUGIN=TRUE $(EXTRA_CMAKE) cmake --build $(CMAKE_BUILD) -- -j $(shell getconf _NPROCESSORS_ONLN) cmake --install $(CMAKE_BUILD) diff --git a/README.md b/README.md index 72453dd..853a6fc 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,39 @@ -# All the Airwindows +# All the Airwindows Consolidated -It's all the [airwindows](https://www.airwindows.com/) in a single -module for [VCV Rack](https://www.vcvrack.com/) coded in collaboration -with the [Surge Synth Team](https://surge-synthesizer.github.io). +It's all the [airwindows](https://www.airwindows.co) presented in three lovely flavors + +1. As a static library with a uniform registry and access pattern for you to use + as a submodule to expose the airwindows +2. As module for [VCV Rack](https://www.vcvrack.com/) coded in collaboration, and +3. As a CLAP/VST3/AU/LV2/Standaloen JUCE plugin Have fun! -## Notes on building +## The Library Version + +The target `airwin-registry` builds a static library for you containing +all of the airwindows under a uniform api. Documenting this is still a +todo, but if you link this target, it will automatically populate the +datastructures so`AirwinRegistry.h` does what you would expect, which is +give you a map to create `airwin2rackbase` operator objects. + +Have a question? Open an issue! + +## The Rack Plugin We are using @qno's excellent cmake SDK. This means the makefile works -like any other rack project. +like any other rack project. But if you pull and want to clean build, make sure to run both the `clean` and `cleandep` targets to rebuild fully. +## The JUCE plugin + +```bask +cmake -Bignore/daw-plugin -DBUILD_JUCE_PLUGIN=TRUE -DCMAKE_BUILD_TYPE=Release +cmake --build ignore/daw-plugin --target awcons-products +``` + ## Updating the airwindows sub-library To update the airwindows library @@ -33,8 +53,19 @@ RACK_DIR=(path-to-sdk) make -j install ## Licensing -This is MIT licensed, like Airwin, but the combined work with rack is of course -GPL3 as distributed here. +## A note on licensing + +Airwindows is MIT licensed and the source code here is also. But using the +GPL3 JUCE and VST3 tier results in a combined work licensed under GPL3, and +similarly the Rack plugin has a VST3 dependency + +We made this library MIT so that you can build *just* the static library +and use the code in your MIT or closed source project, though. + +Still unsure what you can use in a closed source environment? The answer +is basicaly `AirwinRegistry.h` and its dependencies, the cmake target +`airwin-registry` and the documentation in `res/awdoc`. Still stil unsure? +Open an issue and ask! The clipper airwindows graphic is freely distributed by airwindows diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake new file mode 100644 index 0000000..ad6b74a --- /dev/null +++ b/cmake/CPM.cmake @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: MIT +# +# SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors + +set(CPM_DOWNLOAD_VERSION 0.38.6) +set(CPM_HASH_SUM "11c3fa5f1ba14f15d31c2fb63dbc8628ee133d81c8d764caad9a8db9e0bacb07") + +if(CPM_SOURCE_CACHE) + set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +elseif(DEFINED ENV{CPM_SOURCE_CACHE}) + set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +else() + set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +endif() + +# Expand relative path. This is important if the provided path contains a tilde (~) +get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) + +file(DOWNLOAD + https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake + ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM} +) + +include(${CPM_DOWNLOAD_LOCATION}) diff --git a/cmake/CmakeRC.cmake b/cmake/CmakeRC.cmake new file mode 100644 index 0000000..6a5147a --- /dev/null +++ b/cmake/CmakeRC.cmake @@ -0,0 +1,644 @@ +# This block is executed when generating an intermediate resource file, not when +# running in CMake configure mode +if(_CMRC_GENERATE_MODE) + # Read in the digits + file(READ "${INPUT_FILE}" bytes HEX) + # Format each pair into a character literal. Heuristics seem to favor doing + # the conversion in groups of five for fastest conversion + string(REGEX REPLACE "(..)(..)(..)(..)(..)" "'\\\\x\\1','\\\\x\\2','\\\\x\\3','\\\\x\\4','\\\\x\\5'," chars "${bytes}") + # Since we did this in groups, we have some leftovers to clean up + string(LENGTH "${bytes}" n_bytes2) + math(EXPR n_bytes "${n_bytes2} / 2") + math(EXPR remainder "${n_bytes} % 5") # <-- '5' is the grouping count from above + set(cleanup_re "$") + set(cleanup_sub ) + while(remainder) + set(cleanup_re "(..)${cleanup_re}") + set(cleanup_sub "'\\\\x\\${remainder}',${cleanup_sub}") + math(EXPR remainder "${remainder} - 1") + endwhile() + if(NOT cleanup_re STREQUAL "$") + string(REGEX REPLACE "${cleanup_re}" "${cleanup_sub}" chars "${chars}") + endif() + string(CONFIGURE [[ + namespace { const char file_array[] = { @chars@ 0 }; } + namespace cmrc { namespace @NAMESPACE@ { namespace res_chars { + extern const char* const @SYMBOL@_begin = file_array; + extern const char* const @SYMBOL@_end = file_array + @n_bytes@; + }}} + ]] code) + file(WRITE "${OUTPUT_FILE}" "${code}") + # Exit from the script. Nothing else needs to be processed + return() +endif() + +set(_version 2.0.0) + +cmake_minimum_required(VERSION 3.12) +include(CMakeParseArguments) + +if(COMMAND cmrc_add_resource_library) + if(NOT DEFINED _CMRC_VERSION OR NOT (_version STREQUAL _CMRC_VERSION)) + message(WARNING "More than one CMakeRC version has been included in this project.") + endif() + # CMakeRC has already been included! Don't do anything + return() +endif() + +set(_CMRC_VERSION "${_version}" CACHE INTERNAL "CMakeRC version. Used for checking for conflicts") + +set(_CMRC_SCRIPT "${CMAKE_CURRENT_LIST_FILE}" CACHE INTERNAL "Path to CMakeRC script") + +function(_cmrc_normalize_path var) + set(path "${${var}}") + file(TO_CMAKE_PATH "${path}" path) + while(path MATCHES "//") + string(REPLACE "//" "/" path "${path}") + endwhile() + string(REGEX REPLACE "/+$" "" path "${path}") + set("${var}" "${path}" PARENT_SCOPE) +endfunction() + +get_filename_component(_inc_dir "${CMAKE_BINARY_DIR}/_cmrc/include" ABSOLUTE) +set(CMRC_INCLUDE_DIR "${_inc_dir}" CACHE INTERNAL "Directory for CMakeRC include files") +# Let's generate the primary include file +file(MAKE_DIRECTORY "${CMRC_INCLUDE_DIR}/cmrc") +set(hpp_content [==[ +#ifndef CMRC_CMRC_HPP_INCLUDED +#define CMRC_CMRC_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if !(defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) || defined(CMRC_NO_EXCEPTIONS)) +#define CMRC_NO_EXCEPTIONS 1 +#endif + +namespace cmrc { namespace detail { struct dummy; } } + +#define CMRC_DECLARE(libid) \ + namespace cmrc { namespace detail { \ + struct dummy; \ + static_assert(std::is_same::value, "CMRC_DECLARE() must only appear at the global namespace"); \ + } } \ + namespace cmrc { namespace libid { \ + cmrc::embedded_filesystem get_filesystem(); \ + } } static_assert(true, "") + +namespace cmrc { + +class file { + const char* _begin = nullptr; + const char* _end = nullptr; + +public: + using iterator = const char*; + using const_iterator = iterator; + iterator begin() const noexcept { return _begin; } + iterator cbegin() const noexcept { return _begin; } + iterator end() const noexcept { return _end; } + iterator cend() const noexcept { return _end; } + std::size_t size() const { return static_cast(std::distance(begin(), end())); } + + file() = default; + file(iterator beg, iterator end) noexcept : _begin(beg), _end(end) {} +}; + +class directory_entry; + +namespace detail { + +class directory; +class file_data; + +class file_or_directory { + union _data_t { + class file_data* file_data; + class directory* directory; + } _data; + bool _is_file = true; + +public: + explicit file_or_directory(file_data& f) { + _data.file_data = &f; + } + explicit file_or_directory(directory& d) { + _data.directory = &d; + _is_file = false; + } + bool is_file() const noexcept { + return _is_file; + } + bool is_directory() const noexcept { + return !is_file(); + } + const directory& as_directory() const noexcept { + assert(!is_file()); + return *_data.directory; + } + const file_data& as_file() const noexcept { + assert(is_file()); + return *_data.file_data; + } +}; + +class file_data { +public: + const char* begin_ptr; + const char* end_ptr; + file_data(const file_data&) = delete; + file_data(const char* b, const char* e) : begin_ptr(b), end_ptr(e) {} +}; + +inline std::pair split_path(const std::string& path) { + auto first_sep = path.find("/"); + if (first_sep == path.npos) { + return std::make_pair(path, ""); + } else { + return std::make_pair(path.substr(0, first_sep), path.substr(first_sep + 1)); + } +} + +struct created_subdirectory { + class directory& directory; + class file_or_directory& index_entry; +}; + +class directory { + std::list _files; + std::list _dirs; + std::map _index; + + using base_iterator = std::map::const_iterator; + +public: + + directory() = default; + directory(const directory&) = delete; + + created_subdirectory add_subdir(std::string name) & { + _dirs.emplace_back(); + auto& back = _dirs.back(); + auto& fod = _index.emplace(name, file_or_directory{back}).first->second; + return created_subdirectory{back, fod}; + } + + file_or_directory* add_file(std::string name, const char* begin, const char* end) & { + assert(_index.find(name) == _index.end()); + _files.emplace_back(begin, end); + return &_index.emplace(name, file_or_directory{_files.back()}).first->second; + } + + const file_or_directory* get(const std::string& path) const { + auto pair = split_path(path); + auto child = _index.find(pair.first); + if (child == _index.end()) { + return nullptr; + } + auto& entry = child->second; + if (pair.second.empty()) { + // We're at the end of the path + return &entry; + } + + if (entry.is_file()) { + // We can't traverse into a file. Stop. + return nullptr; + } + // Keep going down + return entry.as_directory().get(pair.second); + } + + class iterator { + base_iterator _base_iter; + base_iterator _end_iter; + public: + using value_type = directory_entry; + using difference_type = std::ptrdiff_t; + using pointer = const value_type*; + using reference = const value_type&; + using iterator_category = std::input_iterator_tag; + + iterator() = default; + explicit iterator(base_iterator iter, base_iterator end) : _base_iter(iter), _end_iter(end) {} + + iterator begin() const noexcept { + return *this; + } + + iterator end() const noexcept { + return iterator(_end_iter, _end_iter); + } + + inline value_type operator*() const noexcept; + + bool operator==(const iterator& rhs) const noexcept { + return _base_iter == rhs._base_iter; + } + + bool operator!=(const iterator& rhs) const noexcept { + return !(*this == rhs); + } + + iterator& operator++() noexcept { + ++_base_iter; + return *this; + } + + iterator operator++(int) noexcept { + auto cp = *this; + ++_base_iter; + return cp; + } + }; + + using const_iterator = iterator; + + iterator begin() const noexcept { + return iterator(_index.begin(), _index.end()); + } + + iterator end() const noexcept { + return iterator(); + } +}; + +inline std::string normalize_path(std::string path) { + while (path.find("/") == 0) { + path.erase(path.begin()); + } + while (!path.empty() && (path.rfind("/") == path.size() - 1)) { + path.pop_back(); + } + auto off = path.npos; + while ((off = path.find("//")) != path.npos) { + path.erase(path.begin() + static_cast(off)); + } + return path; +} + +using index_type = std::map; + +} // detail + +class directory_entry { + std::string _fname; + const detail::file_or_directory* _item; + +public: + directory_entry() = delete; + explicit directory_entry(std::string filename, const detail::file_or_directory& item) + : _fname(filename) + , _item(&item) + {} + + const std::string& filename() const & { + return _fname; + } + std::string filename() const && { + return std::move(_fname); + } + + bool is_file() const { + return _item->is_file(); + } + + bool is_directory() const { + return _item->is_directory(); + } +}; + +directory_entry detail::directory::iterator::operator*() const noexcept { + assert(begin() != end()); + return directory_entry(_base_iter->first, _base_iter->second); +} + +using directory_iterator = detail::directory::iterator; + +class embedded_filesystem { + // Never-null: + const cmrc::detail::index_type* _index; + const detail::file_or_directory* _get(std::string path) const { + path = detail::normalize_path(path); + auto found = _index->find(path); + if (found == _index->end()) { + return nullptr; + } else { + return found->second; + } + } + +public: + explicit embedded_filesystem(const detail::index_type& index) + : _index(&index) + {} + + file open(const std::string& path) const { + auto entry_ptr = _get(path); + if (!entry_ptr || !entry_ptr->is_file()) { +#ifdef CMRC_NO_EXCEPTIONS + fprintf(stderr, "Error no such file or directory: %s\n", path.c_str()); + abort(); +#else + throw std::system_error(make_error_code(std::errc::no_such_file_or_directory), path); +#endif + } + auto& dat = entry_ptr->as_file(); + return file{dat.begin_ptr, dat.end_ptr}; + } + + bool is_file(const std::string& path) const noexcept { + auto entry_ptr = _get(path); + return entry_ptr && entry_ptr->is_file(); + } + + bool is_directory(const std::string& path) const noexcept { + auto entry_ptr = _get(path); + return entry_ptr && entry_ptr->is_directory(); + } + + bool exists(const std::string& path) const noexcept { + return !!_get(path); + } + + directory_iterator iterate_directory(const std::string& path) const { + auto entry_ptr = _get(path); + if (!entry_ptr) { +#ifdef CMRC_NO_EXCEPTIONS + fprintf(stderr, "Error no such file or directory: %s\n", path.c_str()); + abort(); +#else + throw std::system_error(make_error_code(std::errc::no_such_file_or_directory), path); +#endif + } + if (!entry_ptr->is_directory()) { +#ifdef CMRC_NO_EXCEPTIONS + fprintf(stderr, "Error not a directory: %s\n", path.c_str()); + abort(); +#else + throw std::system_error(make_error_code(std::errc::not_a_directory), path); +#endif + } + return entry_ptr->as_directory().begin(); + } +}; + +} + +#endif // CMRC_CMRC_HPP_INCLUDED +]==]) + +set(cmrc_hpp "${CMRC_INCLUDE_DIR}/cmrc/cmrc.hpp" CACHE INTERNAL "") +set(_generate 1) +if(EXISTS "${cmrc_hpp}") + file(READ "${cmrc_hpp}" _current) + if(_current STREQUAL hpp_content) + set(_generate 0) + endif() +endif() +file(GENERATE OUTPUT "${cmrc_hpp}" CONTENT "${hpp_content}" CONDITION ${_generate}) + +add_library(cmrc-base INTERFACE) +target_include_directories(cmrc-base INTERFACE $) +# Signal a basic C++11 feature to require C++11. +target_compile_features(cmrc-base INTERFACE cxx_nullptr) +set_property(TARGET cmrc-base PROPERTY INTERFACE_CXX_EXTENSIONS OFF) +add_library(cmrc::base ALIAS cmrc-base) + +function(cmrc_add_resource_library name) + set(args ALIAS NAMESPACE TYPE) + cmake_parse_arguments(ARG "" "${args}" "" "${ARGN}") + # Generate the identifier for the resource library's namespace + set(ns_re "[a-zA-Z_][a-zA-Z0-9_]*") + if(NOT DEFINED ARG_NAMESPACE) + # Check that the library name is also a valid namespace + if(NOT name MATCHES "${ns_re}") + message(SEND_ERROR "Library name is not a valid namespace. Specify the NAMESPACE argument") + endif() + set(ARG_NAMESPACE "${name}") + else() + if(NOT ARG_NAMESPACE MATCHES "${ns_re}") + message(SEND_ERROR "NAMESPACE for ${name} is not a valid C++ namespace identifier (${ARG_NAMESPACE})") + endif() + endif() + set(libname "${name}") + # Check that type is either "STATIC" or "OBJECT", or default to "STATIC" if + # not set + if(NOT DEFINED ARG_TYPE) + set(ARG_TYPE STATIC) + elseif(NOT "${ARG_TYPE}" MATCHES "^(STATIC|OBJECT)$") + message(SEND_ERROR "${ARG_TYPE} is not a valid TYPE (STATIC and OBJECT are acceptable)") + set(ARG_TYPE STATIC) + endif() + # Generate a library with the compiled in character arrays. + string(CONFIGURE [=[ + #include + #include + #include + + namespace cmrc { + namespace @ARG_NAMESPACE@ { + + namespace res_chars { + // These are the files which are available in this resource library + $, + > + } + + namespace { + + const cmrc::detail::index_type& + get_root_index() { + static cmrc::detail::directory root_directory_; + static cmrc::detail::file_or_directory root_directory_fod{root_directory_}; + static cmrc::detail::index_type root_index; + root_index.emplace("", &root_directory_fod); + struct dir_inl { + class cmrc::detail::directory& directory; + }; + dir_inl root_directory_dir{root_directory_}; + (void)root_directory_dir; + $, + > + $, + > + return root_index; + } + + } + + cmrc::embedded_filesystem get_filesystem() { + static auto& index = get_root_index(); + return cmrc::embedded_filesystem{index}; + } + + } // @ARG_NAMESPACE@ + } // cmrc + ]=] cpp_content @ONLY) + get_filename_component(libdir "${CMAKE_CURRENT_BINARY_DIR}/__cmrc_${name}" ABSOLUTE) + get_filename_component(lib_tmp_cpp "${libdir}/lib_.cpp" ABSOLUTE) + string(REPLACE "\n " "\n" cpp_content "${cpp_content}") + file(GENERATE OUTPUT "${lib_tmp_cpp}" CONTENT "${cpp_content}") + get_filename_component(libcpp "${libdir}/lib.cpp" ABSOLUTE) + add_custom_command(OUTPUT "${libcpp}" + DEPENDS "${lib_tmp_cpp}" "${cmrc_hpp}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${lib_tmp_cpp}" "${libcpp}" + COMMENT "Generating ${name} resource loader" + ) + # Generate the actual static library. Each source file is just a single file + # with a character array compiled in containing the contents of the + # corresponding resource file. + add_library(${name} ${ARG_TYPE} ${libcpp}) + set_property(TARGET ${name} PROPERTY CMRC_LIBDIR "${libdir}") + set_property(TARGET ${name} PROPERTY CMRC_NAMESPACE "${ARG_NAMESPACE}") + target_link_libraries(${name} PUBLIC cmrc::base) + set_property(TARGET ${name} PROPERTY CMRC_IS_RESOURCE_LIBRARY TRUE) + if(ARG_ALIAS) + add_library("${ARG_ALIAS}" ALIAS ${name}) + endif() + cmrc_add_resources(${name} ${ARG_UNPARSED_ARGUMENTS}) +endfunction() + +function(_cmrc_register_dirs name dirpath) + if(dirpath STREQUAL "") + return() + endif() + # Skip this dir if we have already registered it + get_target_property(registered "${name}" _CMRC_REGISTERED_DIRS) + if(dirpath IN_LIST registered) + return() + endif() + # Register the parent directory first + get_filename_component(parent "${dirpath}" DIRECTORY) + if(NOT parent STREQUAL "") + _cmrc_register_dirs("${name}" "${parent}") + endif() + # Now generate the registration + set_property(TARGET "${name}" APPEND PROPERTY _CMRC_REGISTERED_DIRS "${dirpath}") + _cm_encode_fpath(sym "${dirpath}") + if(parent STREQUAL "") + set(parent_sym root_directory) + else() + _cm_encode_fpath(parent_sym "${parent}") + endif() + get_filename_component(leaf "${dirpath}" NAME) + set_property( + TARGET "${name}" + APPEND PROPERTY CMRC_MAKE_DIRS + "static auto ${sym}_dir = ${parent_sym}_dir.directory.add_subdir(\"${leaf}\")\;" + "root_index.emplace(\"${dirpath}\", &${sym}_dir.index_entry)\;" + ) +endfunction() + +function(cmrc_add_resources name) + get_target_property(is_reslib ${name} CMRC_IS_RESOURCE_LIBRARY) + if(NOT TARGET ${name} OR NOT is_reslib) + message(SEND_ERROR "cmrc_add_resources called on target '${name}' which is not an existing resource library") + return() + endif() + + set(options) + set(args WHENCE PREFIX) + set(list_args) + cmake_parse_arguments(ARG "${options}" "${args}" "${list_args}" "${ARGN}") + + if(NOT ARG_WHENCE) + set(ARG_WHENCE ${CMAKE_CURRENT_SOURCE_DIR}) + endif() + _cmrc_normalize_path(ARG_WHENCE) + get_filename_component(ARG_WHENCE "${ARG_WHENCE}" ABSOLUTE) + + # Generate the identifier for the resource library's namespace + get_target_property(lib_ns "${name}" CMRC_NAMESPACE) + + get_target_property(libdir ${name} CMRC_LIBDIR) + get_target_property(target_dir ${name} SOURCE_DIR) + file(RELATIVE_PATH reldir "${target_dir}" "${CMAKE_CURRENT_SOURCE_DIR}") + if(reldir MATCHES "^\\.\\.") + message(SEND_ERROR "Cannot call cmrc_add_resources in a parent directory from the resource library target") + return() + endif() + + foreach(input IN LISTS ARG_UNPARSED_ARGUMENTS) + _cmrc_normalize_path(input) + get_filename_component(abs_in "${input}" ABSOLUTE) + # Generate a filename based on the input filename that we can put in + # the intermediate directory. + file(RELATIVE_PATH relpath "${ARG_WHENCE}" "${abs_in}") + if(relpath MATCHES "^\\.\\.") + # For now we just error on files that exist outside of the soure dir. + message(SEND_ERROR "Cannot add file '${input}': File must be in a subdirectory of ${ARG_WHENCE}") + continue() + endif() + if(DEFINED ARG_PREFIX) + _cmrc_normalize_path(ARG_PREFIX) + endif() + if(ARG_PREFIX AND NOT ARG_PREFIX MATCHES "/$") + set(ARG_PREFIX "${ARG_PREFIX}/") + endif() + get_filename_component(dirpath "${ARG_PREFIX}${relpath}" DIRECTORY) + _cmrc_register_dirs("${name}" "${dirpath}") + get_filename_component(abs_out "${libdir}/intermediate/${ARG_PREFIX}${relpath}.cpp" ABSOLUTE) + # Generate a symbol name relpath the file's character array + _cm_encode_fpath(sym "${relpath}") + # Get the symbol name for the parent directory + if(dirpath STREQUAL "") + set(parent_sym root_directory) + else() + _cm_encode_fpath(parent_sym "${dirpath}") + endif() + # Generate the rule for the intermediate source file + _cmrc_generate_intermediate_cpp(${lib_ns} ${sym} "${abs_out}" "${abs_in}") + target_sources(${name} PRIVATE "${abs_out}") + set_property(TARGET ${name} APPEND PROPERTY CMRC_EXTERN_DECLS + "// Pointers to ${input}" + "extern const char* const ${sym}_begin\;" + "extern const char* const ${sym}_end\;" + ) + get_filename_component(leaf "${relpath}" NAME) + set_property( + TARGET ${name} + APPEND PROPERTY CMRC_MAKE_FILES + "root_index.emplace(" + " \"${ARG_PREFIX}${relpath}\"," + " ${parent_sym}_dir.directory.add_file(" + " \"${leaf}\"," + " res_chars::${sym}_begin," + " res_chars::${sym}_end" + " )" + ")\;" + ) + endforeach() +endfunction() + +function(_cmrc_generate_intermediate_cpp lib_ns symbol outfile infile) + add_custom_command( + # This is the file we will generate + OUTPUT "${outfile}" + # These are the primary files that affect the output + DEPENDS "${infile}" "${_CMRC_SCRIPT}" + COMMAND + "${CMAKE_COMMAND}" + -D_CMRC_GENERATE_MODE=TRUE + -DNAMESPACE=${lib_ns} + -DSYMBOL=${symbol} + "-DINPUT_FILE=${infile}" + "-DOUTPUT_FILE=${outfile}" + -P "${_CMRC_SCRIPT}" + COMMENT "Generating intermediate file for ${infile}" + ) +endfunction() + +function(_cm_encode_fpath var fpath) + string(MAKE_C_IDENTIFIER "${fpath}" ident) + string(MD5 hash "${fpath}") + string(SUBSTRING "${hash}" 0 4 hash) + set(${var} f_${hash}_${ident} PARENT_SCOPE) +endfunction() diff --git a/cmake/basic-installer.cmake b/cmake/basic-installer.cmake new file mode 100644 index 0000000..8185ae8 --- /dev/null +++ b/cmake/basic-installer.cmake @@ -0,0 +1,122 @@ +# A basic installer setup. +# +# This cmake file introduces two targets +# awcons-products: moves all the built assets to a well named directory +# awcons-installer: depends on awcons-products, builds an installer +# +# Right now awcons-installer builds just the crudest zip file but this is the target +# on which we will hang the proper installers later + +set(AWCONS_PRODUCT_DIR ${CMAKE_BINARY_DIR}/awcons-products) +file(MAKE_DIRECTORY ${AWCONS_PRODUCT_DIR}) + +message(STATUS "Configuring installer") +add_custom_target(awcons-products ALL) +add_custom_target(awcons-installer) + +function(awcons_package format) + if (TARGET airwin-consolidated) + get_target_property(output_dir airwin-consolidated RUNTIME_OUTPUT_DIRECTORY) + + if (TARGET airwin-consolidated_${format}) + message(STATUS "Adding airwin-consolidated_${format} to installer") + add_dependencies(awcons-products airwin-consolidated_${format}) + add_custom_command( + TARGET awcons-products + POST_BUILD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMAND echo "Installing ${output_dir}/${format} to ${AWCONS_PRODUCT_DIR}" + COMMAND ${CMAKE_COMMAND} -E copy_directory ${output_dir}/${format}/ ${AWCONS_PRODUCT_DIR}/ + ) + endif () + endif() +endfunction() + +awcons_package(VST3) +awcons_package(VST) +awcons_package(LV2) +awcons_package(AU) +awcons_package(CLAP) +awcons_package(Standalone) + +if (WIN32) + message(STATUS "Including special Windows cleanup installer stage") + add_custom_command(TARGET awcons-products + POST_BUILD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMAND ${CMAKE_COMMAND} -E echo "Cleaning up Windows goobits" + COMMAND ${CMAKE_COMMAND} -E rm -f "${AWCONS_PRODUCT_DIR}/Airwindows Consolidated.exp" + COMMAND ${CMAKE_COMMAND} -E rm -f "${AWCONS_PRODUCT_DIR}/Airwindows Consolidated.ilk" + COMMAND ${CMAKE_COMMAND} -E rm -f "${AWCONS_PRODUCT_DIR}/Airwindows Consolidated.lib" + COMMAND ${CMAKE_COMMAND} -E rm -f "${AWCONS_PRODUCT_DIR}/Airwindows Consolidated.pdb" + ) +endif () + +add_dependencies(awcons-installer awcons-products) + +add_custom_command( + TARGET awcons-installer + POST_BUILD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMAND echo "Installing LICENSE and so forth to ${AWCONS_PRODUCT_DIR}" + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/res/LICENSE.gpl3 ${AWCONS_PRODUCT_DIR}/ +) + +find_package(Git) + +if (Git_FOUND) + execute_process( + COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE VERSION_CHUNK + OUTPUT_STRIP_TRAILING_WHITESPACE + ) +else () + set(VERSION_CHUNK "unknownhash") +endif () + +string(TIMESTAMP AWCONS_DATE "%Y-%m-%d") +set(AWCONS_ZIP AirwindowsConsolidated-${AWCONS_DATE}-${VERSION_CHUNK}-${CMAKE_SYSTEM_NAME}.zip) + + +if (APPLE) + message(STATUS "Configuring for Mac installer.") + add_custom_command( + TARGET awcons-installer + POST_BUILD + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMAND ${CMAKE_COMMAND} -E make_directory installer + COMMAND ${CMAKE_SOURCE_DIR}/scripts/installer_mac/make_installer.sh "Airwindows Consolidated" ${CMAKE_BINARY_DIR}/awcons-products ${CMAKE_SOURCE_DIR}/res/installer_mac ${CMAKE_BINARY_DIR}/installer "${AWCONS_DATE}-${VERSION_CHUNK}" + COMMAND zip -r installer/${AWCONS_ZIP} ${AWCONS_PRODUCT_DIR}/ + ) +elseif (WIN32) + message(STATUS "Configuring for Windows installer") + add_custom_command( + TARGET awcons-installer + POST_BUILD + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMAND ${CMAKE_COMMAND} -E make_directory installer + COMMAND 7z a -r installer/${AWCONS_ZIP} ${AWCONS_PRODUCT_DIR}/ + COMMAND ${CMAKE_COMMAND} -E echo "ZIP Installer in: installer/${AWCONS_ZIP}") + #find_program(AWCONS_NUGET_EXE nuget.exe PATHS ENV "PATH") + #if(AWCONS_NUGET_EXE) + # message(STATUS "NuGet found at ${AWCONS_NUGET_EXE}") + # add_custom_command( + # TARGET awcons-installer + # POST_BUILD + # WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + # COMMAND ${AWCONS_NUGET_EXE} install Tools.InnoSetup -version 6.2.1 + # COMMAND Tools.InnoSetup.6.2.1/tools/iscc.exe /O"installer" /DAWCONS_SRC="${CMAKE_SOURCE_DIR}" /DAWCONS_BIN="${CMAKE_BINARY_DIR}" /DMyAppVersion="${AWCONS_DATE}-${VERSION_CHUNK}" "${CMAKE_SOURCE_DIR}/scripts/awcons.iss") + #else() + # message(STATUS "NuGet not found!") + #endif() +else () + message(STATUS "Basic installer: Target is installer/${AWCONS_ZIP}") + add_custom_command( + TARGET awcons-installer + POST_BUILD + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMAND ${CMAKE_COMMAND} -E make_directory installer + COMMAND ${CMAKE_COMMAND} -E tar cvf installer/${AWCONS_ZIP} --format=zip ${AWCONS_PRODUCT_DIR}/ + COMMAND ${CMAKE_COMMAND} -E echo "Installer in: installer/${AWCONS_ZIP}") +endif () diff --git a/res/LICENSE.gpl3 b/res/LICENSE.gpl3 new file mode 100644 index 0000000..0c95a3e --- /dev/null +++ b/res/LICENSE.gpl3 @@ -0,0 +1,674 @@ +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) 2018 {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + surge Copyright (C) 2018 Claes Johanson + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/res/installer_mac/License.txt b/res/installer_mac/License.txt new file mode 100644 index 0000000..0c95a3e --- /dev/null +++ b/res/installer_mac/License.txt @@ -0,0 +1,674 @@ +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) 2018 {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + surge Copyright (C) 2018 Claes Johanson + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/res/installer_mac/Readme.rtf b/res/installer_mac/Readme.rtf new file mode 100644 index 0000000..a7cef7b --- /dev/null +++ b/res/installer_mac/Readme.rtf @@ -0,0 +1,17 @@ +{\rtf1\ansi\ansicpg1252\cocoartf2758 +\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +{\*\expandedcolortbl;;} +\margl1440\margr1440\vieww10800\viewh8400\viewkind0 +\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\qc\partightenfactor0 + +\f0\b\fs36 \cf0 Airwindows Consolidated +\fs24 \ + +\f1\b0 All the Airwindows, One plugin +\f0\b \ +\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 +\cf0 \ +Have fun and update often! +\f1\b0 \ +} \ No newline at end of file diff --git a/scripts/installer_mac/make_installer.sh b/scripts/installer_mac/make_installer.sh new file mode 100755 index 0000000..190deca --- /dev/null +++ b/scripts/installer_mac/make_installer.sh @@ -0,0 +1,194 @@ +#!/bin/bash + +# Documentation for pkgbuild and productbuild: https://developer.apple.com/library/archive/documentation/DeveloperTools/Reference/DistributionDefinitionRef/Chapters/Distribution_XML_Ref.html + +# preflight check +PRODUCT=$1 +INDIR=$2 +RESOURCESDIR=$3 +TARGET_DIR=$4 +VERSION=$5 + +TMPDIR="./installer-tmp" +mkdir -p $TMPDIR + +echo "MAKE from $INDIR $RESOURCESDIR into $TARGET_DIR with $VERSION" + +VST3="${PRODUCT}.vst3" +AU="${PRODUCT}.component" +CLAP="${PRODUCT}.clap" +APP="${PRODUCT}.app" + +PRODUCTFILE=`echo $PRODUCT | tr ' ' '-' | tr '[:upper:]' '[:lower:]'` +echo "PRODUCTFILE is ${PRODUCTFILE}" + +mkdir $TMPDIR/AppResourcesPackageScript +cat > $TMPDIR/AppResourcesPackageScript/postinstall << PIEND +#!/bin/bash +rsync -r --delete "/tmp/sst-installer/$APP" /Applications +chown -R $USER:staff "/Applications/$APP"; +# rm -rf /tmp/sst-installer +date > /tmp/sst-installer/handled +PIEND + +chmod 755 $TMPDIR/AppResourcesPackageScript/postinstall + + +if [ "$VERSION" == "" ]; then + echo "You must specify the version you are packaging!" + echo "eg: ./make_installer.sh 1.0.6b4" + exit 1 +fi + + +OUTPUT_BASE_FILENAME="${PRODUCTFILE}-macOS-$VERSION" + +build_flavor() +{ + flavor=$1 + flavorprod=$2 + ident=$3 + loc=$4 + scripts=$5 + + echo --- BUILDING ${PRODUCTFILE}_${flavor}.pkg from "$flavorprod" --- + + workdir=$TMPDIR/$flavor + mkdir -p $workdir + + # In the past we would pkgbuild --analyze first to make a plist file, but we are perfectly fine with the + # defaults, so we skip that step here. http://thegreyblog.blogspot.com/2014/06/os-x-creating-packages-from-command_2.html + # was pretty handy in figuring that out and man pkgbuild convinced us to not do it, as did testing. + # + # The defaults only work if a component is a sole entry in a staging directory though, so synthesize that + # by moving the product to a tmp dir + + cp -r "$INDIR/$flavorprod" "$workdir" + ls -l $workdir + + sca="" + if [[ ! -z $scripts ]]; then + sca="--scripts $scripts" + fi + + if [[ ! -z $MAC_SIGNING_CERT ]]; then + [[ -z $MAC_INSTALLING_CERT ]] && echo "You need an installing cert too " && exit 2 + codesign --force -s "$MAC_SIGNING_CERT" -o runtime --deep "$workdir/$flavorprod" + codesign -vvv "$workdir/$flavorprod" + + pkgbuild --sign "$MAC_INSTALLING_CERT" --root $workdir --identifier $ident --version $VERSION --install-location "$loc" "$TMPDIR/${PRODUCTFILE}_${flavor}.pkg" $sca || exit 1 + echo pkgbuild --sign "$MAC_INSTALLING_CERT" --root $workdir --identifier $ident --version $VERSION --install-location "$loc" "$TMPDIR/${PRODUCTFILE}_${flavor}.pkg" $sca || exit 1 + else + pkgbuild --root $workdir --identifier $ident --version $VERSION --install-location "$loc" "$TMPDIR/${PRODUCTFILE}_${flavor}.pkg" $sca || exit 1 + fi + + #rm -rf $workdir +} + + +if [[ -d $INDIR/$VST3 ]]; then + build_flavor "VST3" "$VST3" "org.surge-synth-team.${PRODUCTFILE}.vst3.pkg" "/Library/Audio/Plug-Ins/VST3" +fi + +if [[ -d $INDIR/$AU ]]; then + build_flavor "AU" "$AU" "org.surge-synth-team.${PRODUCTFILE}.component.pkg" "/Library/Audio/Plug-Ins/Components" +fi + +if [[ -d $INDIR/$APP ]]; then + build_flavor "APP" "$APP" "org.surge-synth-team.${PRODUCTFILE}.app.pkg" "/tmp/sst-installer" $TMPDIR/AppResourcesPackageScript +fi + +if [[ -d $INDIR/$CLAP ]]; then + build_flavor "CLAP" "$CLAP" "org.surge-synth-team.${PRODUCTFILE}.clap.pkg" "/Library/Audio/Plug-Ins/CLAP" +fi + +echo --- Sub Packages Created --- +ls -l "${TMPDIR}" + +# create distribution.xml + +if [[ -d $INDIR/$VST3 ]]; then + VST3_PKG_REF="" + VST3_CHOICE="" + VST3_CHOICE_DEF="${PRODUCTFILE}_VST3.pkg" +fi +if [[ -d $INDIR/$AU ]]; then + AU_PKG_REF="" + AU_CHOICE="" + AU_CHOICE_DEF="${PRODUCTFILE}_AU.pkg" +fi +if [[ -d $INDIR/$CLAP ]]; then + CLAP_PKG_REF="" + CLAP_CHOICE="" + CLAP_CHOICE_DEF="${PRODUCTFILE}_CLAP.pkg" +fi +if [[ -d $INDIR/$APP ]]; then + APP_PKG_REF="" + APP_CHOICE="" + APP_CHOICE_DEF="${PRODUCTFILE}_APP.pkg" +fi + + +cat > $TMPDIR/distribution.xml << XMLEND + + + ${PRODUCT} ${VERSION} + + + ${VST3_PKG_REF} + ${AU_PKG_REF} + ${CLAP_PKG_REF} + ${APP_PKG_REF} + + + ${VST3_CHOICE} + ${AU_CHOICE} + ${CLAP_CHOICE} + ${APP_CHOICE} + + ${VST3_CHOICE_DEF} + ${AU_CHOICE_DEF} + ${CLAP_CHOICE_DEF} + ${APP_CHOICE_DEF} + +XMLEND + +# build installation bundle + +pushd ${TMPDIR} +if [[ ! -z $MAC_INSTALLING_CERT ]]; then + echo "Building SIGNED PKG" + echo productbuild --sign "$MAC_INSTALLING_CERT" --distribution "distribution.xml" --package-path "." --resources ${RESOURCESDIR} "$OUTPUT_BASE_FILENAME.pkg" + productbuild --sign "$MAC_INSTALLING_CERT" --distribution "distribution.xml" --package-path "." --resources ${RESOURCESDIR} "$OUTPUT_BASE_FILENAME.pkg" +else + echo "Building UNSIGNED PKG" + productbuild --distribution "distribution.xml" --package-path "." --resources ${RESOURCESDIR}/ "$OUTPUT_BASE_FILENAME.pkg" +fi + +popd + +Rez -append ${RESOURCESDIR}/icns.rsrc -o "${TMPDIR}/${OUTPUT_BASE_FILENAME}.pkg" +SetFile -a C "${TMPDIR}/${OUTPUT_BASE_FILENAME}.pkg" +mkdir -p "${TMPDIR}/${PRODUCTFILE}" + +mv "${TMPDIR}/${OUTPUT_BASE_FILENAME}.pkg" "${TMPDIR}/${PRODUCTFILE}" +# create a DMG if required + +if [[ -f "${TARGET_DIR}/$OUTPUT_BASE_FILENAME.dmg" ]]; then + rm "${TARGET_DIR}/$OUTPUT_BASE_FILENAME.dmg" +fi +hdiutil create /tmp/tmp.dmg -ov -volname "$OUTPUT_BASE_FILENAME" -fs HFS+ -srcfolder "${TMPDIR}/${PRODUCTFILE}/" +hdiutil convert /tmp/tmp.dmg -format UDZO -o "${TARGET_DIR}/$OUTPUT_BASE_FILENAME.dmg" + +if [[ ! -z $MAC_SIGNING_CERT ]]; then + codesign --force -s "$MAC_SIGNING_CERT" --timestamp "${TARGET_DIR}/$OUTPUT_BASE_FILENAME.dmg" + codesign -vvv "${TARGET_DIR}/$OUTPUT_BASE_FILENAME.dmg" + xcrun notarytool submit "${TARGET_DIR}/$OUTPUT_BASE_FILENAME.dmg" --apple-id ${MAC_SIGNING_ID} --team-id ${MAC_SIGNING_TEAM} --password ${MAC_SIGNING_1UPW} --wait + + xcrun stapler staple "${TARGET_DIR}/${OUTPUT_BASE_FILENAME}.dmg" +fi + +# clean up + +#rm distribution.xml +#rm Surge_*.pkg diff --git a/src-juce/AWConsolidatedEditor.cpp b/src-juce/AWConsolidatedEditor.cpp new file mode 100644 index 0000000..1a0b9a5 --- /dev/null +++ b/src-juce/AWConsolidatedEditor.cpp @@ -0,0 +1,563 @@ + +#include + +#include "AWConsolidatedEditor.h" +#include "AirwinRegistry.h" + +#include + +CMRC_DECLARE(awconsolidated_resources); +namespace awres = cmrc::awconsolidated_resources; + +struct AWLookAndFeel : public juce::LookAndFeel_V4 +{ + AWLookAndFeel() + { + setColour(juce::PopupMenu::backgroundColourId, juce::Colour(10, 10, 15)); + setColour(juce::PopupMenu::textColourId, juce::Colours::white); + setColour(juce::PopupMenu::highlightedBackgroundColourId, juce::Colour(60, 60, 65)); + setColour(juce::PopupMenu::highlightedTextColourId, juce::Colours::white); + setColour(juce::ScrollBar::ColourIds::thumbColourId, juce::Colour(120,120,125)); + + auto fs = awres::get_filesystem(); + if (fs.is_file("res/PlusJakartaSans-Medium.ttf")) + { + auto f = fs.open("res/PlusJakartaSans-Medium.ttf"); + jakartaSansMedium = juce::Typeface::createSystemTypefaceFor(f.begin(), f.size()); + } + } + + juce::Typeface::Ptr jakartaSansMedium; + juce::Font getPopupMenuFont() override { return juce::Font(jakartaSansMedium).withHeight(16); } +}; + +struct Picker : public juce::Component +{ + Picker(AWConsolidatedAudioProcessorEditor *ed) : editor(ed) + { + setAccessible(true); + setTitle("Select Airwindow"); + setDescription("Select Airwindow"); + setWantsKeyboardFocus(true); + } + void paint(juce::Graphics &g) override + { + int idx = editor->processor.curentProcessorIndex; + auto &rg = AirwinRegistry::registry[idx]; + auto bounds = getLocalBounds().toFloat().reduced(2.f, 2.f); + + g.setColour(juce::Colours::black); + g.fillRoundedRectangle(bounds, 5); + g.setColour(juce::Colours::lightgrey); + g.drawRoundedRectangle(bounds, 5, 1); + g.setColour(juce::Colours::white); + g.setFont(juce::Font(editor->jakartaSansSemi).withHeight(28)); + g.drawText(rg.name, bounds.reduced(8, 5), juce::Justification::centredBottom); + + g.setFont(juce::Font(editor->jakartaSansMedium).withHeight(18)); + g.drawText(rg.category, bounds.reduced(8, 3), juce::Justification::centredTop); + + auto p = juce::Path(); + + auto jd = jogUp.reduced(3, 5); + p.addTriangle(jd.getX(), jd.getY() + jd.getHeight(), jd.getX() + jd.getWidth(), + jd.getY() + jd.getHeight(), jd.getX() + 0.5 * jd.getWidth(), jd.getY()); + g.setColour(juce::Colour(90, 90, 95)); + g.fillPath(p); + g.setColour(juce::Colours::white); + g.strokePath(p, juce::PathStrokeType(1)); + + p = juce::Path(); + jd = jogDown.reduced(3, 5); + p.addTriangle(jd.getX(), jd.getY(), jd.getX() + jd.getWidth(), jd.getY(), + jd.getX() + 0.5 * jd.getWidth(), jd.getY() + jd.getHeight()); + g.setColour(juce::Colour(90, 90, 95)); + g.fillPath(p); + g.setColour(juce::Colours::white); + g.strokePath(p, juce::PathStrokeType(1)); + + auto bx = getLocalBounds().reduced(8, 16); + bx = bx.withWidth(bx.getHeight()); + auto r = bx.withHeight(bx.getHeight() / 5); + for (int i = 0; i < 3; ++i) + { + auto q = r.reduced(1).toFloat(); + g.setColour(juce::Colour(90, 90, 95)); + g.fillRoundedRectangle(q, 1); + g.setColour(juce::Colours::white); + g.drawRoundedRectangle(q, 1, 1); + + r = r.translated(0, 2 * bx.getHeight() / 5); + } + } + + juce::Rectangle jogUp, jogDown; + void resized() override + { + auto b = getLocalBounds().reduced(8, 10); + auto hh = b.getHeight() / 2; + jogUp = b.withHeight(hh).withTrimmedLeft(b.getWidth() - hh); + jogDown = jogUp.translated(0, hh); + } + + bool keyPressed(const juce::KeyPress &p) override + { + if (p.getKeyCode() == juce::KeyPress::returnKey || + (p.getKeyCode() == juce::KeyPress::F10Key && p.getModifiers().isShiftDown())) + { + editor->showMenu(); + return true; + } + return false; + } + void mouseDown(const juce::MouseEvent &e) override + { + if (jogUp.toFloat().contains(e.position)) + { + editor->jog(-1); + startJogHold(-1); + } + else if (jogDown.toFloat().contains(e.position)) + { + editor->jog(1); + startJogHold(1); + } + else + { + editor->showMenu(); + } + } + void mouseUp(const juce::MouseEvent &e) override { stopJogHold(); } + + bool isJogHeld{false}; + void startJogHold(int dir) + { + isJogHeld = true; + juce::Timer::callAfterDelay(800, [dir, w = juce::Component::SafePointer(this)]() { + if (w) + { + w->doJogHold(dir); + } + }); + } + + void stopJogHold() { isJogHeld = false; } + void doJogHold(int dir) + { + if (!isJogHeld) + return; + editor->jog(dir); + juce::Timer::callAfterDelay(200, [dir, w = juce::Component::SafePointer(this)]() { + if (w) + { + w->doJogHold(dir); + } + }); + } + + AWConsolidatedAudioProcessorEditor *editor{nullptr}; +#if 0 + struct AH : public juce::AccessibilityHandler + { + struct AHV : public juce::AccessibilityValueInterface + { + explicit AHV(Picker *s) : comp(s) {} + + Picker *comp; + + bool isReadOnly() const override { return true; } + double getCurrentValue() const override { return 0.; } + + void setValue(double) override {} + void setValueAsString(const juce::String &newValue) override {} + AccessibleValueRange getRange() const override { return {{0, 1}, 1}; } + juce::String getCurrentValueAsString() const override + { + return fx_type_names[comp->editor->processor.getEffectType()]; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AHV); + }; + + explicit AH(Picker *s) + : comp(s), + juce::AccessibilityHandler(*s, juce::AccessibilityRole::button, + juce::AccessibilityActions() + .addAction(juce::AccessibilityActionType::press, + [this]() { comp->editor->showMenu(); }) + .addAction(juce::AccessibilityActionType::showMenu, + [this]() { comp->editor->showMenu(); }), + AccessibilityHandler::Interfaces{std::make_unique(s)}) + { + } + + Picker *comp; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AH); + }; + + std::unique_ptr createAccessibilityHandler() override + { + return std::make_unique(this); + } +#endif +}; + +struct DocPanel : juce::Component +{ + AWConsolidatedAudioProcessorEditor *editor{nullptr}; + DocPanel(AWConsolidatedAudioProcessorEditor *ed) : editor(ed) {} + void rebuild() { + auto r = juce::Rectangle(0, 0, targetWidth, 10000); + + auto tFont = juce::Font(editor->jakartaSansSemi).withHeight(18); + juce::GlyphArrangement gaTitle; + gaTitle.addFittedText(tFont, editor->docHeader.substring(2), r.getX(), r.getY(), + r.getWidth(), r.getHeight(), juce::Justification::topLeft, 3); + auto bounds = gaTitle.getBoundingBox(0, -1, true); + + auto q = r.translated(0, bounds.getHeight() + 8); + + auto bFont = juce::Font(editor->jakartaSansSemi).withHeight(13); + juce::GlyphArrangement gaBody; + gaBody.addFittedText(bFont, editor->docString.trim(), q.getX(), q.getY(), + q.getWidth(), q.getHeight(), juce::Justification::topLeft, 1000); + auto bodyBounds = gaBody.getBoundingBox(0, -1, true); + r = r.withHeight(bodyBounds.getBottom()); + + setSize(r.getWidth(), r.getHeight()); + + } + + float targetWidth{10}; + + void paint(juce::Graphics &g) + { + g.setColour(juce::Colours::white); + auto r = getLocalBounds(); + auto tFont = juce::Font(editor->jakartaSansSemi).withHeight(18); + juce::GlyphArrangement gaTitle; + gaTitle.addFittedText(tFont, editor->docHeader.substring(2), r.getX(), r.getY(), + r.getWidth(), r.getHeight(), juce::Justification::topLeft, 3); + auto bounds = gaTitle.getBoundingBox(0, -1, true); + r = r.translated(0, bounds.getHeight() + 4); + gaTitle.draw(g); + + g.setColour(juce::Colour(120, 120, 125)); + g.drawLine(r.getX(), r.getY(), r.getX() + r.getWidth(), r.getY()); + r = r.translated(0, 4); + + g.setColour(juce::Colours::white.darker(0.3f)); + + auto q = r; + auto bFont = juce::Font(editor->jakartaSansMedium).withHeight(13); + juce::GlyphArrangement gaBody; + gaBody.addFittedText(bFont, editor->docString.trim(), q.getX(), q.getY(), + q.getWidth(), q.getHeight(), juce::Justification::topLeft, 1000); + gaBody.draw(g); + } +}; + +struct ParamKnob : juce::Component +{ + juce::AudioParameterFloat *weakParam{nullptr}; + const std::atomic &active; + ParamKnob(const juce::String &cn, juce::AudioParameterFloat *param, const std::atomic &a) + : juce::Component(cn), weakParam{param}, active{a} + { + } + + float getValue() const { return weakParam ? weakParam->get() : 0.f; } + void setValue(float to) + { + if (weakParam) + weakParam->setValueNotifyingHost(to); + } + + void paint(juce::Graphics &g) override + { + auto knobHandle = getLocalBounds().reduced(4).toFloat(); + if (!active) + { + g.setColour(juce::Colours::grey.withAlpha(0.3f)); + g.fillEllipse(knobHandle.toFloat()); + return; + } + + auto arc = [&](auto startV, auto endV) { + float dPath = 0.2; + float dAng = juce::MathConstants::pi * (1 - dPath); + float startA = dAng * (2 * startV - 1); + float endA = dAng * (2 * endV - 1); + + auto region = knobHandle; + auto p = juce::Path(); + p.startNewSubPath(region.getCentre().toFloat()); + p.addArc(region.getX(), region.getY(), region.getWidth(), region.getHeight(), startA, + endA, true); + return p; + }; + + g.setColour(juce::Colour(60, 60, 65)); + g.fillEllipse(knobHandle.reduced(2)); + + g.setColour(juce::Colour(140, 140, 150)); + g.strokePath(arc(-0.01f, 1.01f), juce::PathStrokeType(6)); + + g.setColour(juce::Colour(0, 0, 0)); + g.strokePath(arc(0.f, 1.f), juce::PathStrokeType(4)); + + g.setColour(juce::Colour(220, 220, 230)); + g.strokePath(arc(0.f, getValue()), juce::PathStrokeType(4)); + } + + juce::Point mousePos; + void mouseDown(const juce::MouseEvent &event) override + { + mousePos = event.position; + weakParam->beginChangeGesture(); + } + + void mouseUp(const juce::MouseEvent &event) override { weakParam->endChangeGesture(); } + + void mouseDrag(const juce::MouseEvent &event) override + { + auto dy = -event.position.getY() + mousePos.getY(); + mousePos = event.position; + + float mul = 0.01; + if (event.mods.isShiftDown()) + mul *= 0.1; + + auto nv = std::clamp(weakParam->get() + dy * mul, 0.f, 1.f); + + weakParam->setValueNotifyingHost(nv); + } +}; + +struct ParamDisp : juce::Component +{ + juce::AudioParameterFloat *weakParam{nullptr}; + const std::atomic &active; + AWConsolidatedAudioProcessorEditor *editor{nullptr}; + + ParamDisp(const juce::String &cn, juce::AudioParameterFloat *param, const std::atomic &a, + AWConsolidatedAudioProcessorEditor *ed) + : juce::Component(cn), weakParam{param}, active(a), editor(ed) + { + } + + float getValue() const { return weakParam ? weakParam->get() : 0.f; } + + void paint(juce::Graphics &g) override + { + if (!active) + { + g.setColour(juce::Colours::black.withAlpha(0.1f)); + g.fillRoundedRectangle(getLocalBounds().toFloat(), 3); + g.setColour(juce::Colours::white.withAlpha(0.2f)); + g.drawRoundedRectangle(getLocalBounds().toFloat(), 3, 1); + return; + } + g.setColour(juce::Colours::black); + g.fillRoundedRectangle(getLocalBounds().toFloat(), 3); + g.setColour(juce::Colours::white); + g.drawRoundedRectangle(getLocalBounds().toFloat(), 3, 1); + + auto bounds = getLocalBounds().reduced(5, 2); + g.setFont(juce::Font(editor->firaMono).withHeight(18)); + g.drawText(weakParam->getCurrentValueAsText().trim(), bounds.withTrimmedBottom(2), + juce::Justification::bottomLeft); + + g.setFont(juce::Font(editor->jakartaSansMedium).withHeight(14)); + g.drawText(weakParam->getName(64), bounds, juce::Justification::topLeft); + } +}; +//============================================================================== +AWConsolidatedAudioProcessorEditor::AWConsolidatedAudioProcessorEditor( + AWConsolidatedAudioProcessor &p) + : AudioProcessorEditor(&p), processor(p) +{ + lnf = std::make_unique(); + juce::LookAndFeel::setDefaultLookAndFeel(lnf.get()); + setAccessible(true); + setFocusContainerType(juce::Component::FocusContainerType::keyboardFocusContainer); + + setSize(baseHeight, baseHeight); + + + auto fs = awres::get_filesystem(); + try + { + if (fs.is_file("res/clipper.svg")) + { + auto f = fs.open("res/clipper.svg"); + clipperIcon = juce::Drawable::createFromImageData(f.begin(), f.size()); + } + + if (fs.is_file("res/PlusJakartaSans-Medium.ttf")) + { + auto f = fs.open("res/PlusJakartaSans-Medium.ttf"); + jakartaSansMedium = juce::Typeface::createSystemTypefaceFor(f.begin(), f.size()); + } + + if (fs.is_file("res/PlusJakartaSans-SemiBold.ttf")) + { + auto f = fs.open("res/PlusJakartaSans-SemiBold.ttf"); + jakartaSansSemi = juce::Typeface::createSystemTypefaceFor(f.begin(), f.size()); + } + + if (fs.is_file("res/FiraMono-Regular.ttf")) + { + auto f = fs.open("res/FiraMono-Regular.ttf"); + firaMono = juce::Typeface::createSystemTypefaceFor(f.begin(), f.size()); + } + } + catch (std::exception &e) + { + } + + auto margin{5}; + menuPicker = std::make_unique(this); + addAndMakeVisible(*menuPicker); + menuPicker->setBounds(getLocalBounds().reduced(margin).withHeight(60)); + idleTimer = std::make_unique(this); + idleTimer->startTimer(1000 / 60); + + auto sz{40}; + auto kb = getLocalBounds().withHeight(sz).withWidth(sz).translated(margin, 60 + 2 * margin); + + for (int i = 0; i < AWConsolidatedAudioProcessor::nAWParams; ++i) + { + auto sl = std::make_unique(juce::String("kb") + std::to_string(i), + processor.fxParams[i], processor.active[i]); + sl->setBounds(kb); + addAndMakeVisible(*sl); + + knobs[i] = std::move(sl); + + auto lb = std::make_unique(juce::String("lb") + std::to_string(i), + processor.fxParams[i], processor.active[i], this); + lb->setBounds(kb.withWidth(180).translated(sz + margin, 0)); + addAndMakeVisible(*lb); + labels[i] = std::move(lb); + + kb = kb.translated(0, sz + margin); + } + + auto da = std::make_unique(this); + auto db = getLocalBounds().withTrimmedLeft(margin * 3 + sz + 180) + .withTrimmedRight(margin * 2) + .withTrimmedTop(60 + 2 * margin) + .withTrimmedBottom(40 + margin); + + da->targetWidth = db.getWidth() - 10; + da->rebuild(); + docArea = std::move(da); + + docView = std::make_unique(); + docView->setBounds(db); + docView->setViewedComponent(docArea.get(), false); + addAndMakeVisible(*docView); +} + +AWConsolidatedAudioProcessorEditor::~AWConsolidatedAudioProcessorEditor() +{ + idleTimer->stopTimer(); +} + +void AWConsolidatedAudioProcessorEditor::idle() +{ + if (processor.refreshUI.exchange(false)) + { + auto reg = AirwinRegistry::registry[processor.curentProcessorIndex]; + auto doc = std::string("res/awpdoc/") + reg.name + ".txt"; + + auto fs = awres::get_filesystem(); + try + { + if (fs.is_file(doc)) + { + auto fn = fs.open(doc); + docString = std::string(fn.begin(), fn.end()); + docHeader = docString.upToFirstOccurrenceOf("\n", false, false); + docString = docString.fromFirstOccurrenceOf("\n", false, false).trim(); + if (docArea) + docArea->rebuild(); + } + else + { + docString = "Documentation not present"; + } + } + catch (std::exception &e) + { + } + + repaint(); + } +} + +void AWConsolidatedAudioProcessorEditor::handleAsyncUpdate() {} + +void AWConsolidatedAudioProcessorEditor::resized() {} + +void AWConsolidatedAudioProcessorEditor::paint(juce::Graphics &g) +{ + auto b = getLocalBounds(); + auto gr = juce::ColourGradient(juce::Colour(20, 20, 25), {0.f, 0.f}, juce::Colour(50, 50, 55), + {0.f, 1.f * getHeight()}, false); + g.setGradientFill(gr); + g.fillAll(); + + static constexpr float footerHeight{40}; + auto fa = b.withHeight(footerHeight).withY(getHeight() - footerHeight); + g.setColour(juce::Colour(160, 160, 170)); + g.fillRect(fa); + g.setColour(juce::Colours::black); + g.drawLine(fa.getX(), fa.getY(), fa.getX() + fa.getWidth(), fa.getY(), 1); + g.setFont(juce::Font(jakartaSansSemi).withHeight(28)); + g.drawText("Airwindows", fa, juce::Justification::centred); + + g.setFont(juce::Font(jakartaSansMedium).withHeight(12)); + g.setColour(juce::Colour(110, 110, 115)); + + g.drawText(std::string("Build : ") + __DATE__, fa.reduced(3), juce::Justification::bottomRight); + + if (clipperIcon) + { + auto ss = juce::Graphics::ScopedSaveState(g); + g.addTransform(juce::AffineTransform().scaled(0.4).translated(10, 420)); + clipperIcon->draw(g, 0.6); + } +} + +void AWConsolidatedAudioProcessorEditor::jog(int dir) +{ + auto nx = AirwinRegistry::neighborIndexFor(processor.curentProcessorIndex, dir); + processor.pushResetTypeFromUI(nx); +} + +void AWConsolidatedAudioProcessorEditor::showMenu() +{ + // auto nx = AirwinRegistry::neighborIndexFor(processor.curentProcessorIndex, 1); + // processor.pushResetTypeFromUI(nx); + auto p = juce::PopupMenu(); + auto ent = AirwinRegistry::registry[processor.curentProcessorIndex]; + + p.addSectionHeader("Airwindows Effect"); + p.addSeparator(); + for (const auto &[cat, set] : AirwinRegistry::fxByCategory) + { + juce::PopupMenu sub; + for (const auto &nm : set) + { + sub.addItem(nm, true, nm == ent.name, [nm, w = juce::Component::SafePointer(this)]() { + if (w) + w->processor.pushResetTypeFromUI(AirwinRegistry::nameToIndex.at(nm)); + }); + } + p.addSubMenu(cat, sub, true, nullptr, cat == ent.category); + } + + p.showMenuAsync(juce::PopupMenu::Options().withParentComponent(this)); +} diff --git a/src-juce/AWConsolidatedEditor.h b/src-juce/AWConsolidatedEditor.h new file mode 100644 index 0000000..f4081ce --- /dev/null +++ b/src-juce/AWConsolidatedEditor.h @@ -0,0 +1,139 @@ +#ifndef AWCONSOLIDATED_EDITOR_H +#define AWCONSOLIDATED_EDITOR_H + +#include "AWConsolidatedProcessor.h" +#include "juce_gui_basics/juce_gui_basics.h" + +//============================================================================== +/** + */ +struct DocPanel; +class AWConsolidatedAudioProcessorEditor : public juce::AudioProcessorEditor, juce::AsyncUpdater +{ + public: + AWConsolidatedAudioProcessorEditor(AWConsolidatedAudioProcessor &); + ~AWConsolidatedAudioProcessorEditor(); + + + //============================================================================== + void paint(juce::Graphics &) override; + void resized() override; + + virtual void handleAsyncUpdate() override; + + void showMenu(); + void jog(int dir); + + // This reference is provided as a quick way for your editor to + // access the processor object that created it. + AWConsolidatedAudioProcessor &processor; + + static constexpr int baseWidth = 400, baseHeight = 600; + + struct IdleTimer : juce::Timer + { + IdleTimer(AWConsolidatedAudioProcessorEditor *ed) : ed(ed) {} + ~IdleTimer() = default; + void timerCallback() override { ed->idle(); } + AWConsolidatedAudioProcessorEditor *ed; + }; + void idle(); + std::unique_ptr idleTimer; + std::unique_ptr menuPicker; + std::array, AWConsolidatedAudioProcessor::nAWParams> knobs; + std::array, AWConsolidatedAudioProcessor::nAWParams> labels; + + std::unique_ptr clipperIcon; + + std::unique_ptr docArea; + std::unique_ptr docView; + + juce::String docString, docHeader; + + juce::Typeface::Ptr jakartaSansMedium, jakartaSansSemi, firaMono; + + std::unique_ptr lnf; + + // debugging + int gv{0}, bv{0}; +#if 0 + struct AccSlider : public juce::Slider + { + AccSlider() { setWantsKeyboardFocus(true); } + juce::String getTextFromValue(double v) override + { + // std::cout << "GTFV " << v << std::endl; + // return juce::Slider::getTextFromValue(v); + // This is a bit of a hack to externalize this but + return tv; + } + + juce::String tv; + void setTextValue(juce::String s) + { + tv = s; + if (auto *handler = getAccessibilityHandler()) + { + handler->notifyAccessibilityEvent(juce::AccessibilityEvent::valueChanged); + } + } + bool keyPressed(const juce::KeyPress &key) override + { + float amt = 0.05; + if (key.getModifiers().isShiftDown()) + amt = 0.01; + if (key.getKeyCode() == juce::KeyPress::upKey) + { + setValue(std::clamp(getValue() + amt, 0., 1.), + juce::NotificationType::sendNotification); + return true; + } + + if (key.getKeyCode() == juce::KeyPress::downKey) + { + setValue(std::clamp(getValue() - amt, 0., 1.), + juce::NotificationType::sendNotification); + return true; + } + + if (key.getKeyCode() == juce::KeyPress::homeKey) + { + setValue(1., juce::NotificationType::sendNotification); + return true; + } + + if (key.getKeyCode() == juce::KeyPress::endKey) + { + setValue(0., juce::NotificationType::sendNotification); + return true; + } + return false; + } + }; + AccSlider fxParamSliders[n_fx_params]; + + void blastToggleState(int i); + void resetLabels(); + + std::unique_ptr fxNameLabel; + + void addAndMakeVisibleRecordOrder(juce::Component *c) + { + accessibleOrderWeakRefs.push_back(c); + addAndMakeVisible(c); + } + + bool keyPressed(const juce::KeyPress &key) override; + + public: + std::vector accessibleOrderWeakRefs; + + public: + std::unique_ptr createFocusTraverser() override; +#endif + + private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AWConsolidatedAudioProcessorEditor) +}; + +#endif // SURGE_SRC_SURGE_FX_SURGEFXEDITOR_H diff --git a/src-juce/AWConsolidatedProcessor.cpp b/src-juce/AWConsolidatedProcessor.cpp new file mode 100644 index 0000000..a553101 --- /dev/null +++ b/src-juce/AWConsolidatedProcessor.cpp @@ -0,0 +1,267 @@ +/* + * Surge XT - a free and open source hybrid synthesizer, + * built by Surge Synth Team + * + * Learn more at https://surge-synthesizer.github.io/ + * + * Copyright 2018-2024, various authors, as described in the GitHub + * transaction log. + * + * Surge XT is released under the GNU General Public Licence v3 + * or later (GPL-3.0-or-later). The license is found in the "LICENSE" + * file in the root of this repository, or at + * https://www.gnu.org/licenses/gpl-3.0.en.html + * + * Surge was a commercial product from 2004-2018, copyright and ownership + * held by Claes Johanson at Vember Audio during that period. + * Claes made Surge open source in September 2018. + * + * All source for Surge XT is available at + * https://github.com/surge-synthesizer/surge + */ + +#include "AWConsolidatedProcessor.h" +#include "AWConsolidatedEditor.h" +#include "AirwinRegistry.h" + +#if LINUX +// getCurrentPosition is deprecated in J7 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +//============================================================================== +AWConsolidatedAudioProcessor::AWConsolidatedAudioProcessor() + : AudioProcessor(BusesProperties() + .withInput("Input", juce::AudioChannelSet::stereo(), true) + .withOutput("Output", juce::AudioChannelSet::stereo(), true)) +{ + Airwin2RackBase::defaultSampleRate = 48000; + + // Multiple calls to addParameter here + for (int i = 0; i < nAWParams; ++i) + { + fxParams[i] = new AWParam(juce::ParameterID(std::string("ctrl_") + std::to_string(i), 1), "Name", + juce::NormalisableRange(0.f, 1.f), 0.f); + fxParams[i]->getTextHandler = [i, this](auto f, auto iv) { + std::lock_guard g(this->displayProcessorMutex); + if (this->awDisplayProcessor && i < this->nProcessorParams) + { + for (int id = 0; id < this->nProcessorParams; ++id) + { + awDisplayProcessor->setParameter(id, fxParams[id]->get()); + } + char tl[kVstMaxParamStrLen], td[kVstMaxParamStrLen]; + this->awDisplayProcessor->getParameterDisplay(i, td); + this->awDisplayProcessor->getParameterLabel(i, tl); + return std::string(td) + (tl[0] == 0 ? "" : " " ) + std::string(tl); + } + else + { + return std::string("-"); + } + }; + fxParams[i]->getTextToValue = [i, this](auto s) { + std::lock_guard g(this->displayProcessorMutex); + if (this->awDisplayProcessor && i < this->nProcessorParams) + { + float rv = 0.f; + auto res = this->awDisplayProcessor->parameterTextToValue(i, s.toStdString().c_str(), rv); + if (res) + return rv; + } + return this->fxParams[i]->get(); + }; + fxParams[i]->addListener(this); + addParameter(fxParams[i]); + } + + setAWProcessorTo(AirwinRegistry::nameToIndex.at("Galactic"), true); +} + +AWConsolidatedAudioProcessor::~AWConsolidatedAudioProcessor() {} + +//============================================================================== +const juce::String AWConsolidatedAudioProcessor::getName() const { return JucePlugin_Name; } + +bool AWConsolidatedAudioProcessor::acceptsMidi() const { return false; } + +bool AWConsolidatedAudioProcessor::producesMidi() const { return false; } + +bool AWConsolidatedAudioProcessor::isMidiEffect() const { return false; } + +double AWConsolidatedAudioProcessor::getTailLengthSeconds() const { return 2.0; } + +int AWConsolidatedAudioProcessor::getNumPrograms() +{ + return 2; // NB: some hosts don't cope very well if you tell them there are 0 programs, + // so this should be at least 1, even if you're not really implementing programs. +} + +int AWConsolidatedAudioProcessor::getCurrentProgram() { return 0; } + +void AWConsolidatedAudioProcessor::setCurrentProgram(int index) {} + +const juce::String AWConsolidatedAudioProcessor::getProgramName(int index) +{ + return "Default " + std::to_string(index); +} + +void AWConsolidatedAudioProcessor::changeProgramName(int index, const juce::String &newName) {} + +//============================================================================== +void AWConsolidatedAudioProcessor::prepareToPlay(double sr, int samplesPerBlock) +{ + Airwin2RackBase::defaultSampleRate = sr; + if (awProcessor) + awProcessor->setSampleRate(sr); +} + +void AWConsolidatedAudioProcessor::releaseResources() +{ + // When playback stops, you can use this as an opportunity to free up any + // spare memory, etc. +} + +bool AWConsolidatedAudioProcessor::isBusesLayoutSupported(const BusesLayout &layouts) const +{ + bool inputValid = layouts.getMainInputChannelSet() == juce::AudioChannelSet::stereo(); + + bool outputValid = layouts.getMainOutputChannelSet() == juce::AudioChannelSet::mono(); + + return inputValid && outputValid; +} + +void AWConsolidatedAudioProcessor::processBlock(juce::AudioBuffer &buffer, + juce::MidiBuffer &midiMessages) +{ + juce::ScopedNoDenormals noDenormals; + + ResetTypeMsg item; + while (resetType.pop(item)) + { + if (item.type == -1) + { + setAWProcessorTo(item.toIndex, false); + } + } + + if (!awProcessor) + return; + + for (int i = 0; i < nProcessorParams; ++i) + { + awProcessor->setParameter(i, fxParams[i]->get()); + } + + awProcessor->processReplacing((float **)buffer.getArrayOfReadPointers(), + (float **)buffer.getArrayOfWritePointers(), + buffer.getNumSamples()); +} + +//============================================================================== +bool AWConsolidatedAudioProcessor::hasEditor() const +{ + return true; // (change this to false if you choose to not supply an editor) +} + +juce::AudioProcessorEditor *AWConsolidatedAudioProcessor::createEditor() +{ + return new AWConsolidatedAudioProcessorEditor(*this); +} + +void AWConsolidatedAudioProcessor::parameterValueChanged(int parameterIndex, float newValue) +{ + refreshUI = true; +} + +void AWConsolidatedAudioProcessor::setAWProcessorTo(int registryIndex, bool initDisplay) +{ + curentProcessorIndex = registryIndex; + auto rg = AirwinRegistry::registry[registryIndex]; + + awProcessor = rg.generator(); + if (awProcessor) + { + awProcessor->setSampleRate(getSampleRate()); + nProcessorParams = rg.nParams; + for (int i = 0; i < rg.nParams; ++i) + { + char txt[kVstMaxParamStrLen]; + awProcessor->getParameterName(i, txt); + fxParams[i]->mutableName = txt; + fxParams[i]->setValueNotifyingHost(awProcessor->getParameter(i)); + defaultValues[i] = awProcessor->getParameter(i); + active[i] = true; + } + for (int i = rg.nParams; i < nAWParams; ++i) + { + fxParams[i]->mutableName = "-"; + fxParams[i]->setValueNotifyingHost(0.f); + active[i] = false; + } + } + + if (initDisplay) + { + std::lock_guard g(displayProcessorMutex); + awDisplayProcessor = rg.generator(); + awDisplayProcessor->setSampleRate(getSampleRate()); + } + + updateHostDisplay(juce::AudioProcessor::ChangeDetails().withParameterInfoChanged(true)); + refreshUI = true; +} +//============================================================================== +void AWConsolidatedAudioProcessor::getStateInformation(juce::MemoryBlock &destData) +{ + std::unique_ptr xml(new juce::XmlElement("awconsolidated")); + xml->setAttribute("streamingVersion", (int)8524); + + xml->setAttribute("currentProcessorName", AirwinRegistry::registry[curentProcessorIndex].name); + for (int i = 0; i < nAWParams; ++i) + { + juce::String nm = juce::String("awp_") + std::to_string(i); + float val{0.f}; + if (i < nProcessorParams) + val = *(fxParams[i]); + + xml->setAttribute(nm, val); + } + + copyXmlToBinary(*xml, destData); +} + +void AWConsolidatedAudioProcessor::setStateInformation(const void *data, int sizeInBytes) +{ + std::unique_ptr xmlState(getXmlFromBinary(data, sizeInBytes)); + + if (xmlState.get() != nullptr) + { + if (xmlState->hasTagName("awconsolidated")) + { + auto streamingVersion = xmlState->getIntAttribute("streamingVersion", (int)2); + auto effectName = xmlState->getStringAttribute("currentProcessorName"); + + if (AirwinRegistry::nameToIndex.find(effectName.toStdString()) + != AirwinRegistry::nameToIndex.end()) + { + setAWProcessorTo(AirwinRegistry::nameToIndex.at(effectName.toStdString()), true); + } + + for (int i=0; igetDoubleAttribute(nm); + fxParams[i]->setValueNotifyingHost(f); + } + } + } +} + +//============================================================================== +// This creates new instances of the plugin.. +juce::AudioProcessor *JUCE_CALLTYPE createPluginFilter() +{ + return new AWConsolidatedAudioProcessor(); +} diff --git a/src-juce/AWConsolidatedProcessor.h b/src-juce/AWConsolidatedProcessor.h new file mode 100644 index 0000000..7a8dd2a --- /dev/null +++ b/src-juce/AWConsolidatedProcessor.h @@ -0,0 +1,164 @@ + +#ifndef AWCONSOLIDATED_PROCESSOR_H +#define AWCONSOLIDATED_PROCESSOR_H + +#include "AirwinRegistry.h" +#include "juce_audio_processors/juce_audio_processors.h" + +#if MAC +#include +#endif + +template +class LockFreeQueue +{ + public: + LockFreeQueue() + : fifo(Capacity) + { + } + + void push(const T& item) + { + int start1, size1, start2, size2; + fifo.prepareToWrite(1, start1, size1, start2, size2); + + if (size1 > 0) + { + buffer[start1] = item; + } + + fifo.finishedWrite(size1); + } + + bool pop(T& item) + { + int start1, size1, start2, size2; + fifo.prepareToRead(1, start1, size1, start2, size2); + + if (size1 > 0) + { + item = buffer[start1]; + } + + fifo.finishedRead(size1); + + return size1 > 0; + } + + private: + juce::AbstractFifo fifo; + std::array buffer; +}; + +//============================================================================== +/** + */ +class AWConsolidatedAudioProcessor : public juce::AudioProcessor, + public juce::AudioProcessorParameter::Listener, + public juce::AsyncUpdater +{ + public: + + static constexpr int nAWParams{10}; + + //============================================================================== + AWConsolidatedAudioProcessor(); + ~AWConsolidatedAudioProcessor(); + + //============================================================================== + void prepareToPlay(double sampleRate, int samplesPerBlock) override; + void releaseResources() override; + + bool isBusesLayoutSupported(const BusesLayout &layouts) const override; + + void processBlock(juce::AudioBuffer &, juce::MidiBuffer &) override; + + //============================================================================== + juce::AudioProcessorEditor *createEditor() override; + bool hasEditor() const override; + + //============================================================================== + const juce::String getName() const override; + + bool acceptsMidi() const override; + bool producesMidi() const override; + bool isMidiEffect() const override; + double getTailLengthSeconds() const override; + + //============================================================================== + int getNumPrograms() override; + int getCurrentProgram() override; + void setCurrentProgram(int index) override; + const juce::String getProgramName(int index) override; + void changeProgramName(int index, const juce::String &newName) override; + + //============================================================================== + void getStateInformation(juce::MemoryBlock &destData) override; + void setStateInformation(const void *data, int sizeInBytes) override; + + void parameterValueChanged(int parameterIndex, float newValue) override; + void parameterGestureChanged(int parameterIndex, bool isStarting) override {} + void handleAsyncUpdate() override {} + + struct ResetTypeMsg + { + int type; // -1 for type, otherwise param + int32_t toIndex; + float value; + }; + LockFreeQueue resetType; + + // Call this from the UI thread only + void pushResetTypeFromUI(int32_t index) + { + auto &rg = AirwinRegistry::registry[index]; + { + std::lock_guard g(displayProcessorMutex); + awDisplayProcessor = rg.generator(); + awDisplayProcessor->setSampleRate(getSampleRate()); + } + resetType.push({-1, index, 0.f}); + } + std::atomic refreshUI{false}; + + struct AWParam : public juce::AudioParameterFloat + { + juce::String mutableName; + template + AWParam(Args &&...args) : juce::AudioParameterFloat(std::forward(args)...) + { + } + + + juce::String getName(int end) const override { return mutableName.substring(0, end); } + + std::function getTextHandler{nullptr}; + std::function getTextToValue{nullptr}; + juce::String getText(float f, int i) const override { return getTextHandler(f, i); } + + float getValueForText(const juce::String &text) const override + { + return getTextToValue(text); + } + }; + + //============================================================================== + typedef AWParam float_param_t; + float_param_t *fxParams[nAWParams]; + float defaultValues[nAWParams]; + std::array, nAWParams> active; + + void setAWProcessorTo(int registryIndex, bool initDisplay); + + std::unique_ptr awProcessor, awDisplayProcessor; + std::mutex displayProcessorMutex; + int nProcessorParams{0}; + std::atomic curentProcessorIndex{0}; + + + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AWConsolidatedAudioProcessor) +}; + +#endif // SURGE_SRC_SURGE_FX_SURGEFXPROCESSOR_H diff --git a/src-juce/CMakeLists.txt b/src-juce/CMakeLists.txt new file mode 100644 index 0000000..e65464a --- /dev/null +++ b/src-juce/CMakeLists.txt @@ -0,0 +1,121 @@ +# vi:set sw=2 et: +project(airwin-consolidated VERSION ${CMAKE_PROJECT_VERSION}) + +include ("../cmake/CPM.cmake") + + +CPMAddPackage( + NAME JUCE + GITHUB_REPOSITORY juce-framework/JUCE + GIT_TAG 7.0.12 # specify the tag to a version of your choice +) + +CPMAddPackage( + NAME clap-juce-extensions + GITHUB_REPOSITORY free-audio/clap-juce-extensions + GIT_TAG main + SUBMODULE_RECURSIVE ON +) + +list(APPEND AWCO_FORMATS VST3) +list(APPEND AWCO_FORMATS AU) +list(APPEND AWCO_FORMATS Standalone) +if (UNIX AND NOT APPLE) + # I know it works on other platforms but demand is low there + list(APPEND AWCO_FORMATS LV2) +endif() + +set(AW2J_PRODUCT_NAME "Airwindows Consolidated") + +juce_add_plugin(${PROJECT_NAME} + PRODUCT_NAME ${AW2J_PRODUCT_NAME} + COMPANY_NAME "Airwindows" + COMPANY_WEBSITE "https://airwindows.com/" + BUNDLE_ID "com.airwindows.all-windows" + PLUGIN_MANUFACTURER_CODE Dthr + PLUGIN_CODE alFX + + IS_SYNTH FALSE + NEEDS_MIDI_INPUT FALSE + NEEDS_MIDI_OUTPUT FALSE + IS_MIDI_EFFECT FALSE + MICROPHONE_PERMISSION_ENABLED TRUE + MICROPHONE_PERMISSION_TEXT "AirwindowsEffects would like to use your microphone for Audio Input" + + BLUETOOTH_PERMISSION_ENABLED TRUE + BLUETOOTH_PERMISSION_TEXT "Airwindows Effects would like to use BlueTooth" + + + COPY_PLUGIN_AFTER_BUILD TRUE + + VST3_CATEGORIES Fx + AU_MAIN_TYPE kAudioUnitType_Effect + AU_SANDBOX_SAFE TR-UE + + + LV2_URI https://airwindows.com/lv2/airwindows-all + LV2_SHARED_LIBRARY_NAME AirwindowsALL + + FORMATS ${AWCO_FORMATS} + ) + +target_compile_definitions(${PROJECT_NAME} PUBLIC + JUCE_ALLOW_STATIC_NULL_VARIABLES=0 + JUCE_STRICT_REFCOUNTEDPOINTER=1 + + JUCE_VST3_CAN_REPLACE_VST2=0 + JUCE_USE_CURL=0 + JUCE_WEB_BROWSER=0 + JUCE_USE_CAMERA=disabled + + JUCE_DISPLAY_SPLASH_SCREEN=0 + JUCE_REPORT_APP_USAGE=0 + + JUCE_MODAL_LOOPS_PERMITTED=0 + + JUCE_COREGRAPHICS_DRAW_ASYNC=1 + + JUCE_ALSA=1 + JUCE_JACK=1 + + JUCE_WASAPI=1 + JUCE_DIRECTSOUND=1 + + JUCE_CATCH_UNHANDLED_EXCEPTIONS=0 +) +target_sources(${PROJECT_NAME} PRIVATE + AWConsolidatedEditor.cpp + AWConsolidatedEditor.h + AWConsolidatedProcessor.cpp + AWConsolidatedProcessor.h + ) + +target_link_libraries(${PROJECT_NAME} PRIVATE + airwin-registry + awconsolidated_resources + juce::juce_audio_utils + juce::juce_audio_processors + +) + +clap_juce_extensions_plugin(TARGET ${PROJECT_NAME} + CLAP_ID "com.airwindows.consolidated" + CLAP_PROCESS_EVENTS_RESOLUTION_SAMPLES TRUE + CLAP_FEATURES + "audio-effect" + "filter" + "phaser" + "rotary speaker" + "equalizer" + "granular" + "frequency-shifter" + "distortion" + "flanger" + "chorus" + "delay" + "reverb" + "multi-effects" + "stereo" + "free and open source") + +include(../cmake/basic-installer.cmake) \ No newline at end of file diff --git a/src/AirwinRegistry.h b/src/AirwinRegistry.h index f316925..74b9dec 100644 --- a/src/AirwinRegistry.h +++ b/src/AirwinRegistry.h @@ -12,11 +12,14 @@ #include #include #include -#include "airwin2rackbase.h" +#include #include -#include #include #include +#include +#include + +#include "airwin2rackbase.h" struct AirwinRegistry { diff --git a/src/Module.cpp b/src/Module.cpp index 72d1cef..f8e7ade 100644 --- a/src/Module.cpp +++ b/src/Module.cpp @@ -23,7 +23,6 @@ // @TODO: Cloud perlin ala Steve - #define MAX_POLY 16 struct AW2RModule : virtual rack::Module, sst::rackhelpers::module_connector::NeighborConnectable_V1 @@ -113,17 +112,19 @@ struct AW2RModule : virtual rack::Module, sst::rackhelpers::module_connector::Ne enum PolyphonyMode { MONOPHONIC, - POLYPHONIC, // the i/o is are LLL... RRR... - POLYPHONIC_MIXMASTER, // the i/o is LRLRLR ... LRLRLR - MIXMASTER_TO_MONOPHONIC, // in is LRLR...LRLRL summed to mono + POLYPHONIC, // the i/o is are LLL... RRR... + POLYPHONIC_MIXMASTER, // the i/o is LRLRLR ... LRLRLR + MIXMASTER_TO_MONOPHONIC, // in is LRLR...LRLRL summed to mono MIXMASTER_TO_STEREO_POLY, // in is LRLRLR LRLRLRL out is LLLL RRRR - STEREO_POLY_TO_MIXMASTER // in is LLL RRR out is LRLR LRLR + STEREO_POLY_TO_MIXMASTER // in is LLL RRR out is LRLR LRLR }; std::atomic polyphonyMode{MONOPHONIC}; std::atomic lockedType{false}, randomizeFX{false}; AW2RModule() { + Airwin2RackBase::defaultSampleRate = APP->engine->getSampleRate(); + assert(!AirwinRegistry::registry.empty()); config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); @@ -179,7 +180,10 @@ struct AW2RModule : virtual rack::Module, sst::rackhelpers::module_connector::Ne selectedCat = AirwinRegistry::registry[registryIdx].category; selectedWhat = AirwinRegistry::registry[registryIdx].whatText; + Airwin2RackBase::defaultSampleRate = APP->engine->getSampleRate(); + airwin_display = AirwinRegistry::registry[registryIdx].generator(); + airwin_display->setSampleRate(APP->engine->getSampleRate()); if (polyphonyMode != MONOPHONIC && polyphonyMode != MIXMASTER_TO_MONOPHONIC) { @@ -187,11 +191,14 @@ struct AW2RModule : virtual rack::Module, sst::rackhelpers::module_connector::Ne for (int i = 0; i < MAX_POLY; ++i) { poly_airwin[i] = AirwinRegistry::registry[registryIdx].generator(); + poly_airwin[i]->setSampleRate(APP->engine->getSampleRate()); } } else { airwin = AirwinRegistry::registry[registryIdx].generator(); + airwin->setSampleRate(APP->engine->getSampleRate()); + for (auto &aw : poly_airwin) aw.reset(nullptr); } @@ -219,6 +226,25 @@ struct AW2RModule : virtual rack::Module, sst::rackhelpers::module_connector::Ne polyIO[c].reset(); resetCount++; + + updateSampleRates(); + } + + void updateSampleRates() + { + auto sr = APP->engine->getSampleRate(); + Airwin2RackBase::defaultSampleRate = sr; + airwin_display->setSampleRate(sr); + if (airwin) + airwin->setSampleRate(sr); + for (auto &p : poly_airwin) + if (p) + p->setSampleRate(sr); + } + + void onSampleRateChange(const SampleRateChangeEvent &e) override + { + updateSampleRates(); } json_t *dataToJson() override @@ -238,7 +264,8 @@ struct AW2RModule : virtual rack::Module, sst::rackhelpers::module_connector::Ne { namespace jh = sst::rackhelpers::json; - resetAirwinByName(jh::jsonSafeGet(rootJ, "airwindowSelectedFX").value_or("Galactic"), false); + resetAirwinByName( + jh::jsonSafeGet(rootJ, "airwindowSelectedFX").value_or("Galactic"), false); lockedType = jh::jsonSafeGet(rootJ, "lockedType").value_or(false); bool oldPoly = jh::jsonSafeGet(rootJ, "polyphonic").value_or(false); @@ -337,7 +364,7 @@ struct AW2RModule : virtual rack::Module, sst::rackhelpers::module_connector::Ne resetAirwinByName(selectedFX, false); } - switch(polyphonyMode) + switch (polyphonyMode) { case MONOPHONIC: processMono(args, false); @@ -366,12 +393,12 @@ struct AW2RModule : virtual rack::Module, sst::rackhelpers::module_connector::Ne { monoIO.in[0][monoIO.inPos] = 0; monoIO.in[1][monoIO.inPos] = 0; - for (int c=0; c(rootJ, "defaultSkin").value_or(DARK), false); - menuOrdering = (MenuOrdering)sst::rackhelpers::json::jsonSafeGet(rootJ, "defaultMenuOrdering").value_or(ALPHA); + changeTo( + (Skin)sst::rackhelpers::json::jsonSafeGet(rootJ, "defaultSkin").value_or(DARK), + false); + menuOrdering = + (MenuOrdering)sst::rackhelpers::json::jsonSafeGet(rootJ, "defaultMenuOrdering") + .value_or(ALPHA); } } @@ -570,8 +605,7 @@ struct AWSkin readConfig(); } - template - T dl(const T &dark, const T &light) + template T dl(const T &dark, const T &light) { if (skin == DARK) return dark; @@ -607,29 +641,29 @@ struct AWSkin COL(selectorCategory, nvgRGB(210, 210, 210), nvgRGB(210, 210, 210)); COL(selectorPoly, nvgRGB(140, 140, 140), nvgRGB(140, 140, 140)); - COL(helpBorder, nvgRGB(180,180,180), nvgRGB(180,180,180)); - COL(helpBG, nvgRGB(20,20,20), nvgRGB(20,20,20)); - COL(helpText, nvgRGB(220,220,225), nvgRGB(220,220,225)); + COL(helpBorder, nvgRGB(180, 180, 180), nvgRGB(180, 180, 180)); + COL(helpBG, nvgRGB(20, 20, 20), nvgRGB(20, 20, 20)); + COL(helpText, nvgRGB(220, 220, 225), nvgRGB(220, 220, 225)); - COL(panelGradientStart, nvgRGB(50,50,60), nvgRGB(225,225,230)); - COL(panelGradientEnd, nvgRGB(70,70,75), nvgRGB(235,235,245)); + COL(panelGradientStart, nvgRGB(50, 50, 60), nvgRGB(225, 225, 230)); + COL(panelGradientEnd, nvgRGB(70, 70, 75), nvgRGB(235, 235, 245)); - COL(panelBottomRegion, nvgRGB(160,160,170), nvgRGB(160,160,170)); - COL(panelBottomStroke, nvgRGB(0,0,0), nvgRGB(0,0,0)); + COL(panelBottomRegion, nvgRGB(160, 160, 170), nvgRGB(160, 160, 170)); + COL(panelBottomStroke, nvgRGB(0, 0, 0), nvgRGB(0, 0, 0)); - COL(panelInputFill, nvgRGB(190,190,200), nvgRGB(190,190,200)); - COL(panelInputBorder, nvgRGB(140,140,150), nvgRGB(140,140,150)); - COL(panelInputText, nvgRGB(40,40,50), nvgRGB(40,40,50)); + COL(panelInputFill, nvgRGB(190, 190, 200), nvgRGB(190, 190, 200)); + COL(panelInputBorder, nvgRGB(140, 140, 150), nvgRGB(140, 140, 150)); + COL(panelInputText, nvgRGB(40, 40, 50), nvgRGB(40, 40, 50)); - COL(panelOutputFill,nvgRGB(60,60,70), nvgRGB(60,60,70)); - COL(panelOutputBorder, nvgRGB(40,40,50), nvgRGB(40,40,50)); - COL(panelOutputText, nvgRGB(190,190,200), nvgRGB(190,190,200)); + COL(panelOutputFill, nvgRGB(60, 60, 70), nvgRGB(60, 60, 70)); + COL(panelOutputBorder, nvgRGB(40, 40, 50), nvgRGB(40, 40, 50)); + COL(panelOutputText, nvgRGB(190, 190, 200), nvgRGB(190, 190, 200)); - COL(panelBrandText, nvgRGB(0,0,0), nvgRGB(0,0,0)); + COL(panelBrandText, nvgRGB(0, 0, 0), nvgRGB(0, 0, 0)); - float svgAlpha() { return dl(0.73, 0.23);} + float svgAlpha() { return dl(0.73, 0.23); } - COL(moduleOutline, nvgRGB(100,100,100), nvgRGB(100,100,100)); + COL(moduleOutline, nvgRGB(100, 100, 100), nvgRGB(100, 100, 100)); }; AWSkin awSkin; @@ -713,8 +747,8 @@ template struct PixelKnob : rack::Knob minAngle = -M_PI * (180 - angleSpreadDegrees) / 180; maxAngle = M_PI * (180 - angleSpreadDegrees) / 180; - bdw = new sst::rackhelpers::ui::BufferedDrawFunctionWidget(rack::Vec(0, 0), box.size, - [this](auto vg) { drawKnob(vg); }); + bdw = new sst::rackhelpers::ui::BufferedDrawFunctionWidget( + rack::Vec(0, 0), box.size, [this](auto vg) { drawKnob(vg); }); addChild(bdw); } @@ -816,8 +850,8 @@ struct AWLabel : rack::Widget AWLabel() {} void setup() { - bdw = new sst::rackhelpers::ui::BufferedDrawFunctionWidget(rack::Vec(0, 0), box.size, - [this](auto vg) { drawLabel(vg); }); + bdw = new sst::rackhelpers::ui::BufferedDrawFunctionWidget( + rack::Vec(0, 0), box.size, [this](auto vg) { drawLabel(vg); }); addChild(bdw); } void drawLabel(NVGcontext *vg) @@ -867,8 +901,8 @@ struct AWJog : rack::Widget sst::rackhelpers::ui::BufferedDrawFunctionWidget *bdw{nullptr}; void setup() { - bdw = new sst::rackhelpers::ui::BufferedDrawFunctionWidget(rack::Vec(0, 0), box.size, - [this](auto vg) { drawArrow(vg); }); + bdw = new sst::rackhelpers::ui::BufferedDrawFunctionWidget( + rack::Vec(0, 0), box.size, [this](auto vg) { drawArrow(vg); }); addChild(bdw); } void drawArrow(NVGcontext *vg) @@ -997,8 +1031,8 @@ struct AWHelp : rack::Widget sst::rackhelpers::ui::BufferedDrawFunctionWidget *bdw{nullptr}; void setup() { - bdw = new sst::rackhelpers::ui::BufferedDrawFunctionWidget(rack::Vec(0, 0), box.size, - [this](auto vg) { drawHelp(vg); }); + bdw = new sst::rackhelpers::ui::BufferedDrawFunctionWidget( + rack::Vec(0, 0), box.size, [this](auto vg) { drawHelp(vg); }); addChild(bdw); } void drawHelp(NVGcontext *vg) @@ -1056,8 +1090,8 @@ struct AWSelector : rack::Widget static constexpr int jogButttonSize{12}; void setup() { - bdw = new sst::rackhelpers::ui::BufferedDrawFunctionWidget(rack::Vec(0, 0), box.size, - [this](auto vg) { drawSelector(vg); }); + bdw = new sst::rackhelpers::ui::BufferedDrawFunctionWidget( + rack::Vec(0, 0), box.size, [this](auto vg) { drawSelector(vg); }); addChild(bdw); auto asz{jogButttonSize - 1}; @@ -1153,8 +1187,9 @@ struct AWSelector : rack::Widget nvgFontSize(vg, 8.5); nvgText(vg, box.size.x * 0.5, box.size.y * 0.22, lastCat.c_str(), nullptr); - if (lastPoly == AW2RModule::POLYPHONIC || lastPoly == AW2RModule::POLYPHONIC_MIXMASTER - || lastPoly == AW2RModule::MIXMASTER_TO_STEREO_POLY || lastPoly == AW2RModule::STEREO_POLY_TO_MIXMASTER) + if (lastPoly == AW2RModule::POLYPHONIC || lastPoly == AW2RModule::POLYPHONIC_MIXMASTER || + lastPoly == AW2RModule::MIXMASTER_TO_STEREO_POLY || + lastPoly == AW2RModule::STEREO_POLY_TO_MIXMASTER) { nvgBeginPath(vg); nvgFillColor(vg, awSkin.selectorPoly()); @@ -1270,7 +1305,8 @@ struct AWSelector : rack::Widget } if (ct > maxEntries) { - m->addChild(rack::createMenuLabel(std::to_string(result.size()-maxEntries) + " more matches...")); + m->addChild(rack::createMenuLabel(std::to_string(result.size() - maxEntries) + + " more matches...")); } } } @@ -1296,11 +1332,12 @@ struct AWSelector : rack::Widget CHECKMARK(module->randomizeFX), [this]() { module->randomizeFX = !module->randomizeFX; })); m->addChild(new rack::MenuSeparator); - m->addChild(rack::createMenuItem("Categories in Alphabetical Order", CHECKMARK(awSkin.menuOrdering == AWSkin::ALPHA), - []() { awSkin.changeOrderingTo(AWSkin::ALPHA); })); - m->addChild(rack::createMenuItem("Categories in 'Chris' (quality) Order", CHECKMARK(awSkin.menuOrdering == AWSkin::CHRIS), - []() { awSkin.changeOrderingTo(AWSkin::CHRIS); })); - + m->addChild(rack::createMenuItem("Categories in Alphabetical Order", + CHECKMARK(awSkin.menuOrdering == AWSkin::ALPHA), + []() { awSkin.changeOrderingTo(AWSkin::ALPHA); })); + m->addChild(rack::createMenuItem("Categories in 'Chris' (quality) Order", + CHECKMARK(awSkin.menuOrdering == AWSkin::CHRIS), + []() { awSkin.changeOrderingTo(AWSkin::CHRIS); })); } void createCategoryMenu(rack::Menu *m, const std::string &cat) { @@ -1325,7 +1362,7 @@ struct AWSelector : rack::Widget const auto &name = r.name; auto checked = name == module->selectedFX; auto mi = rack::createMenuItem(name, CHECKMARK(checked), - [this, i = ridx]() { pushFXChange(module, i); }); + [this, i = ridx]() { pushFXChange(module, i); }); mi->disabled = module->lockedType; m->addChild(mi); } @@ -1442,7 +1479,7 @@ struct AW2RModuleWidget : rack::ModuleWidget clipperSvg = rack::Svg::load(rack::asset::plugin(pluginInstance, "res/clipper.svg")); bg = new sst::rackhelpers::ui::BufferedDrawFunctionWidget(rack::Vec(0, 0), box.size, - [this](auto vg) { drawBG(vg); }); + [this](auto vg) { drawBG(vg); }); bg->box.pos = rack::Vec(0.0); bg->box.size = box.size; addChild(bg); @@ -1570,36 +1607,45 @@ struct AW2RModuleWidget : rack::ModuleWidget if (awm) { menu->addChild(new rack::MenuSeparator); - menu->addChild(rack::createMenuItem("Monophonic (Sum Inputs)", CHECKMARK(awm->polyphonyMode == AW2RModule::MONOPHONIC), - [awm, this]() { - awm->stagePolyReset(AW2RModule::MONOPHONIC); - bg->dirty = true; - })); - menu->addChild(rack::createMenuItem("Monophonic (MixMaster Sum Input)", CHECKMARK(awm->polyphonyMode == AW2RModule::MIXMASTER_TO_MONOPHONIC), - [awm, this]() { - awm->stagePolyReset(AW2RModule::MIXMASTER_TO_MONOPHONIC); - bg->dirty = true; - })); - menu->addChild(rack::createMenuItem("Polyphonic (Stereo to Stereo)", CHECKMARK(awm->polyphonyMode == AW2RModule::POLYPHONIC), - [awm, this]() { - awm->stagePolyReset(AW2RModule::POLYPHONIC); - bg->dirty = true; - })); - menu->addChild(rack::createMenuItem("Polyphonic (MixMaster to MixMaster)", CHECKMARK(awm->polyphonyMode == AW2RModule::POLYPHONIC_MIXMASTER), - [awm, this]() { - awm->stagePolyReset(AW2RModule::POLYPHONIC_MIXMASTER); - bg->dirty = true; - })); - menu->addChild(rack::createMenuItem("Polyphonic (MixMaster to Stereo)", CHECKMARK(awm->polyphonyMode == AW2RModule::MIXMASTER_TO_STEREO_POLY), - [awm, this]() { - awm->stagePolyReset(AW2RModule::MIXMASTER_TO_STEREO_POLY); - bg->dirty = true; - })); - menu->addChild(rack::createMenuItem("Polyphonic (Stereo to MixMaster)", CHECKMARK(awm->polyphonyMode == AW2RModule::STEREO_POLY_TO_MIXMASTER), - [awm, this]() { - awm->stagePolyReset(AW2RModule::STEREO_POLY_TO_MIXMASTER); - bg->dirty = true; - })); + menu->addChild(rack::createMenuItem( + "Monophonic (Sum Inputs)", CHECKMARK(awm->polyphonyMode == AW2RModule::MONOPHONIC), + [awm, this]() { + awm->stagePolyReset(AW2RModule::MONOPHONIC); + bg->dirty = true; + })); + menu->addChild(rack::createMenuItem( + "Monophonic (MixMaster Sum Input)", + CHECKMARK(awm->polyphonyMode == AW2RModule::MIXMASTER_TO_MONOPHONIC), + [awm, this]() { + awm->stagePolyReset(AW2RModule::MIXMASTER_TO_MONOPHONIC); + bg->dirty = true; + })); + menu->addChild(rack::createMenuItem( + "Polyphonic (Stereo to Stereo)", + CHECKMARK(awm->polyphonyMode == AW2RModule::POLYPHONIC), [awm, this]() { + awm->stagePolyReset(AW2RModule::POLYPHONIC); + bg->dirty = true; + })); + menu->addChild(rack::createMenuItem( + "Polyphonic (MixMaster to MixMaster)", + CHECKMARK(awm->polyphonyMode == AW2RModule::POLYPHONIC_MIXMASTER), [awm, this]() { + awm->stagePolyReset(AW2RModule::POLYPHONIC_MIXMASTER); + bg->dirty = true; + })); + menu->addChild(rack::createMenuItem( + "Polyphonic (MixMaster to Stereo)", + CHECKMARK(awm->polyphonyMode == AW2RModule::MIXMASTER_TO_STEREO_POLY), + [awm, this]() { + awm->stagePolyReset(AW2RModule::MIXMASTER_TO_STEREO_POLY); + bg->dirty = true; + })); + menu->addChild(rack::createMenuItem( + "Polyphonic (Stereo to MixMaster)", + CHECKMARK(awm->polyphonyMode == AW2RModule::STEREO_POLY_TO_MIXMASTER), + [awm, this]() { + awm->stagePolyReset(AW2RModule::STEREO_POLY_TO_MIXMASTER); + bg->dirty = true; + })); menu->addChild(new rack::MenuSeparator); auto s = "Block Size (" + std::to_string(awm->blockSize) + ")"; @@ -1621,11 +1667,12 @@ struct AW2RModuleWidget : rack::ModuleWidget } menu->addChild(new rack::MenuSeparator); - menu->addChild(rack::createMenuItem("Categories in Alphabetical Order", CHECKMARK(awSkin.menuOrdering == AWSkin::ALPHA), + menu->addChild(rack::createMenuItem("Categories in Alphabetical Order", + CHECKMARK(awSkin.menuOrdering == AWSkin::ALPHA), []() { awSkin.changeOrderingTo(AWSkin::ALPHA); })); - menu->addChild(rack::createMenuItem("Categories in 'Chris' (quality) Order", CHECKMARK(awSkin.menuOrdering == AWSkin::CHRIS), + menu->addChild(rack::createMenuItem("Categories in 'Chris' (quality) Order", + CHECKMARK(awSkin.menuOrdering == AWSkin::CHRIS), []() { awSkin.changeOrderingTo(AWSkin::CHRIS); })); - } struct HelpWidget : rack::Widget @@ -1672,15 +1719,16 @@ struct AW2RModuleWidget : rack::ModuleWidget box.size.x / APP->scene->rackScroll->getZoom() - 2 * (margin + 2), hw->helpText.c_str(), nullptr); nvgTextBoxBounds(vg, margin + 2, bnd[3] + margin + 2, - box.size.x / APP->scene->rackScroll->getZoom() - 2 * (margin + 2), - hw->helpText.c_str(), nullptr, bnd); + box.size.x / APP->scene->rackScroll->getZoom() - 2 * (margin + 2), + hw->helpText.c_str(), nullptr, bnd); nvgRestore(vg); box.size.y = bnd[3]; } } *render{nullptr}; - void setup() { + void setup() + { ctime = rack::system::getTime(); sw = new rack::ui::ScrollWidget; sw->box.pos = rack::Vec(margin, margin); @@ -1691,7 +1739,7 @@ struct AW2RModuleWidget : rack::ModuleWidget render = new Render(); render->hw = this; - render->box.pos = rack::Vec(0,0); + render->box.pos = rack::Vec(0, 0); render->box.size = sw->box.size; sw->container->addChild(render); } @@ -1768,7 +1816,8 @@ struct AW2RModuleWidget : rack::ModuleWidget resetBounds(); } - void resetBounds() { + void resetBounds() + { sw->box.pos = rack::Vec(margin, margin); sw->box.size = box.size; sw->box.size.x -= 2 * margin; @@ -1867,8 +1916,7 @@ struct AW2RModuleWidget : rack::ModuleWidget // use nextPoly here since audio thread may have not swept it yet if (!awm || ((awm->nextPoly != AW2RModule::POLYPHONIC_MIXMASTER) && (awm->nextPoly != AW2RModule::MIXMASTER_TO_MONOPHONIC) && - (awm->nextPoly != AW2RModule::MIXMASTER_TO_STEREO_POLY)) - ) + (awm->nextPoly != AW2RModule::MIXMASTER_TO_STEREO_POLY))) { nvgFontSize(vg, 10); nvgText(vg, box.size.x * 0.25 - dc, box.size.y - cutPoint + 38, "L", nullptr); @@ -1895,8 +1943,7 @@ struct AW2RModuleWidget : rack::ModuleWidget nvgTextAlign(vg, NVG_ALIGN_BOTTOM | NVG_ALIGN_CENTER); nvgFontFaceId(vg, fid); if (!awm || ((awm->nextPoly != AW2RModule::POLYPHONIC_MIXMASTER) && - (awm->nextPoly != AW2RModule::STEREO_POLY_TO_MIXMASTER)) - ) + (awm->nextPoly != AW2RModule::STEREO_POLY_TO_MIXMASTER))) { nvgFontSize(vg, 10); nvgText(vg, box.size.x * 0.75, box.size.y - cutPoint + 38, "OUT", nullptr); @@ -1926,7 +1973,7 @@ struct AW2RModuleWidget : rack::ModuleWidget float t[6]; nvgTranslate(vg, 0, 269); nvgScale(vg, 0.14, 0.14); - nvgAlpha(vg,awSkin.svgAlpha()); + nvgAlpha(vg, awSkin.svgAlpha()); clipperSvg->draw(vg); nvgRestore(vg); } diff --git a/src/airwin2rackbase.cpp b/src/airwin2rackbase.cpp index 9b5d110..190bbac 100644 --- a/src/airwin2rackbase.cpp +++ b/src/airwin2rackbase.cpp @@ -7,8 +7,11 @@ */ #include "airwin2rackbase.h" +#include #include +float Airwin2RackBase::defaultSampleRate{0.f}; + bool string2float(const char *txt, float &f) { try { diff --git a/src/airwin2rackbase.h b/src/airwin2rackbase.h index ebee3ff..f33ac8c 100644 --- a/src/airwin2rackbase.h +++ b/src/airwin2rackbase.h @@ -10,7 +10,10 @@ #define AIRWIN2_RACK_AIRWIN2RACKBASE_H #include -#include +#include +#include +#include +#include typedef int32_t audioMasterCallback; @@ -26,7 +29,13 @@ struct Airwin2RackBase { Airwin2RackBase(audioMasterCallback m, int, int) {} virtual ~Airwin2RackBase() = default; - float getSampleRate() { return APP->engine->getSampleRate(); } + static float defaultSampleRate; + float sampleRate{defaultSampleRate}; + void setSampleRate(float sr) { sampleRate = sr; } + float getSampleRate() { + assert(sampleRate > 2000); + return sampleRate; + } virtual bool getEffectName(char *name) { return false; } virtual void setNumInputs(int) {}