gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

commit 2f3fdcd869447625584141613a1e8fda910e9e19
parent 282a3d895989fea84b91b98ed5f677ac4a3c5a79
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Sat,  7 Dec 2024 14:53:37 +0100

add changelog generator

Diffstat:
Adoc/changelog_split/.gitignore | 1+
Msource/CMakeLists.txt | 4++++
Asource/commandlineGenerator/CMakeLists.txt | 14++++++++++++++
Asource/commandlineGenerator/commandlineGenerator.cpp | 366+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 385 insertions(+), 0 deletions(-)

diff --git a/doc/changelog_split/.gitignore b/doc/changelog_split/.gitignore @@ -0,0 +1 @@ +*.txt diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt @@ -18,6 +18,10 @@ add_custom_target(cmakeScripts SOURCES skins.h.in ../base.cmake) +# ----------------- Tools + +add_subdirectory(commandlineGenerator) + # ----------------- DSP56300 emulator set(ASMJIT_STATIC TRUE) diff --git a/source/commandlineGenerator/CMakeLists.txt b/source/commandlineGenerator/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.10) + +project(commandlineGenerator) + +add_executable(commandlineGenerator) + +set(SOURCES + commandlineGenerator.cpp +) + +target_sources(commandlineGenerator PRIVATE ${SOURCES}) +source_group("source" FILES ${SOURCES}) + +target_link_libraries(commandlineGenerator PUBLIC baseLib) diff --git a/source/commandlineGenerator/commandlineGenerator.cpp b/source/commandlineGenerator/commandlineGenerator.cpp @@ -0,0 +1,366 @@ +#include <fstream> +#include <iostream> +#include <ostream> +#include <vector> +#include <functional> + +#include "baseLib/commandline.h" + +using Lines = std::vector<std::string>; +using LinesPerKey = std::map<std::string, Lines>; + +namespace +{ + std::string& trim(std::string& _line) + { + auto needsTrim = [](const char _c) -> bool + { + return _c == ' ' || _c == '\t' || _c == '\n' || _c == '\r'; + }; + + while (!_line.empty()) + { + if (needsTrim(_line.back())) + _line.pop_back(); + else if (needsTrim(_line.front())) + _line.erase(_line.begin()); + else + break; + } + return _line; + } + + Lines& trim(Lines& _lines) + { + while (_lines.front().empty()) + _lines.erase(_lines.begin()); + while (_lines.back().empty()) + _lines.pop_back(); + return _lines; + } + + std::string parseVersion(const std::string& _line) + { + const auto posA = _line.find('.'); + const auto posB = _line.find('.', posA + 1); + + if (posA == std::string::npos || posB == std::string::npos || posB < posA) + { + return {}; + } + + for (size_t i = 0; i < posA; ++i) + { + if (!std::isdigit(_line[i])) + return {}; + } + + for (size_t i = posA + 1; i < posB; ++i) + { + if (!std::isdigit(_line[i])) + return {}; + } + + for (size_t i = posA + 1; i < posB; ++i) + { + if (!std::isdigit(_line[i])) + return {}; + } + + return _line; + } + + std::string parseProduct(const std::string& _line) + { + // Needs to start with an uppercase letter A-Z + if (_line.empty() || !std::isupper(_line.front())) + return {}; + + // Needs to have : at the end + if (_line.back() != ':') + return {}; + + auto res = _line; + res.pop_back(); + return res; + } + + LinesPerKey groupBy(const Lines& _lines, const std::function<std::string(const std::string&)>& _eval) + { + Lines currentLines; + + std::map<std::string, Lines> linesPerKey; + + std::string currentKey; + + for (const auto& line : _lines) + { + const auto key = _eval(line); + if (!key.empty()) + { + if (!currentKey.empty()) + { + linesPerKey.insert({currentKey, trim(currentLines)}); + currentLines.clear(); + } + currentKey = key; + } + else if (!currentKey.empty()) + { + currentLines.push_back(line); + } + } + + if (!currentKey.empty() && !currentLines.empty()) + linesPerKey.insert({ currentKey, trim(currentLines) }); + + if (linesPerKey.empty()) + linesPerKey.insert({ "", _lines }); + return linesPerKey; + } + + Lines& fixSpacing(Lines& _lines) + { + size_t spaces = 0; + + for (auto& line : _lines) + { + if (line.empty()) + continue; + + if (line.front() == '-') + { + // lines either start with "- " or with "- [...] ", adjust spaces accordingly + auto bracketPos = line.find(']'); + if (bracketPos != std::string::npos) + spaces = bracketPos + 2; + else + spaces = 2; + } + else if (spaces) + { + for (size_t i=0; i<spaces; ++i) + line.insert(line.begin(), ' '); + } + } + return _lines; + } + + std::string fixFilename(const std::string& _filename) + { + std::string out; + for (auto& c : _filename) + { + if (c != ':') + out += c; + } + return out; + } + bool writeProduct(std::ofstream& _out, const std::string& _product, const Lines& _lines) + { + if (_lines.empty()) + return false; + + if (!_product.empty()) + { + _out << _product << ":\n"; + _out << '\n'; + } + + for (const auto& line : _lines) + _out << line << '\n'; + + return true; + } +} + +int main(const int _argc, char* _argv[]) +{ + const baseLib::CommandLine cmdLine(_argc, _argv); + + const auto inFile = cmdLine.get("i"); + + if (inFile.empty()) + { + std::cout << "No input file specified" << '\n'; + return -1; + } + + auto outPath = cmdLine.get("o"); + + if (outPath.empty()) + { + std::cout << "No output path specified" << '\n'; + return -1; + } + + if (outPath.back() != '/' && outPath.back() != '\\') + outPath.push_back('/'); + + std::ifstream file(inFile); + + if (!file.is_open()) + { + std::cout << "Failed to open input file '" << inFile << "'" << '\n'; + return -1; + } + + Lines allLines; + + while (true) + { + std::string line; + std::getline(file, line); + if (file.eof()) + break; + trim(line); + allLines.push_back(line); + } + + if (allLines.empty()) + { + std::cout << "No lines read from input file" << '\n'; + return -1; + } + + // group by version + const auto linesPerVersion = groupBy(allLines, parseVersion); + + // group by product + std::map<std::string, LinesPerKey> productPerVersion; + + for (const auto& it : linesPerVersion) + { + const auto& version = it.first; + const auto& l = it.second; + const auto linesPerProduct = groupBy(l, parseProduct); + productPerVersion.insert({ version, linesPerProduct }); + } + + // multiple products might have been specified via /, i.e. Osirus/OsTIrus, add them to two individual products + for (auto& itVersion : productPerVersion) + { + const auto& version = itVersion.first; + auto& productPerLines = itVersion.second; + + for (auto itProduct = productPerLines.begin(); itProduct != productPerLines.end();) + { + const auto& product = itProduct->first; + const auto& lines = itProduct->second; + const auto pos = product.find('/'); + + if (pos == std::string::npos) + { + ++itProduct; + continue; + } + + const auto productA = product.substr(0, pos); + const auto productB = product.substr(pos + 1); + auto& linesA = productPerVersion[version][productA]; + linesA.insert(linesA.end(), lines.begin(), lines.end()); + auto& linesB = productPerVersion[version][productB]; + linesB.insert(linesB.end(), lines.begin(), lines.end()); + itProduct = productPerLines.erase(itProduct); + } + } + + // adjust spacing for all lines + for (auto& itVersion : productPerVersion) + { + for (auto& itProduct : itVersion.second) + fixSpacing(itProduct.second); + } + + // create individual files per version and product + std::set<std::string> globalProducts = { "DSP", "Framework", "Patch Manager" }; + std::set<std::string> localProducts = { "Osirus", "OsTIrus", "Xenia", "Vavra", "NodalRed2x" }; + + for (auto& itVersion : productPerVersion) + { + const auto& version = itVersion.first; + const auto& products = itVersion.second; + + std::map<std::string, Lines> globals; + std::map<std::string, Lines> locals; + + for (const auto& itProduct : products) + { + const auto& product = itProduct.first; + + if (globalProducts.find(product) != globalProducts.end()) + globals.insert({ product, itProduct.second }); + else + locals.insert({ product, itProduct.second }); + } + + if (!globals.empty()) + { + for (const auto& localProduct : localProducts) + { + if (locals.find(localProduct) == locals.end()) + locals.insert({ localProduct, {} }); + } + } + + // write one file per product + if (locals.size() > 1) + { + for (const auto& itProduct : locals) + { + const auto& product = itProduct.first; + + const auto outName = outPath + fixFilename(version + "_" + product + ".txt"); + std::ofstream outFile(outName); + + if (!outFile.is_open()) + { + std::cout << "Failed to create output file '" << outName << '\n'; + return -1; + } + + if (!product.empty()) + outFile << product << ' '; + + outFile << "Version " << version << '\n'; + outFile << '\n'; + + for (const auto& global : globals) + { + if (writeProduct(outFile, global.first, global.second)) + outFile << '\n'; + } + + writeProduct(outFile, product, itProduct.second); + } + } + + // write one file for all products + const auto outName = outPath + fixFilename(version + ".txt"); + std::ofstream outFile(outName); + + if (!outFile.is_open()) + { + std::cout << "Failed to create output file '" << outName << '\n'; + return -1; + } + outFile << "Version " << version << '\n'; + outFile << '\n'; + for (const auto& global : globals) + { + if (writeProduct(outFile, global.first, global.second)) + outFile << '\n'; + } + + bool needsSpace = false; + + for (const auto& local : locals) + { + if (needsSpace) + outFile << '\n'; + + needsSpace = writeProduct(outFile, local.first, local.second); + } + } + return 0; +}