Files
ep-133-project-export/src/lib/exporters/ableton/utils.ts

147 lines
3.7 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'),
};
export async function loadTemplate<T>(templateName: string): Promise<T> {
if (templateCache[templateName]) {
return templateCache[templateName];
}
const importFn = templateMap[templateName];
if (!importFn) {
throw new Error(`Unknown template: ${templateName}`);
}
const templateModule = await importFn();
const parsed = create(templateModule.default).toObject();
templateCache[templateName] = 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;
}
export function fixIds(node: any): any {
if (Array.isArray(node)) {
return node.map((n) => fixIds(n));
}
if (!node || typeof node !== 'object') {
return node;
}
if (node?.['@Id']) {
const idNum = parseInt(String(node['@Id']), 10);
if (idNum > MIN_ID && !assignedNodes.has(node)) {
node['@Id'] = _id;
assignedNodes.add(node);
_id++;
}
}
Object.keys(node).forEach((key) => {
if (!key.startsWith('@')) {
node[key] = fixIds(node[key]);
}
});
return node;
}
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);
}