feat(playground): per-engine param presets for additive + FM (meml-ax4)
Additive engine: 5 presets across 3 tiers (Spectral Basics, Formant Play, Harmonic Sculptor, Phase Explorer, Full Spectrum). FM engine: 5 presets across 3 tiers (Simple FM, Bell Tones, Matrix Explorer, Feedback Machine, Full Matrix). Engine-aware preset system: applyPreset() dispatches to C15 groupOverrides or new flat engineParamOverrides depending on active engine. Preset selector dropdown rebuilds on engine switch. URL ?preset param searches all engines.
This commit is contained in:
parent
5a004c6a3e
commit
329b326ede
3 changed files with 632 additions and 43 deletions
|
|
@ -15,6 +15,8 @@ import { GamepadInput } from './ui/gamepad.js';
|
||||||
import { HandTracker } from './ui/hand-tracker.js';
|
import { HandTracker } from './ui/hand-tracker.js';
|
||||||
import { createDevPanel } from './ui/dev-panel.js';
|
import { createDevPanel } from './ui/dev-panel.js';
|
||||||
import { SYNTH_PRESETS, PRESET_TIERS } from './synth/presets.js';
|
import { SYNTH_PRESETS, PRESET_TIERS } from './synth/presets.js';
|
||||||
|
import { ADDITIVE_PRESETS, ADDITIVE_PRESET_TIERS } from './synth/additive-presets.js';
|
||||||
|
import { FM_PRESETS, FM_PRESET_TIERS } from './synth/fm-presets.js';
|
||||||
import { EOCChain } from './eoc/index.js';
|
import { EOCChain } from './eoc/index.js';
|
||||||
import { EOCChainUI, moduleFactory } from './ui/eoc-chain-ui.js';
|
import { EOCChainUI, moduleFactory } from './ui/eoc-chain-ui.js';
|
||||||
import { EngineSwitcher } from './ui/engine-switcher.js';
|
import { EngineSwitcher } from './ui/engine-switcher.js';
|
||||||
|
|
@ -256,11 +258,69 @@ const paramToSection = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Engine-aware preset helpers ----
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the preset array for the given engine id.
|
||||||
|
*/
|
||||||
|
function getPresetsForEngine(engineId) {
|
||||||
|
switch (engineId) {
|
||||||
|
case 'shaper-feedback': return SYNTH_PRESETS;
|
||||||
|
case 'additive': return ADDITIVE_PRESETS;
|
||||||
|
case 'fm': return FM_PRESETS;
|
||||||
|
default: return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the tier labels for the given engine id.
|
||||||
|
*/
|
||||||
|
function getPresetTiersForEngine(engineId) {
|
||||||
|
switch (engineId) {
|
||||||
|
case 'shaper-feedback': return PRESET_TIERS;
|
||||||
|
case 'additive': return ADDITIVE_PRESET_TIERS;
|
||||||
|
case 'fm': return FM_PRESET_TIERS;
|
||||||
|
default: return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Engine param overrides (for non-C15 engines) ----
|
||||||
|
// Flat array of per-param overrides for the current Faust engine.
|
||||||
|
// null when C15 is active (uses groupOverrides instead).
|
||||||
|
// Each entry: { min, max, curve, muted, fixedValue }
|
||||||
|
let engineParamOverrides = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a fresh engineParamOverrides array from the current engine's paramMeta.
|
||||||
|
* All params default to unmuted with [0,1] range and linear curve.
|
||||||
|
*/
|
||||||
|
function buildEngineParamOverrides() {
|
||||||
|
const meta = activeEngine?.paramMeta;
|
||||||
|
if (!meta) { engineParamOverrides = null; return; }
|
||||||
|
engineParamOverrides = meta.map(p => ({
|
||||||
|
min: 0,
|
||||||
|
max: 1,
|
||||||
|
curve: 0.5,
|
||||||
|
muted: false,
|
||||||
|
fixedValue: 0.5,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply group overrides (per-param curve + min/max) to a single ML output value.
|
* Apply group overrides (per-param curve + min/max) to a single ML output value.
|
||||||
* Returns the remapped value, or fixedValue if the param is muted.
|
* Returns the remapped value, or fixedValue if the param is muted.
|
||||||
|
*
|
||||||
|
* For non-C15 engines, uses engineParamOverrides (flat array).
|
||||||
|
* For C15, uses the nested groupOverrides/paramToSection system.
|
||||||
*/
|
*/
|
||||||
function applyGroupOverrides(rawValue, paramIndex) {
|
function applyGroupOverrides(rawValue, paramIndex) {
|
||||||
|
// Non-C15 engine: use flat engine param overrides
|
||||||
|
if (engineParamOverrides && paramIndex < engineParamOverrides.length) {
|
||||||
|
const p = engineParamOverrides[paramIndex];
|
||||||
|
if (p.muted) return p.fixedValue;
|
||||||
|
return applyGroupOverride(rawValue, p.curve, p.min, p.max);
|
||||||
|
}
|
||||||
|
// C15 path: nested section/group overrides
|
||||||
const mapping = paramToSection[paramIndex];
|
const mapping = paramToSection[paramIndex];
|
||||||
if (!mapping) return rawValue;
|
if (!mapping) return rawValue;
|
||||||
const ov = groupOverrides[mapping.si];
|
const ov = groupOverrides[mapping.si];
|
||||||
|
|
@ -273,6 +333,11 @@ function applyGroupOverrides(rawValue, paramIndex) {
|
||||||
|
|
||||||
/** Check if param at given index is muted */
|
/** Check if param at given index is muted */
|
||||||
function isParamMuted(paramIndex) {
|
function isParamMuted(paramIndex) {
|
||||||
|
// Non-C15 engine: use flat engine param overrides
|
||||||
|
if (engineParamOverrides && paramIndex < engineParamOverrides.length) {
|
||||||
|
return engineParamOverrides[paramIndex].muted;
|
||||||
|
}
|
||||||
|
// C15 path
|
||||||
const mapping = paramToSection[paramIndex];
|
const mapping = paramToSection[paramIndex];
|
||||||
if (!mapping) return false;
|
if (!mapping) return false;
|
||||||
return groupOverrides[mapping.si].params[mapping.li].muted;
|
return groupOverrides[mapping.si].params[mapping.li].muted;
|
||||||
|
|
@ -282,22 +347,27 @@ function isParamMuted(paramIndex) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply a synth preset by id.
|
* Apply a synth preset by id.
|
||||||
* Sets groupOverrides (muted/active, min/max/curve/fixedValue) for all 126 params,
|
* Engine-aware: uses groupOverrides for C15, engineParamOverrides for Faust engines.
|
||||||
|
* Sets muted/active, min/max/curve/fixedValue for all params,
|
||||||
* re-routes outputs, saves state, and updates the UI dropdown.
|
* re-routes outputs, saves state, and updates the UI dropdown.
|
||||||
*/
|
*/
|
||||||
function applyPreset(presetId) {
|
function applyPreset(presetId) {
|
||||||
const preset = SYNTH_PRESETS.find(p => p.id === presetId);
|
const engineId = activeEngine?.id ?? 'shaper-feedback';
|
||||||
|
const presets = getPresetsForEngine(engineId);
|
||||||
|
const preset = presets.find(p => p.id === presetId);
|
||||||
if (!preset) {
|
if (!preset) {
|
||||||
console.warn(`[NISPS] Unknown preset: ${presetId}`);
|
console.warn(`[NISPS] Unknown preset: ${presetId} (engine: ${engineId})`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const t = tameLevel;
|
const allActive = preset.active === null; // null = all params active
|
||||||
const allActive = preset.active === null; // null = all params active (Tier 4)
|
|
||||||
const activeSet = allActive ? null : new Set(preset.active);
|
const activeSet = allActive ? null : new Set(preset.active);
|
||||||
const overrides = preset.overrides || {};
|
const overrides = preset.overrides || {};
|
||||||
const mutedOv = preset.mutedOverrides || {};
|
const mutedOv = preset.mutedOverrides || {};
|
||||||
|
|
||||||
|
if (engineId === 'shaper-feedback') {
|
||||||
|
// ---- C15 path: uses groupOverrides / paramToSection ----
|
||||||
|
const t = tameLevel;
|
||||||
for (let i = 0; i < N_SYNTH_OUTPUTS; i++) {
|
for (let i = 0; i < N_SYNTH_OUTPUTS; i++) {
|
||||||
const param = SYNTH_PARAM_MAP[i];
|
const param = SYNTH_PARAM_MAP[i];
|
||||||
const mapping = paramToSection[i];
|
const mapping = paramToSection[i];
|
||||||
|
|
@ -305,7 +375,6 @@ function applyPreset(presetId) {
|
||||||
const gp = groupOverrides[mapping.si].params[mapping.li];
|
const gp = groupOverrides[mapping.si].params[mapping.li];
|
||||||
const paramName = param.name;
|
const paramName = param.name;
|
||||||
|
|
||||||
// Tame-derived defaults for this param
|
|
||||||
const safeMin = param.safeMin ?? 0;
|
const safeMin = param.safeMin ?? 0;
|
||||||
const safeMax = param.safeMax ?? 1;
|
const safeMax = param.safeMax ?? 1;
|
||||||
const tameMin = safeMin * t;
|
const tameMin = safeMin * t;
|
||||||
|
|
@ -331,7 +400,6 @@ function applyPreset(presetId) {
|
||||||
gp.muted = true;
|
gp.muted = true;
|
||||||
const mov = mutedOv[paramName];
|
const mov = mutedOv[paramName];
|
||||||
gp.fixedValue = (mov && mov.fixedValue !== undefined) ? mov.fixedValue : param.defaultValue;
|
gp.fixedValue = (mov && mov.fixedValue !== undefined) ? mov.fixedValue : param.defaultValue;
|
||||||
// Keep tame-derived ranges for manual unmuting
|
|
||||||
gp.min = tameMin;
|
gp.min = tameMin;
|
||||||
gp.max = tameMax;
|
gp.max = tameMax;
|
||||||
gp.curve = 0.5;
|
gp.curve = 0.5;
|
||||||
|
|
@ -344,6 +412,47 @@ function applyPreset(presetId) {
|
||||||
const secName = SYNTH_SECTIONS[si].name;
|
const secName = SYNTH_SECTIONS[si].name;
|
||||||
groupOverrides[si].curve = (gc[secName] !== undefined) ? gc[secName] : 0.5;
|
groupOverrides[si].curve = (gc[secName] !== undefined) ? gc[secName] : 0.5;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// ---- Faust engine path: uses engineParamOverrides (flat array) ----
|
||||||
|
const meta = activeEngine?.paramMeta ?? [];
|
||||||
|
// Build id -> index lookup
|
||||||
|
const idToIndex = new Map();
|
||||||
|
meta.forEach((p, i) => idToIndex.set(p.id, i));
|
||||||
|
|
||||||
|
// Ensure engineParamOverrides exists with correct length
|
||||||
|
if (!engineParamOverrides || engineParamOverrides.length !== meta.length) {
|
||||||
|
buildEngineParamOverrides();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < meta.length; i++) {
|
||||||
|
const paramId = meta[i].id;
|
||||||
|
const ep = engineParamOverrides[i];
|
||||||
|
const isActive = allActive || activeSet.has(paramId);
|
||||||
|
|
||||||
|
if (isActive) {
|
||||||
|
ep.muted = false;
|
||||||
|
const ov = overrides[paramId];
|
||||||
|
if (ov) {
|
||||||
|
ep.min = ov.min !== undefined ? ov.min : 0;
|
||||||
|
ep.max = ov.max !== undefined ? ov.max : 1;
|
||||||
|
ep.curve = ov.curve !== undefined ? ov.curve : 0.5;
|
||||||
|
ep.fixedValue = ov.fixedValue !== undefined ? ov.fixedValue : 0.5;
|
||||||
|
} else {
|
||||||
|
ep.min = 0;
|
||||||
|
ep.max = 1;
|
||||||
|
ep.curve = 0.5;
|
||||||
|
ep.fixedValue = 0.5;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ep.muted = true;
|
||||||
|
const mov = mutedOv[paramId];
|
||||||
|
ep.fixedValue = (mov && mov.fixedValue !== undefined) ? mov.fixedValue : 0.5;
|
||||||
|
ep.min = 0;
|
||||||
|
ep.max = 1;
|
||||||
|
ep.curve = 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
activeSynthPresetId = presetId;
|
activeSynthPresetId = presetId;
|
||||||
|
|
||||||
|
|
@ -362,7 +471,7 @@ function applyPreset(presetId) {
|
||||||
// Save to storage
|
// Save to storage
|
||||||
saveState();
|
saveState();
|
||||||
|
|
||||||
console.log(`[NISPS] Applied synth preset: ${preset.name}`);
|
console.log(`[NISPS] Applied synth preset: ${preset.name} (engine: ${engineId})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- SynthVisualizer class ----
|
// ---- SynthVisualizer class ----
|
||||||
|
|
@ -843,6 +952,19 @@ async function setActiveEngine(engine) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build engine-specific param overrides (null for C15, flat array for Faust)
|
||||||
|
if (engine.id === 'shaper-feedback') {
|
||||||
|
engineParamOverrides = null;
|
||||||
|
} else {
|
||||||
|
buildEngineParamOverrides();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild the preset selector for the new engine
|
||||||
|
rebuildPresetSelector();
|
||||||
|
|
||||||
|
// Clear active preset — it belongs to the previous engine
|
||||||
|
activeSynthPresetId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resizeMLP(newOutputCount) {
|
async function resizeMLP(newOutputCount) {
|
||||||
|
|
@ -1044,9 +1166,10 @@ async function init() {
|
||||||
// Wire synth preset selector
|
// Wire synth preset selector
|
||||||
wireSynthPresets();
|
wireSynthPresets();
|
||||||
|
|
||||||
// Check URL ?preset param (overrides localStorage)
|
// Check URL ?preset param (overrides localStorage) — search all engines
|
||||||
const urlPreset = urlParams.get('preset');
|
const urlPreset = urlParams.get('preset');
|
||||||
if (urlPreset && SYNTH_PRESETS.some(p => p.id === urlPreset)) {
|
const allPresets = [...SYNTH_PRESETS, ...ADDITIVE_PRESETS, ...FM_PRESETS];
|
||||||
|
if (urlPreset && allPresets.some(p => p.id === urlPreset)) {
|
||||||
applyPreset(urlPreset);
|
applyPreset(urlPreset);
|
||||||
} else if (activeSynthPresetId) {
|
} else if (activeSynthPresetId) {
|
||||||
// Restored from localStorage — sync the dropdown
|
// Restored from localStorage — sync the dropdown
|
||||||
|
|
@ -3065,9 +3188,58 @@ function wireKeyboard() {
|
||||||
|
|
||||||
// ---- Quick play controls ----
|
// ---- Quick play controls ----
|
||||||
// ---- Synth Preset Selector ----
|
// ---- Synth Preset Selector ----
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rebuild the <select> dropdown options for the current engine's presets.
|
||||||
|
* Called on engine switch and at init time.
|
||||||
|
*/
|
||||||
|
function rebuildPresetSelector() {
|
||||||
|
const $select = document.getElementById('synth-preset-select');
|
||||||
|
if (!$select) return;
|
||||||
|
|
||||||
|
const engineId = activeEngine?.id ?? 'shaper-feedback';
|
||||||
|
const presets = getPresetsForEngine(engineId);
|
||||||
|
const tiers = getPresetTiersForEngine(engineId);
|
||||||
|
|
||||||
|
// Clear existing options
|
||||||
|
$select.innerHTML = '';
|
||||||
|
|
||||||
|
// Manual option
|
||||||
|
const manualOpt = document.createElement('option');
|
||||||
|
manualOpt.value = '';
|
||||||
|
manualOpt.textContent = 'Manual';
|
||||||
|
$select.appendChild(manualOpt);
|
||||||
|
|
||||||
|
// Group by tier
|
||||||
|
for (const tier of tiers) {
|
||||||
|
const group = document.createElement('optgroup');
|
||||||
|
group.label = tier.label;
|
||||||
|
for (const preset of presets.filter(p => p.tier === tier.tier)) {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = preset.id;
|
||||||
|
opt.textContent = preset.name;
|
||||||
|
group.appendChild(opt);
|
||||||
|
}
|
||||||
|
if (group.children.length > 0) {
|
||||||
|
$select.appendChild(group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync current selection
|
||||||
|
if (activeSynthPresetId && presets.some(p => p.id === activeSynthPresetId)) {
|
||||||
|
$select.value = activeSynthPresetId;
|
||||||
|
} else {
|
||||||
|
$select.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function wireSynthPresets() {
|
function wireSynthPresets() {
|
||||||
const $select = document.getElementById('synth-preset-select');
|
const $select = document.getElementById('synth-preset-select');
|
||||||
if (!$select) return;
|
if (!$select) return;
|
||||||
|
|
||||||
|
// Build initial options for the active engine
|
||||||
|
rebuildPresetSelector();
|
||||||
|
|
||||||
$select.addEventListener('change', () => {
|
$select.addEventListener('change', () => {
|
||||||
const val = $select.value;
|
const val = $select.value;
|
||||||
if (val === '') {
|
if (val === '') {
|
||||||
|
|
|
||||||
204
playground/js/synth/additive-presets.js
Normal file
204
playground/js/synth/additive-presets.js
Normal file
|
|
@ -0,0 +1,204 @@
|
||||||
|
// Additive synth presets for NISPS playground
|
||||||
|
// Each preset defines which params are active (ML-controlled) and which are
|
||||||
|
// muted at safe defaults. Param IDs match faustParamAddress() output from
|
||||||
|
// additive.json (e.g. '1_spectral_shape_00_h1_amp').
|
||||||
|
//
|
||||||
|
// 3 tiers of progressive complexity across 5 presets.
|
||||||
|
|
||||||
|
export const ADDITIVE_PRESETS = [
|
||||||
|
// ======================================================================
|
||||||
|
// TIER 1 — Beginner (~12-15 active params)
|
||||||
|
// ======================================================================
|
||||||
|
{
|
||||||
|
id: 'additive-1',
|
||||||
|
name: '1.1',
|
||||||
|
tier: 1,
|
||||||
|
description: 'Spectral Basics — H1-H4 amplitudes, global ADSR, spectral tilt, level',
|
||||||
|
active: [
|
||||||
|
'1_spectral_shape_00_h1_amp',
|
||||||
|
'1_spectral_shape_01_h2_amp',
|
||||||
|
'1_spectral_shape_02_h3_amp',
|
||||||
|
'1_spectral_shape_03_h4_amp',
|
||||||
|
'1_spectral_shape_11_spectral_tilt',
|
||||||
|
'2_temporal_00_attack',
|
||||||
|
'2_temporal_01_decay',
|
||||||
|
'2_temporal_02_sustain',
|
||||||
|
'2_temporal_03_release',
|
||||||
|
'5_master_00_level',
|
||||||
|
'5_master_04_saturation',
|
||||||
|
'3_phase_04_stereo_phase_spread',
|
||||||
|
],
|
||||||
|
overrides: {
|
||||||
|
'1_spectral_shape_00_h1_amp': { min: 0.3, max: 1.0, curve: 0.55 },
|
||||||
|
'1_spectral_shape_01_h2_amp': { min: 0.0, max: 0.8, curve: 0.45 },
|
||||||
|
'1_spectral_shape_02_h3_amp': { min: 0.0, max: 0.7, curve: 0.4 },
|
||||||
|
'1_spectral_shape_03_h4_amp': { min: 0.0, max: 0.6, curve: 0.4 },
|
||||||
|
'1_spectral_shape_11_spectral_tilt': { min: 0.2, max: 0.8, curve: 0.5 },
|
||||||
|
'2_temporal_00_attack': { min: 0.0, max: 0.7, curve: 0.35 },
|
||||||
|
'2_temporal_01_decay': { min: 0.05, max: 0.6, curve: 0.4 },
|
||||||
|
'2_temporal_02_sustain': { min: 0.2, max: 0.9, curve: 0.55 },
|
||||||
|
'2_temporal_03_release': { min: 0.1, max: 0.7, curve: 0.45 },
|
||||||
|
'5_master_00_level': { min: 0.3, max: 0.85, curve: 0.5 },
|
||||||
|
'5_master_04_saturation': { min: 0.0, max: 0.4, curve: 0.3 },
|
||||||
|
'3_phase_04_stereo_phase_spread': { min: 0.0, max: 0.5, curve: 0.4 },
|
||||||
|
},
|
||||||
|
mutedOverrides: {},
|
||||||
|
groupCurves: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 'additive-2',
|
||||||
|
name: '1.2',
|
||||||
|
tier: 1,
|
||||||
|
description: 'Formant Play — H1-H4, formant shaping, ADSR, level',
|
||||||
|
active: [
|
||||||
|
'1_spectral_shape_00_h1_amp',
|
||||||
|
'1_spectral_shape_01_h2_amp',
|
||||||
|
'1_spectral_shape_02_h3_amp',
|
||||||
|
'1_spectral_shape_03_h4_amp',
|
||||||
|
'4_modulation_07_formant1_freq',
|
||||||
|
'4_modulation_08_formant2_freq',
|
||||||
|
'4_modulation_09_formant_depth',
|
||||||
|
'2_temporal_00_attack',
|
||||||
|
'2_temporal_01_decay',
|
||||||
|
'2_temporal_02_sustain',
|
||||||
|
'2_temporal_03_release',
|
||||||
|
'5_master_00_level',
|
||||||
|
'1_spectral_shape_11_spectral_tilt',
|
||||||
|
],
|
||||||
|
overrides: {
|
||||||
|
'1_spectral_shape_00_h1_amp': { min: 0.3, max: 1.0, curve: 0.55 },
|
||||||
|
'1_spectral_shape_01_h2_amp': { min: 0.0, max: 0.8, curve: 0.45 },
|
||||||
|
'1_spectral_shape_02_h3_amp': { min: 0.0, max: 0.7, curve: 0.4 },
|
||||||
|
'1_spectral_shape_03_h4_amp': { min: 0.0, max: 0.6, curve: 0.4 },
|
||||||
|
'4_modulation_07_formant1_freq': { min: 0.05, max: 0.7, curve: 0.45 },
|
||||||
|
'4_modulation_08_formant2_freq': { min: 0.1, max: 0.8, curve: 0.5 },
|
||||||
|
'4_modulation_09_formant_depth': { min: 0.0, max: 0.8, curve: 0.4 },
|
||||||
|
'2_temporal_00_attack': { min: 0.0, max: 0.6, curve: 0.35 },
|
||||||
|
'2_temporal_01_decay': { min: 0.05, max: 0.5, curve: 0.4 },
|
||||||
|
'2_temporal_02_sustain': { min: 0.3, max: 0.9, curve: 0.55 },
|
||||||
|
'2_temporal_03_release': { min: 0.1, max: 0.65, curve: 0.45 },
|
||||||
|
'5_master_00_level': { min: 0.3, max: 0.85, curve: 0.5 },
|
||||||
|
'1_spectral_shape_11_spectral_tilt': { min: 0.2, max: 0.8, curve: 0.5 },
|
||||||
|
},
|
||||||
|
mutedOverrides: {},
|
||||||
|
groupCurves: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
// ======================================================================
|
||||||
|
// TIER 2 — Intermediate (~25-30 active params)
|
||||||
|
// ======================================================================
|
||||||
|
{
|
||||||
|
id: 'additive-3',
|
||||||
|
name: '2.1',
|
||||||
|
tier: 2,
|
||||||
|
description: 'Harmonic Sculptor — all H amps, spectral tilt, inharmonicity, ADSR, brightness envelope, vibrato, level',
|
||||||
|
active: [
|
||||||
|
// All individual harmonics + groups
|
||||||
|
'1_spectral_shape_00_h1_amp',
|
||||||
|
'1_spectral_shape_01_h2_amp',
|
||||||
|
'1_spectral_shape_02_h3_amp',
|
||||||
|
'1_spectral_shape_03_h4_amp',
|
||||||
|
'1_spectral_shape_04_h5_amp',
|
||||||
|
'1_spectral_shape_05_h6_amp',
|
||||||
|
'1_spectral_shape_06_h7_amp',
|
||||||
|
'1_spectral_shape_07_h8_amp',
|
||||||
|
'1_spectral_shape_08_h9_16_amp',
|
||||||
|
'1_spectral_shape_09_h17_32_amp',
|
||||||
|
'1_spectral_shape_10_h33_64_amp',
|
||||||
|
'1_spectral_shape_11_spectral_tilt',
|
||||||
|
'1_spectral_shape_12_inharmonicity',
|
||||||
|
// Amplitude ADSR
|
||||||
|
'2_temporal_00_attack',
|
||||||
|
'2_temporal_01_decay',
|
||||||
|
'2_temporal_02_sustain',
|
||||||
|
'2_temporal_03_release',
|
||||||
|
// Brightness ADSR
|
||||||
|
'2_temporal_04_brightness_attack',
|
||||||
|
'2_temporal_05_brightness_decay',
|
||||||
|
'2_temporal_06_brightness_sustain',
|
||||||
|
'2_temporal_07_brightness_release',
|
||||||
|
// Vibrato
|
||||||
|
'4_modulation_00_vibrato_rate',
|
||||||
|
'4_modulation_01_vibrato_depth',
|
||||||
|
'4_modulation_02_vibrato_delay',
|
||||||
|
// Master
|
||||||
|
'5_master_00_level',
|
||||||
|
'5_master_04_saturation',
|
||||||
|
],
|
||||||
|
overrides: {
|
||||||
|
'1_spectral_shape_12_inharmonicity': { min: 0.0, max: 0.5, curve: 0.3 },
|
||||||
|
'4_modulation_01_vibrato_depth': { min: 0.0, max: 0.6, curve: 0.35 },
|
||||||
|
'5_master_04_saturation': { min: 0.0, max: 0.5, curve: 0.35 },
|
||||||
|
},
|
||||||
|
mutedOverrides: {},
|
||||||
|
groupCurves: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 'additive-4',
|
||||||
|
name: '2.2',
|
||||||
|
tier: 2,
|
||||||
|
description: 'Phase Explorer — H1-H8, phase randomisation, beating, stereo spread, noise floor, ADSR, formants, level',
|
||||||
|
active: [
|
||||||
|
// H1-H8
|
||||||
|
'1_spectral_shape_00_h1_amp',
|
||||||
|
'1_spectral_shape_01_h2_amp',
|
||||||
|
'1_spectral_shape_02_h3_amp',
|
||||||
|
'1_spectral_shape_03_h4_amp',
|
||||||
|
'1_spectral_shape_04_h5_amp',
|
||||||
|
'1_spectral_shape_05_h6_amp',
|
||||||
|
'1_spectral_shape_06_h7_amp',
|
||||||
|
'1_spectral_shape_07_h8_amp',
|
||||||
|
// Phase controls
|
||||||
|
'3_phase_00_phase_random',
|
||||||
|
'3_phase_02_beating_depth',
|
||||||
|
'3_phase_03_beating_rate',
|
||||||
|
'3_phase_04_stereo_phase_spread',
|
||||||
|
'3_phase_05_noise_floor',
|
||||||
|
'3_phase_06_noise_color',
|
||||||
|
// Amplitude ADSR
|
||||||
|
'2_temporal_00_attack',
|
||||||
|
'2_temporal_01_decay',
|
||||||
|
'2_temporal_02_sustain',
|
||||||
|
'2_temporal_03_release',
|
||||||
|
// Formants
|
||||||
|
'4_modulation_07_formant1_freq',
|
||||||
|
'4_modulation_08_formant2_freq',
|
||||||
|
'4_modulation_09_formant_depth',
|
||||||
|
// Odd/even
|
||||||
|
'1_spectral_shape_13_odd_even',
|
||||||
|
// Master
|
||||||
|
'5_master_00_level',
|
||||||
|
'5_master_04_saturation',
|
||||||
|
'3_phase_07_sub_harmonic',
|
||||||
|
],
|
||||||
|
overrides: {
|
||||||
|
'3_phase_05_noise_floor': { min: 0.0, max: 0.6, curve: 0.3 },
|
||||||
|
'3_phase_02_beating_depth': { min: 0.0, max: 0.6, curve: 0.3 },
|
||||||
|
'5_master_04_saturation': { min: 0.0, max: 0.5, curve: 0.35 },
|
||||||
|
},
|
||||||
|
mutedOverrides: {},
|
||||||
|
groupCurves: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
// ======================================================================
|
||||||
|
// TIER 3 — Advanced (all 48 params)
|
||||||
|
// ======================================================================
|
||||||
|
{
|
||||||
|
id: 'additive-5',
|
||||||
|
name: '3.1',
|
||||||
|
tier: 3,
|
||||||
|
description: 'Full Spectrum — all 48 params active, no restrictions',
|
||||||
|
active: null, // null = all params active
|
||||||
|
overrides: {},
|
||||||
|
mutedOverrides: {},
|
||||||
|
groupCurves: {},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ADDITIVE_PRESET_TIERS = [
|
||||||
|
{ tier: 1, label: 'Beginner' },
|
||||||
|
{ tier: 2, label: 'Intermediate' },
|
||||||
|
{ tier: 3, label: 'Advanced' },
|
||||||
|
];
|
||||||
213
playground/js/synth/fm-presets.js
Normal file
213
playground/js/synth/fm-presets.js
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
// FM synth presets for NISPS playground
|
||||||
|
// Each preset defines which params are active (ML-controlled) and which are
|
||||||
|
// muted at safe defaults. Param IDs match faustParamAddress() output from
|
||||||
|
// fm-matrix.json (e.g. 'operators_op_1_op1_ratio', 'matrix_m21').
|
||||||
|
//
|
||||||
|
// 3 tiers of progressive complexity across 5 presets.
|
||||||
|
|
||||||
|
export const FM_PRESETS = [
|
||||||
|
// ======================================================================
|
||||||
|
// TIER 1 — Beginner (~12-15 active params)
|
||||||
|
// ======================================================================
|
||||||
|
{
|
||||||
|
id: 'fm-1',
|
||||||
|
name: '1.1',
|
||||||
|
tier: 1,
|
||||||
|
description: 'Simple FM — Op1/Op2 ratio+level, m21 mod index, op1 ADSR, master level',
|
||||||
|
active: [
|
||||||
|
'operators_op_1_op1_ratio',
|
||||||
|
'operators_op_1_op1_level',
|
||||||
|
'operators_op_2_op2_ratio',
|
||||||
|
'operators_op_2_op2_level',
|
||||||
|
'matrix_m21',
|
||||||
|
'operators_op_1_op1_attack',
|
||||||
|
'operators_op_1_op1_decay',
|
||||||
|
'operators_op_1_op1_sustain',
|
||||||
|
'operators_op_1_op1_release',
|
||||||
|
'master_level',
|
||||||
|
'master_stereo_spread',
|
||||||
|
'master_waveform_blend',
|
||||||
|
],
|
||||||
|
overrides: {
|
||||||
|
'operators_op_1_op1_ratio': { min: 0.15, max: 0.65, curve: 0.5 },
|
||||||
|
'operators_op_2_op2_ratio': { min: 0.15, max: 0.65, curve: 0.5 },
|
||||||
|
'matrix_m21': { min: 0.0, max: 0.5, curve: 0.4 },
|
||||||
|
'operators_op_1_op1_attack': { min: 0.0, max: 0.6, curve: 0.35 },
|
||||||
|
'operators_op_1_op1_decay': { min: 0.05, max: 0.5, curve: 0.4 },
|
||||||
|
'operators_op_1_op1_sustain': { min: 0.2, max: 0.9, curve: 0.55 },
|
||||||
|
'operators_op_1_op1_release': { min: 0.1, max: 0.6, curve: 0.45 },
|
||||||
|
'master_level': { min: 0.3, max: 0.85, curve: 0.5 },
|
||||||
|
'master_waveform_blend': { min: 0.0, max: 0.5, curve: 0.4 },
|
||||||
|
},
|
||||||
|
mutedOverrides: {},
|
||||||
|
groupCurves: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 'fm-2',
|
||||||
|
name: '1.2',
|
||||||
|
tier: 1,
|
||||||
|
description: 'Bell Tones — Op1-3 ratios+levels, m21+m31 indices, op1 ADSR, fb1, level',
|
||||||
|
active: [
|
||||||
|
'operators_op_1_op1_ratio',
|
||||||
|
'operators_op_1_op1_level',
|
||||||
|
'operators_op_2_op2_ratio',
|
||||||
|
'operators_op_2_op2_level',
|
||||||
|
'operators_op_3_op3_ratio',
|
||||||
|
'operators_op_3_op3_level',
|
||||||
|
'matrix_m21',
|
||||||
|
'matrix_m31',
|
||||||
|
'feedback_fb1',
|
||||||
|
'operators_op_1_op1_attack',
|
||||||
|
'operators_op_1_op1_decay',
|
||||||
|
'operators_op_1_op1_sustain',
|
||||||
|
'operators_op_1_op1_release',
|
||||||
|
'master_level',
|
||||||
|
],
|
||||||
|
overrides: {
|
||||||
|
'operators_op_1_op1_ratio': { min: 0.15, max: 0.55, curve: 0.5 },
|
||||||
|
'operators_op_2_op2_ratio': { min: 0.2, max: 0.7, curve: 0.55 },
|
||||||
|
'operators_op_3_op3_ratio': { min: 0.2, max: 0.7, curve: 0.55 },
|
||||||
|
'matrix_m21': { min: 0.0, max: 0.4, curve: 0.35 },
|
||||||
|
'matrix_m31': { min: 0.0, max: 0.35, curve: 0.3 },
|
||||||
|
'feedback_fb1': { min: 0.0, max: 0.4, curve: 0.3 },
|
||||||
|
'operators_op_1_op1_attack': { min: 0.0, max: 0.5, curve: 0.3 },
|
||||||
|
'operators_op_1_op1_decay': { min: 0.1, max: 0.6, curve: 0.45 },
|
||||||
|
'operators_op_1_op1_sustain': { min: 0.1, max: 0.7, curve: 0.45 },
|
||||||
|
'operators_op_1_op1_release': { min: 0.15, max: 0.7, curve: 0.5 },
|
||||||
|
'master_level': { min: 0.3, max: 0.8, curve: 0.5 },
|
||||||
|
},
|
||||||
|
mutedOverrides: {},
|
||||||
|
groupCurves: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
// ======================================================================
|
||||||
|
// TIER 2 — Intermediate (~25-30 active params)
|
||||||
|
// ======================================================================
|
||||||
|
{
|
||||||
|
id: 'fm-3',
|
||||||
|
name: '2.1',
|
||||||
|
tier: 2,
|
||||||
|
description: 'Matrix Explorer — all 4 op ratios+levels, 6 cross-mod indices, all 4 ADSRs, LFO, level',
|
||||||
|
active: [
|
||||||
|
// All 4 operator ratios + levels
|
||||||
|
'operators_op_1_op1_ratio',
|
||||||
|
'operators_op_1_op1_level',
|
||||||
|
'operators_op_2_op2_ratio',
|
||||||
|
'operators_op_2_op2_level',
|
||||||
|
'operators_op_3_op3_ratio',
|
||||||
|
'operators_op_3_op3_level',
|
||||||
|
'operators_op_4_op4_ratio',
|
||||||
|
'operators_op_4_op4_level',
|
||||||
|
// 6 cross-modulation indices (half the matrix)
|
||||||
|
'matrix_m21',
|
||||||
|
'matrix_m31',
|
||||||
|
'matrix_m41',
|
||||||
|
'matrix_m32',
|
||||||
|
'matrix_m42',
|
||||||
|
'matrix_m43',
|
||||||
|
// All 4 operator ADSRs (op1 only for now — full for intermediate)
|
||||||
|
'operators_op_1_op1_attack',
|
||||||
|
'operators_op_1_op1_decay',
|
||||||
|
'operators_op_1_op1_sustain',
|
||||||
|
'operators_op_1_op1_release',
|
||||||
|
'operators_op_2_op2_attack',
|
||||||
|
'operators_op_2_op2_decay',
|
||||||
|
'operators_op_2_op2_sustain',
|
||||||
|
'operators_op_2_op2_release',
|
||||||
|
// LFO
|
||||||
|
'global_lfo_rate',
|
||||||
|
'global_lfo_depth',
|
||||||
|
'global_lfo_waveform',
|
||||||
|
// Master
|
||||||
|
'master_level',
|
||||||
|
'master_stereo_spread',
|
||||||
|
],
|
||||||
|
overrides: {
|
||||||
|
'matrix_m21': { min: 0.0, max: 0.5, curve: 0.4 },
|
||||||
|
'matrix_m31': { min: 0.0, max: 0.45, curve: 0.35 },
|
||||||
|
'matrix_m41': { min: 0.0, max: 0.4, curve: 0.35 },
|
||||||
|
'matrix_m32': { min: 0.0, max: 0.4, curve: 0.35 },
|
||||||
|
'matrix_m42': { min: 0.0, max: 0.35, curve: 0.3 },
|
||||||
|
'matrix_m43': { min: 0.0, max: 0.35, curve: 0.3 },
|
||||||
|
'global_lfo_depth': { min: 0.0, max: 0.6, curve: 0.35 },
|
||||||
|
},
|
||||||
|
mutedOverrides: {},
|
||||||
|
groupCurves: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 'fm-4',
|
||||||
|
name: '2.2',
|
||||||
|
tier: 2,
|
||||||
|
description: 'Feedback Machine — all op params, all 4 feedbacks, 4 key matrix indices, waveform blend, saturation, level',
|
||||||
|
active: [
|
||||||
|
// All 4 operator ratios + levels
|
||||||
|
'operators_op_1_op1_ratio',
|
||||||
|
'operators_op_1_op1_level',
|
||||||
|
'operators_op_2_op2_ratio',
|
||||||
|
'operators_op_2_op2_level',
|
||||||
|
'operators_op_3_op3_ratio',
|
||||||
|
'operators_op_3_op3_level',
|
||||||
|
'operators_op_4_op4_ratio',
|
||||||
|
'operators_op_4_op4_level',
|
||||||
|
// All 4 feedbacks
|
||||||
|
'feedback_fb1',
|
||||||
|
'feedback_fb2',
|
||||||
|
'feedback_fb3',
|
||||||
|
'feedback_fb4',
|
||||||
|
// 4 key cross-mod indices
|
||||||
|
'matrix_m21',
|
||||||
|
'matrix_m31',
|
||||||
|
'matrix_m12',
|
||||||
|
'matrix_m34',
|
||||||
|
// Op1 + Op2 ADSR
|
||||||
|
'operators_op_1_op1_attack',
|
||||||
|
'operators_op_1_op1_decay',
|
||||||
|
'operators_op_1_op1_sustain',
|
||||||
|
'operators_op_1_op1_release',
|
||||||
|
'operators_op_2_op2_attack',
|
||||||
|
'operators_op_2_op2_decay',
|
||||||
|
'operators_op_2_op2_sustain',
|
||||||
|
'operators_op_2_op2_release',
|
||||||
|
// Waveform, saturation, master
|
||||||
|
'master_waveform_blend',
|
||||||
|
'master_output_saturation',
|
||||||
|
'master_level',
|
||||||
|
'master_stereo_spread',
|
||||||
|
],
|
||||||
|
overrides: {
|
||||||
|
'feedback_fb1': { min: 0.0, max: 0.6, curve: 0.35 },
|
||||||
|
'feedback_fb2': { min: 0.0, max: 0.6, curve: 0.35 },
|
||||||
|
'feedback_fb3': { min: 0.0, max: 0.5, curve: 0.3 },
|
||||||
|
'feedback_fb4': { min: 0.0, max: 0.5, curve: 0.3 },
|
||||||
|
'master_output_saturation': { min: 0.0, max: 0.5, curve: 0.35 },
|
||||||
|
'matrix_m21': { min: 0.0, max: 0.5, curve: 0.4 },
|
||||||
|
'matrix_m31': { min: 0.0, max: 0.45, curve: 0.35 },
|
||||||
|
'matrix_m12': { min: 0.0, max: 0.4, curve: 0.35 },
|
||||||
|
'matrix_m34': { min: 0.0, max: 0.4, curve: 0.35 },
|
||||||
|
},
|
||||||
|
mutedOverrides: {},
|
||||||
|
groupCurves: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
// ======================================================================
|
||||||
|
// TIER 3 — Advanced (all 55 params)
|
||||||
|
// ======================================================================
|
||||||
|
{
|
||||||
|
id: 'fm-5',
|
||||||
|
name: '3.1',
|
||||||
|
tier: 3,
|
||||||
|
description: 'Full Matrix — all 55 params active, no restrictions',
|
||||||
|
active: null, // null = all params active
|
||||||
|
overrides: {},
|
||||||
|
mutedOverrides: {},
|
||||||
|
groupCurves: {},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const FM_PRESET_TIERS = [
|
||||||
|
{ tier: 1, label: 'Beginner' },
|
||||||
|
{ tier: 2, label: 'Intermediate' },
|
||||||
|
{ tier: 3, label: 'Advanced' },
|
||||||
|
];
|
||||||
Loading…
Reference in a new issue