214 lines
6.2 KiB
TypeScript
214 lines
6.2 KiB
TypeScript
import { create } from 'xmlbuilder2';
|
|
|
|
export const TIME_SIGNATURES: { [key: string]: number } = {
|
|
'1/4': 196,
|
|
'2/4': 198,
|
|
'3/4': 200,
|
|
'4/4': 201,
|
|
'5/4': 202,
|
|
'6/4': 203,
|
|
'7/4': 204,
|
|
'8/4': 205,
|
|
'5/8': 206,
|
|
'6/8': 207,
|
|
'7/8': 208,
|
|
'9/8': 209,
|
|
'12/8': 210,
|
|
'3/2': 211,
|
|
'4/2': 212,
|
|
'5/2': 213,
|
|
'6/2': 214,
|
|
'7/2': 215,
|
|
'1/2': 216,
|
|
'1/1': 217,
|
|
'2/1': 218,
|
|
'3/1': 219,
|
|
'4/1': 220,
|
|
'5/1': 221,
|
|
'3/8': 299,
|
|
};
|
|
const MIN_ID = 22000;
|
|
const START_ID = 22000;
|
|
|
|
let _id = START_ID;
|
|
let assignedNodes = new WeakSet<object>();
|
|
const templateCache: Record<string, any> = {};
|
|
|
|
const templateMap: Record<string, () => Promise<any>> = {
|
|
midiClip: () => import('./templates/midiClip.xml?raw'),
|
|
midiTrack: () => import('./templates/midiTrack.xml?raw'),
|
|
project: () => import('./templates/project.xml?raw'),
|
|
simpler: () => import('./templates/simpler.xml?raw'),
|
|
scene: () => import('./templates/scene.xml?raw'),
|
|
groupTrack: () => import('./templates/groupTrack.xml?raw'),
|
|
drumRack: () => import('./templates/drumRack.xml?raw'),
|
|
drumBranch: () => import('./templates/drumBranch.xml?raw'),
|
|
returnTrack: () => import('./templates/returnTrack.xml?raw'),
|
|
trackSendHolder: () => import('./templates/trackSendHolder.xml?raw'),
|
|
effectReverb: () => import('./templates/effectReverb.xml?raw'),
|
|
effectDelay: () => import('./templates/effectDelay.xml?raw'),
|
|
effectChorus: () => import('./templates/effectChorus.xml?raw'),
|
|
effectDistortion: () => import('./templates/effectDistortion.xml?raw'),
|
|
effectFilter: () => import('./templates/effectFilter.xml?raw'),
|
|
effectCompressor: () => import('./templates/effectCompressor.xml?raw'),
|
|
};
|
|
|
|
const live10TemplateMap: Record<string, () => Promise<any>> = {
|
|
midiClip: () => import('./templates/live10/midiClip.xml?raw'),
|
|
midiTrack: () => import('./templates/live10/midiTrack.xml?raw'),
|
|
project: () => import('./templates/live10/project.xml?raw'),
|
|
simpler: () => import('./templates/live10/simpler.xml?raw'),
|
|
scene: () => import('./templates/live10/scene.xml?raw'),
|
|
groupTrack: () => import('./templates/live10/groupTrack.xml?raw'),
|
|
drumRack: () => import('./templates/live10/drumRack.xml?raw'),
|
|
drumBranch: () => import('./templates/live10/drumBranch.xml?raw'),
|
|
returnTrack: () => import('./templates/live10/returnTrack.xml?raw'),
|
|
trackSendHolder: () => import('./templates/live10/trackSendHolder.xml?raw'),
|
|
effectReverb: () => import('./templates/live10/effectReverb.xml?raw'),
|
|
effectDelay: () => import('./templates/live10/effectDelay.xml?raw'),
|
|
effectChorus: () => import('./templates/live10/effectChorus.xml?raw'),
|
|
effectDistortion: () => import('./templates/live10/effectDistortion.xml?raw'),
|
|
effectFilter: () => import('./templates/live10/effectFilter.xml?raw'),
|
|
effectCompressor: () => import('./templates/live10/effectCompressor.xml?raw'),
|
|
};
|
|
|
|
export async function loadTemplate<T>(
|
|
templateName: string,
|
|
abletonVersion: '10' | '11' = '11',
|
|
): Promise<T> {
|
|
const cacheKey = `${abletonVersion}:${templateName}`;
|
|
if (templateCache[cacheKey]) {
|
|
return templateCache[cacheKey];
|
|
}
|
|
|
|
const importFn = abletonVersion === '10' ? live10TemplateMap[templateName] : templateMap[templateName];
|
|
if (!importFn) {
|
|
throw new Error(`Unknown template: ${templateName}`);
|
|
}
|
|
|
|
const templateModule = await importFn();
|
|
const parsed = create(templateModule.default).toObject();
|
|
templateCache[cacheKey] = parsed;
|
|
return parsed as T;
|
|
}
|
|
|
|
export function koEnvRangeToSeconds(value: number, maxSeconds: number) {
|
|
if (value < 0 || value > 255) {
|
|
throw new RangeError('Value must be between 0 and 255');
|
|
}
|
|
return (value / 255) * maxSeconds;
|
|
}
|
|
|
|
function isPointeeTarget(key: string, idNum: number) {
|
|
return (
|
|
idNum > MIN_ID ||
|
|
key === 'AutomationTarget' ||
|
|
key === 'ModulationTarget' ||
|
|
key === 'Pointee' ||
|
|
key.endsWith('ModulationTarget') ||
|
|
key.startsWith('ControllerTargets.')
|
|
);
|
|
}
|
|
|
|
function assignIds(node: any, key: string, idMap: Map<string, number>): any {
|
|
if (Array.isArray(node)) {
|
|
return node.map((n) => assignIds(n, key, idMap));
|
|
}
|
|
|
|
if (!node || typeof node !== 'object') {
|
|
return node;
|
|
}
|
|
|
|
if (node?.['@Id']) {
|
|
const idNum = parseInt(String(node['@Id']), 10);
|
|
|
|
if (isPointeeTarget(key, idNum) && !assignedNodes.has(node)) {
|
|
const previousId = String(node['@Id']);
|
|
node['@Id'] = _id;
|
|
assignedNodes.add(node);
|
|
idMap.set(previousId, _id);
|
|
_id++;
|
|
}
|
|
}
|
|
|
|
Object.keys(node).forEach((childKey) => {
|
|
if (!childKey.startsWith('@')) {
|
|
node[childKey] = assignIds(node[childKey], childKey, idMap);
|
|
}
|
|
});
|
|
|
|
return node;
|
|
}
|
|
|
|
function rewritePointeeIds(node: any, idMap: Map<string, number>): any {
|
|
if (Array.isArray(node)) {
|
|
return node.map((item) => rewritePointeeIds(item, idMap));
|
|
}
|
|
|
|
if (!node || typeof node !== 'object') {
|
|
return node;
|
|
}
|
|
|
|
if (node.PointeeId?.['@Value'] !== undefined) {
|
|
const replacement = idMap.get(String(node.PointeeId['@Value']));
|
|
if (replacement !== undefined) {
|
|
node.PointeeId['@Value'] = replacement;
|
|
}
|
|
}
|
|
|
|
Object.keys(node).forEach((key) => {
|
|
if (!key.startsWith('@')) {
|
|
node[key] = rewritePointeeIds(node[key], idMap);
|
|
}
|
|
});
|
|
|
|
return node;
|
|
}
|
|
|
|
export function fixIds(node: any): any {
|
|
const idMap = new Map<string, number>();
|
|
assignIds(node, '', idMap);
|
|
return rewritePointeeIds(node, idMap);
|
|
}
|
|
|
|
export function resetIds() {
|
|
_id = START_ID;
|
|
assignedNodes = new WeakSet<object>();
|
|
}
|
|
|
|
export function getId() {
|
|
return _id;
|
|
}
|
|
|
|
export async function gzipString(content: string) {
|
|
const encoder = new TextEncoder();
|
|
const input = encoder.encode(content);
|
|
|
|
const cs = new CompressionStream('gzip');
|
|
const writer = cs.writable.getWriter();
|
|
|
|
writer.write(input);
|
|
writer.close();
|
|
|
|
const compressed = await new Response(cs.readable).arrayBuffer();
|
|
|
|
return compressed;
|
|
}
|
|
|
|
export function filterFreqFromNormalized(x: number) {
|
|
const fMin = 30;
|
|
const fMax = 22000;
|
|
|
|
return fMin * (fMax / fMin) ** x;
|
|
}
|
|
|
|
export function toNativePath(posixPath: string): string {
|
|
const isWindows = navigator.userAgent.includes('indows');
|
|
const pathSep = isWindows ? '\\' : '/';
|
|
if (pathSep === '/') {
|
|
return posixPath;
|
|
}
|
|
|
|
return posixPath.split('/').join(pathSep);
|
|
}
|