Reorganize top-level directories with clearer naming convention

app -> app-desktop-macos, presets -> app-presets, server -> remote-server,
daw-config-reaper -> osc-config-daw, plugin-reaper-realearn -> plugin-reaper-relearn.
Updated run.py and presets.py path references accordingly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Paul Lipscomb
2026-07-15 17:51:05 -04:00
parent e58f06d9fa
commit 7ecc718f5d
2256 changed files with 11 additions and 5 deletions
+236
View File
@@ -0,0 +1,236 @@
import {createSVGWindow} from "svgdom";
import svgjs from "@svgdotjs/svg.js";
const {SVG, registerWindow} = svgjs;
import * as fs from 'fs';
import * as path from 'path';
import pako from "pako";
import got from "got";
import '@svgdotjs/svg.topath.js';
const script_dir = path.resolve('doc/svg-gen');
// We need to embed the CSS into the SVG, otherwise the browser won't load it, tried it.
// (see https://stackoverflow.com/questions/18434094/how-to-style-svg-with-external-css).
const stylesheet = fs.readFileSync(path.resolve(script_dir, 'styles.css'));
generate()
async function generate() {
fs.writeFileSync('doc/architecture/images/modules.svg', await generateModulesDiagram())
fs.writeFileSync('doc/architecture/images/onion-layers.svg', generateOnionLayersDiagram())
fs.writeFileSync('doc/architecture/images/components.svg', await generateComponentDiagram())
fs.writeFileSync('doc/architecture/images/components-midi-fx.svg', await generateComponentDiagram([
'role-realtime-general',
'role-realtime-midi-general',
'role-realtime-midi-fx'
]))
fs.writeFileSync('doc/architecture/images/components-midi-device.svg', await generateComponentDiagram([
'role-realtime-general',
'role-realtime-midi-general',
'role-realtime-midi-device'
]))
fs.writeFileSync('doc/architecture/images/components-osc.svg', await generateComponentDiagram([
'role-realtime-general',
'role-realtime-osc',
]))
fs.writeFileSync('doc/architecture/images/components-management.svg', await generateComponentDiagram([
'role-sync-data',
'role-notify',
]))
}
async function generateModulesDiagram() {
const draw = await loadDotToSvgCanvas('modules.dot');
return draw.svg();
}
async function generateComponentDiagram(shownEdgeClasses = undefined) {
const draw = await loadDotToSvgCanvas('components.dot');
if (shownEdgeClasses) {
draw.style('.edge', {visibility: 'hidden'})
for (let edgeClass of shownEdgeClasses) {
draw.style(`.edge.${edgeClass}`, {visibility: 'visible'})
}
}
return draw.svg();
}
function generateOnionLayersDiagram() {
const draw = initSvgCanvas();
const width = 410;
const height = 410;
draw.size(width, height);
embedStylesheet(draw);
// Default attributes
const defaultFontSize = 15;
const defaultArrowColor = 'black';
const defaultArrowWidth = 2;
const baseFont = {
size: defaultFontSize,
}
const arrowPattern = createArrowPattern('rule-arrow');
const defaultArrowHead = arrowHead();
// Layers
const infrastructureLayer = layer(4, 'layer-infrastructure', 'infrastructure', [
'GUI',
'API',
'Persistence',
'Server',
]);
layer(3, 'layer-management', 'management');
layer(2, 'layer-processing', 'processing');
const baseLayer = layer(1, 'layer-base', 'base');
// Arrows
drawArrow(infrastructureLayer.x(), infrastructureLayer.cy(), baseLayer.cx(), baseLayer.cy(), {
text: 'may use code in',
patternOrColor: arrowPattern,
width: 10,
drawHead: false,
cssClass: 'rule-arrow',
useClipping: true,
});
function arrowHead() {
return draw.marker(10, 7, (add) => {
add.polygon('0,0 10,3.5 0,7').addClass('arrow-head');
});
}
function arcPath(radius, cx, cy, sweep) {
return [
// Start at 9
['M', cx - radius, cy],
// Go to 3
['A', -radius, -radius, 0, 0, sweep, cx + radius, cy],
];
}
function layer(index, cssClass, label, components = []) {
const g = draw.group().addClass(cssClass);
const spacing = 50;
const radius = index * spacing;
const circle = g
.circle(radius * 2)
.center(width / 2, height / 2)
.fill('none')
.stroke({color: 'black'})
.addClass('layerpart-circle')
const pathRadius = radius - spacing / 2;
const radiusFix = defaultFontSize / 3;
const upperArc = arcPath(
pathRadius - radiusFix,
circle.cx(),
circle.cy(),
1
);
g.textPath(label, upperArc)
.attr('text-anchor', 'middle')
.attr('startOffset', '50%')
.font(baseFont)
.addClass('layerpart-label')
const lowerArc = arcPath(
pathRadius + radiusFix,
circle.cx(),
circle.cy(),
0
);
for (let i = 0; i < components.length; i++) {
const segmentLength = (1 / components.length) * 100;
const offset = i * segmentLength + segmentLength / 2;
g.textPath(components[i], lowerArc)
.attr('text-anchor', 'middle')
.attr('startOffset', `${offset}%`)
.font(baseFont)
.addClass('layerpart-secondary-label');
}
return circle;
}
function createArrowPattern(cssClass = undefined) {
return draw.pattern(30, 20, (add) => {
const g = add.group().addClass(cssClass);
g.line(0, 3.5, 10, 3.5)
.stroke({color: defaultArrowColor, width: 1})
.addClass('arrow-pattern-path');
g.polygon('10,0 20,3.5 10,7').addClass('arrow-pattern-head');
});
}
function drawArrow(x1, y1, x2, y2, {
patternOrColor = defaultArrowColor,
width = defaultArrowWidth,
head = defaultArrowHead,
text,
drawHead = true,
cssClass = undefined,
useClipping = false,
}) {
// Group for clipping
const outer_group = draw.group().addClass(cssClass);
const inner_group = outer_group.group();
// Arrow itself
const line = useClipping ? inner_group.line(x1 - 100, y1, x2 + 100, y2) : inner_group.line(x1, y1, x2, y2);
const path = line.toPath().addClass('arrow-path');
path.stroke({color: patternOrColor, width});
// Head
if (drawHead) {
path.marker('end', head.size(8, 8))
}
// Text
outer_group.textPath()
.plot(path.array())
.text(add => {
add.tspan(text).dy(-10)
})
.addClass('arrowpart-label')
.font({...baseFont, anchor: 'middle', startOffset: '50%'});
// Clip (useful for keeping CSS transform animation within bounds)
if (useClipping) {
const clip = inner_group.clip()
.add(inner_group.polygon().plot([[x1, y1 - width], [x2, y2 - width], [x2, y1 + width], [12, y2 + width]]));
inner_group.clipWith(clip);
}
return outer_group;
}
return draw.svg();
}
async function loadDotToSvgCanvas(dotFileName) {
const dot = fs.readFileSync(path.resolve(script_dir, dotFileName));
const svg = await convertDotToSvg(dot);
const qualifiedSvg = svg.replace(/<(!DOCTYPE[^>\[]+(\[[^\]]+)?)+[^>]+>/g, '');
const window = createSVGWindow()
const document = window.document
registerWindow(window, document)
const originalDraw = SVG(document.documentElement);
originalDraw.svg(qualifiedSvg, true);
const draw = SVG(document.documentElement);
embedStylesheet(draw);
return draw;
}
async function convertDotToSvg(dot) {
const data = Buffer.from(dot, 'utf8')
const compressed = pako.deflate(data, {level: 9})
const body = Buffer.from(compressed)
.toString('base64')
.replace(/\+/g, '-').replace(/\//g, '_')
const response = await got(`https://kroki.io/graphviz/svg/${body}`);
return response.body
}
function initSvgCanvas() {
const window = createSVGWindow()
const document = window.document
registerWindow(window, document)
return SVG(document.documentElement);
}
function embedStylesheet(draw) {
draw.element('style').words(stylesheet)
}