UNPKG

u-he-preset-randomizer

Version:

Create random u-he synth presets through randomization and merging of your existing presets.

554 lines 22.3 kB
#!/usr/bin/env node import { analyzeParamsTypeAndRange, convertParamsModelBySection } from "./analyzer.js"; import { loadPresetLibrary, writePresetLibrary } from "./presetLibrary.js"; import fs from "fs-extra"; import fg from "fast-glob"; import { getConfigFromParameters } from "./config.js"; import { generateFullyRandomPresets, generateMergedPresets, generateRandomizedPresets } from "./randomizer.js"; import { detectPresetLibraryLocations } from "./utils/detector.js"; import inquirer from "inquirer"; import inquirerPrompt from "inquirer-autocomplete-prompt"; import { fileURLToPath } from 'url'; import chalk from "chalk"; import { dirname } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const packageJson = fs.readJSONSync(__dirname + '/../package.json'); console.log('======================================================================'); console.log('u-he Preset Randomizer CLI v' + packageJson.version); console.log('======================================================================'); console.log('Documentation: https://github.com/Fannon/u-he-preset-randomizer#readme'); console.log(''); let config = getConfigFromParameters(); (async () => { if (!config.synth) { // If no --synth argument is given, we'll go into fully interactive CLI mode await runInteractiveMode(); } else { runWithoutInteractivity(); } })().catch((err) => { console.error(err); process.exit(1); }); export function runWithoutInteractivity(overrideConfig) { if (overrideConfig) { config = { ...overrideConfig, ...config, }; } const presetLibrary = loadPresetLibrary(config.synth, config); // Narrow down by folder if (config.folder && config.folder !== true) { config.pattern = `${config.folder}${config.pattern || '**/*'}`; } // Filter out presets by favorite file (if given) if (config.favorites && config.favorites !== true) { presetLibrary.presets = narrowDownByFavoritesFile(presetLibrary, config.favorites); } // Filter out presets by author (if given) if (config.author && config.author !== true) { presetLibrary.presets = narrowDownByAuthor(presetLibrary, config.author); } // Filter out presets by category (if given) if (config.category && config.category !== true) { presetLibrary.presets = narrowDownByCategory(presetLibrary, config.category); } const paramsModel = analyzeParamsTypeAndRange(presetLibrary); if (config.debug) { // Write a cleaned up parameter model to ./tmp/paramsModel.json const outputParamsModel = JSON.parse(JSON.stringify(paramsModel)); // Remove values from debug paramsModel, as it would blow up the result too much. for (const paramKey in outputParamsModel) { delete outputParamsModel[paramKey].values; } fs.outputFileSync("./tmp/paramsModel.json", JSON.stringify(convertParamsModelBySection(outputParamsModel), null, 2)); console.debug(chalk.gray(JSON.stringify(config, null, 2))); } if (config.merge) { // Merge multiple presets together, with additional randomness const generatedPresets = generateMergedPresets(presetLibrary, paramsModel, config); writePresetLibrary(generatedPresets); } else if (config.preset) { // Randomize a particular preset const generatedPresets = generateRandomizedPresets(presetLibrary, paramsModel, config); writePresetLibrary(generatedPresets); } else { // Generate fully randomized presets const generatedPresets = generateFullyRandomPresets(presetLibrary, paramsModel, config); writePresetLibrary(generatedPresets); } console.log('======================================================================'); console.log('Successfully completed.'); } async function runInteractiveMode() { // Detect correct Preset Library Location const locations = detectPresetLibraryLocations(config); // 1) Detect available u-he synths and offer user choice inquirer.registerPrompt('autocomplete', inquirerPrompt); const synthChoices = locations.map((el) => { return { value: el.synthName }; }); if (!synthChoices.length) { console.error(chalk.red('Error: No u-he synths detected. Exiting.')); process.exit(1); } const synth = await inquirer.prompt([{ name: 'value', type: 'autocomplete', message: 'Which u-he synth to generate presets for?', pageSize: synthChoices.length, source: async (_answersSoFar, input) => { if (!input) { return synthChoices; } else { return synthChoices.filter((el) => { return el.value.toLowerCase().startsWith(input.toLowerCase()); }); } } }]); config.synth = synth.value; // 2) Choose random generation mode const mode = await inquirer.prompt([{ name: 'value', type: 'list', message: 'Which random mode?', choices: [ { value: "Fully randomized presets", checked: true }, { value: "Randomize existing preset" }, { value: "Merge existing presets" }, ] }]); // 2.5) Choose optional modifiers for randomization if (!config.stable || !config.binary) { const binaryEnabled = ['Repro-1', 'Repro-5'].includes(config.synth); const modifiers = await inquirer.prompt([{ name: 'value', type: 'checkbox', message: 'Set optional flags / modifiers (multi-choice):', choices: [ { value: "folder", name: '[Folder] Narrow down presets by folder' }, { value: "category", name: '[Category] Narrow down presets by category' }, { value: "author", name: '[Author] Narrow down presets by author' }, { value: "favorites", name: '[Favorites] Narrow down presets by favorites from .uhe-fav file' }, { value: "stable", name: '[Stable] More stable randomization approach', checked: true }, { value: "binary", name: '[Binary] Include binary section (WARNING: unstable with some synths!)', checked: binaryEnabled }, { value: "dictionary", name: '[Dictionary] Use real preset names to generate random preset names' }, ] }]); if (modifiers.value.includes('folder')) { config.folder = true; } if (modifiers.value.includes('category')) { config.category = true; } if (modifiers.value.includes('author')) { config.author = true; } if (modifiers.value.includes('favorites')) { config.favorites = true; } if (modifiers.value.includes('stable')) { config.stable = true; } if (modifiers.value.includes('binary')) { config.binary = true; } if (modifiers.value.includes('dictionary')) { config.dictionary = true; } } // Narrow down by folder selection if (config.folder === true) { // Detect correct Preset Library Location const location = detectPresetLibraryLocations(config).find(el => el.synthName.toLowerCase() === config.synth.toLowerCase()); const userFolders = fg.sync("**/*", { cwd: location.userPresets, onlyDirectories: true, }).map((el) => { return `/User/${el}/`; }); const presetFolders = fg.sync("**/*", { cwd: location.presets, onlyDirectories: true, }).map((el) => { return `/Local/${el}/`; }); const folders = [ "/", "/User/", "/Local/", ...userFolders, ...presetFolders, ].sort(); const folderPrompt = await inquirer.prompt([{ name: 'value', type: 'autocomplete', message: 'Which folder to narrow down to?', pageSize: 12, source: async (_answersSoFar, input) => { if (!input) { return folders; } else { return folders.filter((el) => { return el.toLowerCase().includes(input.toLowerCase()); }); } } }]); if (folderPrompt.value && folderPrompt.value !== "/") { config.folder = folderPrompt.value; config.pattern = `${config.folder}${config.pattern || '**/*'}`; } } console.log(`> Loading and analyzing preset library...`); const presetLibrary = loadPresetLibrary(config.synth, config); // Optionally: Narrow down by u-he favorites if (config.favorites === true && presetLibrary.favorites.length) { const allChoices = presetLibrary.favorites.map((el) => { return { value: el.fileName, name: `${el.fileName} (${el.presets.length})` }; }); const favoritesPrompt = await inquirer.prompt([{ name: 'value', type: 'checkbox', message: 'Which favorite file(s) to use as a selection?', choices: allChoices, }]); config.favorites = favoritesPrompt.value; } // Filter out presets by favorite file (if given) if (config.favorites && config.favorites !== true && config.favorites.length > 0) { presetLibrary.presets = narrowDownByFavoritesFile(presetLibrary, config.favorites); } else { console.log('> No selection made, skipping this step.'); } // Optionally: Narrow down by author if (config.author === true) { const availableAuthors = {}; for (const preset of presetLibrary.presets) { const authorMeta = preset.meta.find(el => el.key === 'Author'); if (authorMeta) { if (!availableAuthors[authorMeta.value]) { availableAuthors[authorMeta.value] = 0; } availableAuthors[authorMeta.value]++; } } let allChoices = []; for (const author in availableAuthors) { allChoices.push({ value: author, name: `${author} (${availableAuthors[author]})` }); } allChoices.sort((a, b) => a.value.localeCompare(b.value)); if (allChoices.length) { const authorPrompt = await inquirer.prompt([{ name: 'value', type: 'autocomplete', message: 'Which author to narrow down to?', pageSize: 12, source: async (_answersSoFar, input) => { if (!input) { return allChoices; } else { return allChoices.filter((el) => { return el.name.toLowerCase().includes(input.toLowerCase()); }); } } }]); config.author = authorPrompt.value; } } // Filter out presets by author (if given) if (config.author && config.author !== true) { presetLibrary.presets = narrowDownByAuthor(presetLibrary, config.author); } // Optionally: Narrow down by category if (config.category === true) { const availableCategories = {}; for (const preset of presetLibrary.presets) { for (const category of preset.categories) { const parentCategory = category.split(':')[0]; if (!availableCategories[parentCategory]) { availableCategories[parentCategory] = 0; } availableCategories[parentCategory]++; if (!availableCategories[category]) { availableCategories[category] = 0; } availableCategories[category]++; } } const allChoices = []; for (const category in availableCategories) { allChoices.push({ value: category, name: `${category} (${availableCategories[category]})` }); } allChoices.sort((a, b) => a.value.localeCompare(b.value)); if (allChoices.length) { const categoryPrompt = await inquirer.prompt([{ name: 'value', type: 'autocomplete', message: 'Which category to narrow down to?', pageSize: 12, source: async (_answersSoFar, input) => { if (!input) { return allChoices; } else { return allChoices.filter((el) => { return el.name.toLowerCase().includes(input.toLowerCase()); }); } } }]); config.category = categoryPrompt.value; } } // Filter out presets by category (if given) if (config.category && config.category !== true) { presetLibrary.presets = narrowDownByCategory(presetLibrary, config.category); } const paramsModel = analyzeParamsTypeAndRange(presetLibrary); if (mode.value === "Fully randomized presets") { ////////////////////////////////////////////////// // MODE 1: Generate fully randomized presets // ////////////////////////////////////////////////// if (!config.amount) { config.amount = await chooseAmountOfPresets(32); } const generatedPresets = generateFullyRandomPresets(presetLibrary, paramsModel, config); writePresetLibrary(generatedPresets); } else if (mode.value === "Randomize existing preset") { ////////////////////////////////////////////////// // MODE 2: Randomize existing preset // ////////////////////////////////////////////////// const foundPresets = presetLibrary.presets.map((el) => el.filePath); config.preset = await choosePreset(foundPresets); if (!config.preset) { process.exit(0); } if (!config.randomness) { config.randomness = await chooseRandomness(20); } if (!config.amount) { config.amount = await chooseAmountOfPresets(16); } const generatedPresets = generateRandomizedPresets(presetLibrary, paramsModel, config); writePresetLibrary(generatedPresets); } else if (mode.value === "Merge existing presets") { ////////////////////////////////////////////////// // MODE 3: Merge Random Presets // ////////////////////////////////////////////////// config.merge = []; const foundPresets = presetLibrary.presets.map((el) => el.filePath); // eslint-disable-next-line no-constant-condition while (true) { const presetChoice = await choosePreset(foundPresets, true); if (presetChoice) { config.merge.push(presetChoice); if (presetChoice === "*") { break; } } else { break; } } // Choose amount of randomness if (!config.randomness) { config.randomness = await chooseRandomness(0); } if (!config.amount) { config.amount = await chooseAmountOfPresets(16); } const generatedPresets = generateMergedPresets(presetLibrary, paramsModel, config); writePresetLibrary(generatedPresets); } console.log('======================================================================'); console.log(chalk.green('Successfully completed.')); console.log(''); console.log('To run it with the same configuration again, execute:'); let cliCommand = `npx u-he-preset-randomizer@latest --synth ${config.synth} --amount ${config.amount}`; if (config.preset) { cliCommand += ` --preset "${config.preset}"`; } else if (config.merge) { for (const merge of config.merge) { cliCommand += ` --merge "${merge}"`; } } if (config.randomness) { cliCommand += ` --randomness "${config.randomness}"`; } if (config.pattern && config.pattern !== '**/*') { cliCommand += ` --pattern "${config.pattern}"`; } if (config.folder) { cliCommand += ` --folder "${config.folder}"`; } if (config.favorites) { cliCommand += ` --favorites "${config.favorites}"`; } if (config.category) { cliCommand += ` --category "${config.category}"`; } if (config.author) { cliCommand += ` --author "${config.author}"`; } if (config.dictionary) { cliCommand += ` --dictionary`; } if (config.stable) { cliCommand += ` --stable`; } if (config.debug) { cliCommand += ` --debug`; } console.log(chalk.bgGray(chalk.black(cliCommand))); } ////////////////////////////////////////// // HELPER FUNCTIONS // ////////////////////////////////////////// /** Choose number of presets to generate */ async function chooseAmountOfPresets(initial = 8) { const amount = await inquirer.prompt([{ name: 'value', type: 'number', message: 'How many presets to generate?', default: initial }]); return amount.value; } /** Choose randomness amount */ async function chooseRandomness(initial = 20) { const amount = await inquirer.prompt([{ name: 'value', type: 'number', message: 'How much randomness to apply (0-100)', default: initial, validate: (input) => { return input >= 0 && input <= 100; } }]); return amount.value; } async function choosePreset(foundPresets, allowSelectAll = false) { const controlChoices = [ { value: '', name: ' (no choice / complete)' }, { value: '?', name: '? (random pick)' }, ]; if (allowSelectAll) { controlChoices.push({ value: '*', name: '* (select all)' }); } const allChoices = controlChoices.concat(foundPresets.map((el) => { return { value: el, name: el, }; })); const presetChoice = await inquirer.prompt([{ name: 'value', type: 'autocomplete', message: 'Select preset(s):', pageSize: 12, source: async (_answersSoFar, input) => { if (!input) { return allChoices; } else { const staticChoices = []; if (allowSelectAll) { staticChoices.push({ value: `*${input}*`, name: `*${input}* (all presets including search string)` }); } staticChoices.push({ value: `?${input}?`, name: `?${input}? (random pick of presets including search string)` }); return staticChoices.concat(allChoices.filter((el) => { return el.name.toLowerCase().includes(input.toLowerCase()); })); } } }]); return presetChoice.value; } function narrowDownByCategory(presetLibrary, category) { const filteredPresets = presetLibrary.presets.filter((el) => { if (!el.categories.length) { return false; } for (const ownCategory of el.categories) { if (ownCategory.startsWith(category)) { return true; } } return false; }); console.log(`Narrowed down by category "${category}" to ${filteredPresets.length} presets`); return filteredPresets; } function narrowDownByAuthor(presetLibrary, author) { const filteredPresets = presetLibrary.presets.filter((el) => { if (!el.categories.length) { return false; } const authorMeta = el.meta.find(el => el.key === 'Author'); return authorMeta && authorMeta.value === author; }); console.log(`Narrowed down by author "${author}" to ${filteredPresets.length} presets`); return filteredPresets; } function narrowDownByFavoritesFile(presetLibrary, favorites) { if (!Array.isArray(favorites)) { favorites = [favorites]; } const favPresets = []; const filteredPresets = []; for (const favoriteFilePath of favorites) { const favoriteFile = presetLibrary.favorites.find((el) => el.fileName === favoriteFilePath); if (favoriteFile) { favPresets.push(...favoriteFile.presets); } else { console.error(chalk.red(`Error: Could not find favorites file: ${favorites}`)); return presetLibrary.presets; } } console.log(favPresets.length); // Now filter it out for (const preset of presetLibrary.presets) { for (const fav of favPresets) { if (preset.filePath.toLowerCase() === (fav.path.toLowerCase() + '/' + fav.name.toLowerCase() + '.h2p')) { filteredPresets.push(preset); break; } } } console.log(`Narrowed down via favorite file "${favorites}" to ${filteredPresets.length} presets`); return filteredPresets; } //# sourceMappingURL=cli.js.map