feat(playground): add volume safety limiter, devmode tame parameter, and voice buildup mitigation
- Insert DynamicsCompressorNode as always-on brick-wall limiter after master gain (threshold -6dB, ratio 20:1, 2ms attack) - Add devmode tame parameter (0-1) controlling range constraining: - ?tame=0.7 URL param (default) or window.setTameLevel(n) - 0 = no mitigation, 1 = strongest constraining - Constrain 20 volume/buildup-sensitive params with safeMin/safeMax: - Envelope release/decay2 times capped to prevent infinite ringing - Envelope gains kept in moderate range - All 5 drive stages (ShpA/B, FB, Out, Cabinet) capped - Output mixer levels given floor and ceiling - Echo feedback and reverb size limited to prevent wash buildup - Fix Comb Filter group count (8, not 9) in comments and color generator
This commit is contained in:
parent
c3577be42c
commit
925ddc5be6
3 changed files with 77 additions and 28 deletions
|
|
@ -8,7 +8,7 @@ import { Controls } from './ui/controls.js';
|
||||||
import { ParamDisplay } from './ui/param-display.js';
|
import { ParamDisplay } from './ui/param-display.js';
|
||||||
import { C15Bridge } from './synth/c15-bridge.js';
|
import { C15Bridge } from './synth/c15-bridge.js';
|
||||||
import { Arpeggiator } from './synth/arpeggiator.js';
|
import { Arpeggiator } from './synth/arpeggiator.js';
|
||||||
import { SYNTH_PARAM_MAP, SYNTH_PARAM_NAMES, SYNTH_PARAM_COLORS } from './synth/param-map.js';
|
import { SYNTH_PARAM_MAP, SYNTH_PARAM_NAMES, SYNTH_PARAM_COLORS, applyTame } from './synth/param-map.js';
|
||||||
|
|
||||||
const N_INPUTS = 2;
|
const N_INPUTS = 2;
|
||||||
const N_VISUAL_OUTPUTS = 20;
|
const N_VISUAL_OUTPUTS = 20;
|
||||||
|
|
@ -43,6 +43,14 @@ let expandVisualBtn;
|
||||||
let c15 = null;
|
let c15 = null;
|
||||||
let arpeggiator = null;
|
let arpeggiator = null;
|
||||||
|
|
||||||
|
// Devmode: tame level (0 = no mitigation, 1 = strongest)
|
||||||
|
// Set via URL ?tame=0.7 or window.setTameLevel(0.7)
|
||||||
|
let tameLevel = parseFloat(new URLSearchParams(location.search).get('tame') ?? '0.7');
|
||||||
|
if (isNaN(tameLevel)) tameLevel = 0.7;
|
||||||
|
tameLevel = Math.max(0, Math.min(1, tameLevel));
|
||||||
|
window.setTameLevel = (v) => { tameLevel = Math.max(0, Math.min(1, v)); console.log(`[NISPS] tame=${tameLevel}`); };
|
||||||
|
window.getTameLevel = () => tameLevel;
|
||||||
|
|
||||||
// --- Init ---
|
// --- Init ---
|
||||||
function init() {
|
function init() {
|
||||||
iml = new IML(N_INPUTS, N_OUTPUTS, [32, 48, 64], 1000, 1.0, 0.00001);
|
iml = new IML(N_INPUTS, N_OUTPUTS, [32, 48, 64], 1000, 1.0, 0.00001);
|
||||||
|
|
@ -207,10 +215,11 @@ function routeOutputs(outputs) {
|
||||||
// Always update visualizer (it's always visible)
|
// Always update visualizer (it's always visible)
|
||||||
visualizer.setParams(outputs);
|
visualizer.setParams(outputs);
|
||||||
|
|
||||||
// If in synth mode, also send to C15
|
// If in synth mode, also send to C15 with tame-level constraining
|
||||||
if (outputMode === 'synth' && c15 && c15.running) {
|
if (outputMode === 'synth' && c15 && c15.running) {
|
||||||
for (let i = 0; i < outputs.length && i < SYNTH_PARAM_MAP.length; i++) {
|
for (let i = 0; i < outputs.length && i < SYNTH_PARAM_MAP.length; i++) {
|
||||||
c15.setParameter(SYNTH_PARAM_MAP[i].id, outputs[i]);
|
const value = applyTame(outputs[i], SYNTH_PARAM_MAP[i], tameLevel);
|
||||||
|
c15.setParameter(SYNTH_PARAM_MAP[i].id, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -129,8 +129,17 @@ export class C15Bridge {
|
||||||
this.masterGain = this.audioContext.createGain();
|
this.masterGain = this.audioContext.createGain();
|
||||||
this.masterGain.gain.value = 0.5;
|
this.masterGain.gain.value = 0.5;
|
||||||
|
|
||||||
|
// Limiter: always-on compressor configured as a brick-wall limiter
|
||||||
|
this.limiter = this.audioContext.createDynamicsCompressor();
|
||||||
|
this.limiter.threshold.value = -6; // dB — start limiting at -6dB
|
||||||
|
this.limiter.knee.value = 3; // dB — soft knee for less pumping
|
||||||
|
this.limiter.ratio.value = 20; // near-infinite ratio = limiter
|
||||||
|
this.limiter.attack.value = 0.002; // 2ms — fast attack catches transients
|
||||||
|
this.limiter.release.value = 0.05; // 50ms — quick release, less pumping
|
||||||
|
|
||||||
this.workletNode.connect(this.masterGain);
|
this.workletNode.connect(this.masterGain);
|
||||||
this.masterGain.connect(this.audioContext.destination);
|
this.masterGain.connect(this.limiter);
|
||||||
|
this.limiter.connect(this.audioContext.destination);
|
||||||
|
|
||||||
if (this.audioContext.state === 'suspended') {
|
if (this.audioContext.state === 'suspended') {
|
||||||
await this.audioContext.resume();
|
await this.audioContext.resume();
|
||||||
|
|
|
||||||
|
|
@ -4,35 +4,42 @@
|
||||||
// envelope modulation depths, discrete switches, dangerous volume/pitch controls
|
// envelope modulation depths, discrete switches, dangerous volume/pitch controls
|
||||||
//
|
//
|
||||||
// Grouped by synthesis section for readability:
|
// Grouped by synthesis section for readability:
|
||||||
// Envelopes (20) → Oscillators (10) → Shapers (12) → Filters (25) →
|
// Envelopes (20) → Oscillators (10) → Shapers (12) → Filters (24) →
|
||||||
// Feedback Mixer (9) → Output Mixer (14) → Cabinet (8) →
|
// Feedback Mixer (9) → Output Mixer (14) → Cabinet (8) →
|
||||||
// Flanger (13) → Echo (7) → Reverb (6) → Unison (3) → Mono (1)
|
// Flanger (13) → Echo (7) → Reverb (6) → Unison (3) → Mono (1)
|
||||||
|
//
|
||||||
|
// safeMin/safeMax: when the tame parameter > 0, the ML output range is
|
||||||
|
// interpolated from [0,1] toward [safeMin,safeMax]. This prevents:
|
||||||
|
// - Volume spikes (drive/level params capped)
|
||||||
|
// - Silence (output levels kept above minimum)
|
||||||
|
// - Voice buildup / wall of sound (envelope release/decay times capped,
|
||||||
|
// reverb size and echo feedback limited)
|
||||||
|
|
||||||
export const SYNTH_PARAM_MAP = [
|
export const SYNTH_PARAM_MAP = [
|
||||||
// --- Envelope A (7) ---
|
// --- Envelope A (7) ---
|
||||||
{ id: 0, name: 'Env_A_Att', label: 'EnvA Att', defaultValue: 0, bipolar: false },
|
{ id: 0, name: 'Env_A_Att', label: 'EnvA Att', defaultValue: 0, bipolar: false },
|
||||||
{ id: 2, name: 'Env_A_Dec_1', label: 'EnvA Dec1', defaultValue: 0.59, bipolar: false },
|
{ id: 2, name: 'Env_A_Dec_1', label: 'EnvA Dec1', defaultValue: 0.59, bipolar: false },
|
||||||
{ id: 4, name: 'Env_A_BP', label: 'EnvA BP', defaultValue: 0.5, bipolar: false },
|
{ id: 4, name: 'Env_A_BP', label: 'EnvA BP', defaultValue: 0.5, bipolar: false },
|
||||||
{ id: 6, name: 'Env_A_Dec_2', label: 'EnvA Dec2', defaultValue: 0.79, bipolar: false },
|
{ id: 6, name: 'Env_A_Dec_2', label: 'EnvA Dec2', defaultValue: 0.79, bipolar: false, safeMax: 0.85 },
|
||||||
{ id: 8, name: 'Env_A_Sus', label: 'EnvA Sus', defaultValue: 0, bipolar: false },
|
{ id: 8, name: 'Env_A_Sus', label: 'EnvA Sus', defaultValue: 0, bipolar: false },
|
||||||
{ id: 10, name: 'Env_A_Rel', label: 'EnvA Rel', defaultValue: 0.53, bipolar: false },
|
{ id: 10, name: 'Env_A_Rel', label: 'EnvA Rel', defaultValue: 0.53, bipolar: false, safeMax: 0.7 },
|
||||||
{ id: 12, name: 'Env_A_Gain', label: 'EnvA Gain', defaultValue: 0, bipolar: true },
|
{ id: 12, name: 'Env_A_Gain', label: 'EnvA Gain', defaultValue: 0, bipolar: true, safeMin: 0.2, safeMax: 0.8 },
|
||||||
|
|
||||||
// --- Envelope B (7) ---
|
// --- Envelope B (7) ---
|
||||||
{ id: 19, name: 'Env_B_Att', label: 'EnvB Att', defaultValue: 0, bipolar: false },
|
{ id: 19, name: 'Env_B_Att', label: 'EnvB Att', defaultValue: 0, bipolar: false },
|
||||||
{ id: 21, name: 'Env_B_Dec_1', label: 'EnvB Dec1', defaultValue: 0.59, bipolar: false },
|
{ id: 21, name: 'Env_B_Dec_1', label: 'EnvB Dec1', defaultValue: 0.59, bipolar: false },
|
||||||
{ id: 23, name: 'Env_B_BP', label: 'EnvB BP', defaultValue: 0.5, bipolar: false },
|
{ id: 23, name: 'Env_B_BP', label: 'EnvB BP', defaultValue: 0.5, bipolar: false },
|
||||||
{ id: 25, name: 'Env_B_Dec_2', label: 'EnvB Dec2', defaultValue: 0.79, bipolar: false },
|
{ id: 25, name: 'Env_B_Dec_2', label: 'EnvB Dec2', defaultValue: 0.79, bipolar: false, safeMax: 0.85 },
|
||||||
{ id: 27, name: 'Env_B_Sus', label: 'EnvB Sus', defaultValue: 0, bipolar: false },
|
{ id: 27, name: 'Env_B_Sus', label: 'EnvB Sus', defaultValue: 0, bipolar: false },
|
||||||
{ id: 29, name: 'Env_B_Rel', label: 'EnvB Rel', defaultValue: 0.53, bipolar: false },
|
{ id: 29, name: 'Env_B_Rel', label: 'EnvB Rel', defaultValue: 0.53, bipolar: false, safeMax: 0.7 },
|
||||||
{ id: 31, name: 'Env_B_Gain', label: 'EnvB Gain', defaultValue: 0, bipolar: true },
|
{ id: 31, name: 'Env_B_Gain', label: 'EnvB Gain', defaultValue: 0, bipolar: true, safeMin: 0.2, safeMax: 0.8 },
|
||||||
|
|
||||||
// --- Envelope C (6) ---
|
// --- Envelope C (6) ---
|
||||||
{ id: 38, name: 'Env_C_Att', label: 'EnvC Att', defaultValue: 0, bipolar: false },
|
{ id: 38, name: 'Env_C_Att', label: 'EnvC Att', defaultValue: 0, bipolar: false },
|
||||||
{ id: 40, name: 'Env_C_Dec_1', label: 'EnvC Dec1', defaultValue: 0.59, bipolar: false },
|
{ id: 40, name: 'Env_C_Dec_1', label: 'EnvC Dec1', defaultValue: 0.59, bipolar: false },
|
||||||
{ id: 42, name: 'Env_C_BP', label: 'EnvC BP', defaultValue: 0.5, bipolar: true },
|
{ id: 42, name: 'Env_C_BP', label: 'EnvC BP', defaultValue: 0.5, bipolar: true },
|
||||||
{ id: 44, name: 'Env_C_Dec_2', label: 'EnvC Dec2', defaultValue: 0.79, bipolar: false },
|
{ id: 44, name: 'Env_C_Dec_2', label: 'EnvC Dec2', defaultValue: 0.79, bipolar: false, safeMax: 0.85 },
|
||||||
{ id: 46, name: 'Env_C_Rel', label: 'EnvC Rel', defaultValue: 0.53, bipolar: false },
|
{ id: 46, name: 'Env_C_Rel', label: 'EnvC Rel', defaultValue: 0.53, bipolar: false, safeMax: 0.7 },
|
||||||
{ id: 297, name: 'Env_C_Sus', label: 'EnvC Sus', defaultValue: 0, bipolar: true },
|
{ id: 297, name: 'Env_C_Sus', label: 'EnvC Sus', defaultValue: 0, bipolar: true },
|
||||||
|
|
||||||
// --- Oscillator A (5) ---
|
// --- Oscillator A (5) ---
|
||||||
|
|
@ -50,7 +57,7 @@ export const SYNTH_PARAM_MAP = [
|
||||||
{ id: 302, name: 'Osc_B_Phase', label: 'OscB Phase', defaultValue: 0, bipolar: true },
|
{ id: 302, name: 'Osc_B_Phase', label: 'OscB Phase', defaultValue: 0, bipolar: true },
|
||||||
|
|
||||||
// --- Shaper A (6) ---
|
// --- Shaper A (6) ---
|
||||||
{ id: 71, name: 'Shp_A_Drive', label: 'ShpA Drive', defaultValue: 0.2, bipolar: false },
|
{ id: 71, name: 'Shp_A_Drive', label: 'ShpA Drive', defaultValue: 0.2, bipolar: false, safeMax: 0.6 },
|
||||||
{ id: 74, name: 'Shp_A_Fold', label: 'ShpA Fold', defaultValue: 0.5, bipolar: false },
|
{ id: 74, name: 'Shp_A_Fold', label: 'ShpA Fold', defaultValue: 0.5, bipolar: false },
|
||||||
{ id: 75, name: 'Shp_A_Asym', label: 'ShpA Asym', defaultValue: 0, bipolar: false },
|
{ id: 75, name: 'Shp_A_Asym', label: 'ShpA Asym', defaultValue: 0, bipolar: false },
|
||||||
{ id: 76, name: 'Shp_A_Mix', label: 'ShpA Mix', defaultValue: 0, bipolar: true },
|
{ id: 76, name: 'Shp_A_Mix', label: 'ShpA Mix', defaultValue: 0, bipolar: true },
|
||||||
|
|
@ -58,14 +65,14 @@ export const SYNTH_PARAM_MAP = [
|
||||||
{ id: 81, name: 'Shp_A_Ring_Mod', label: 'ShpA Ring', defaultValue: 0, bipolar: false },
|
{ id: 81, name: 'Shp_A_Ring_Mod', label: 'ShpA Ring', defaultValue: 0, bipolar: false },
|
||||||
|
|
||||||
// --- Shaper B (6) ---
|
// --- Shaper B (6) ---
|
||||||
{ id: 101, name: 'Shp_B_Drive', label: 'ShpB Drive', defaultValue: 0.2, bipolar: false },
|
{ id: 101, name: 'Shp_B_Drive', label: 'ShpB Drive', defaultValue: 0.2, bipolar: false, safeMax: 0.6 },
|
||||||
{ id: 104, name: 'Shp_B_Fold', label: 'ShpB Fold', defaultValue: 0.5, bipolar: false },
|
{ id: 104, name: 'Shp_B_Fold', label: 'ShpB Fold', defaultValue: 0.5, bipolar: false },
|
||||||
{ id: 105, name: 'Shp_B_Asym', label: 'ShpB Asym', defaultValue: 0, bipolar: false },
|
{ id: 105, name: 'Shp_B_Asym', label: 'ShpB Asym', defaultValue: 0, bipolar: false },
|
||||||
{ id: 106, name: 'Shp_B_Mix', label: 'ShpB Mix', defaultValue: 0, bipolar: true },
|
{ id: 106, name: 'Shp_B_Mix', label: 'ShpB Mix', defaultValue: 0, bipolar: true },
|
||||||
{ id: 108, name: 'Shp_B_FB_Mix', label: 'ShpB FB Mix', defaultValue: 0, bipolar: false },
|
{ id: 108, name: 'Shp_B_FB_Mix', label: 'ShpB FB Mix', defaultValue: 0, bipolar: false },
|
||||||
{ id: 111, name: 'Shp_B_Ring_Mod', label: 'ShpB Ring', defaultValue: 0, bipolar: false },
|
{ id: 111, name: 'Shp_B_Ring_Mod', label: 'ShpB Ring', defaultValue: 0, bipolar: false },
|
||||||
|
|
||||||
// --- Comb Filter (9) ---
|
// --- Comb Filter (8) ---
|
||||||
{ id: 113, name: 'Comb_Flt_In_A_B', label: 'Comb In A/B', defaultValue: 0, bipolar: false },
|
{ id: 113, name: 'Comb_Flt_In_A_B', label: 'Comb In A/B', defaultValue: 0, bipolar: false },
|
||||||
{ id: 115, name: 'Comb_Flt_Pitch', label: 'Comb Pitch', defaultValue: 0.5, bipolar: false },
|
{ id: 115, name: 'Comb_Flt_Pitch', label: 'Comb Pitch', defaultValue: 0.5, bipolar: false },
|
||||||
{ id: 119, name: 'Comb_Flt_Decay', label: 'Comb Decay', defaultValue: 0, bipolar: true },
|
{ id: 119, name: 'Comb_Flt_Decay', label: 'Comb Decay', defaultValue: 0, bipolar: true },
|
||||||
|
|
@ -99,30 +106,30 @@ export const SYNTH_PARAM_MAP = [
|
||||||
{ id: 158, name: 'FB_Mix_SVF', label: 'FB SVF', defaultValue: 0, bipolar: true },
|
{ id: 158, name: 'FB_Mix_SVF', label: 'FB SVF', defaultValue: 0, bipolar: true },
|
||||||
{ id: 160, name: 'FB_Mix_FX', label: 'FB FX', defaultValue: 0, bipolar: true },
|
{ id: 160, name: 'FB_Mix_FX', label: 'FB FX', defaultValue: 0, bipolar: true },
|
||||||
{ id: 162, name: 'FB_Mix_Rvb', label: 'FB Reverb', defaultValue: 0.5, bipolar: false },
|
{ id: 162, name: 'FB_Mix_Rvb', label: 'FB Reverb', defaultValue: 0.5, bipolar: false },
|
||||||
{ id: 164, name: 'FB_Mix_Drive', label: 'FB Drive', defaultValue: 0.286, bipolar: false },
|
{ id: 164, name: 'FB_Mix_Drive', label: 'FB Drive', defaultValue: 0.286, bipolar: false, safeMax: 0.5 },
|
||||||
{ id: 166, name: 'FB_Mix_Fold', label: 'FB Fold', defaultValue: 0.5, bipolar: false },
|
{ id: 166, name: 'FB_Mix_Fold', label: 'FB Fold', defaultValue: 0.5, bipolar: false },
|
||||||
{ id: 167, name: 'FB_Mix_Asym', label: 'FB Asym', defaultValue: 0, bipolar: false },
|
{ id: 167, name: 'FB_Mix_Asym', label: 'FB Asym', defaultValue: 0, bipolar: false },
|
||||||
{ id: 299, name: 'FB_Mix_Lvl', label: 'FB Level', defaultValue: 0.38, bipolar: false },
|
{ id: 299, name: 'FB_Mix_Lvl', label: 'FB Level', defaultValue: 0.38, bipolar: false },
|
||||||
{ id: 346, name: 'FB_Mix_Osc', label: 'FB Osc', defaultValue: 0, bipolar: true },
|
{ id: 346, name: 'FB_Mix_Osc', label: 'FB Osc', defaultValue: 0, bipolar: true },
|
||||||
|
|
||||||
// --- Output Mixer (14) ---
|
// --- Output Mixer (14) ---
|
||||||
{ id: 169, name: 'Out_Mix_A_Lvl', label: 'Out A Lvl', defaultValue: 0.75, bipolar: true },
|
{ id: 169, name: 'Out_Mix_A_Lvl', label: 'Out A Lvl', defaultValue: 0.75, bipolar: true, safeMin: 0.15, safeMax: 0.85 },
|
||||||
{ id: 171, name: 'Out_Mix_A_Pan', label: 'Out A Pan', defaultValue: 0, bipolar: true },
|
{ id: 171, name: 'Out_Mix_A_Pan', label: 'Out A Pan', defaultValue: 0, bipolar: true },
|
||||||
{ id: 172, name: 'Out_Mix_B_Lvl', label: 'Out B Lvl', defaultValue: 0, bipolar: true },
|
{ id: 172, name: 'Out_Mix_B_Lvl', label: 'Out B Lvl', defaultValue: 0, bipolar: true, safeMin: 0.15, safeMax: 0.85 },
|
||||||
{ id: 174, name: 'Out_Mix_B_Pan', label: 'Out B Pan', defaultValue: 0, bipolar: true },
|
{ id: 174, name: 'Out_Mix_B_Pan', label: 'Out B Pan', defaultValue: 0, bipolar: true },
|
||||||
{ id: 175, name: 'Out_Mix_Comb_Lvl', label: 'Out Comb Lvl', defaultValue: 0, bipolar: true },
|
{ id: 175, name: 'Out_Mix_Comb_Lvl', label: 'Out Comb Lvl', defaultValue: 0, bipolar: true, safeMin: 0.15, safeMax: 0.85 },
|
||||||
{ id: 177, name: 'Out_Mix_Comb_Pan', label: 'Out Comb Pan', defaultValue: 0, bipolar: true },
|
{ id: 177, name: 'Out_Mix_Comb_Pan', label: 'Out Comb Pan', defaultValue: 0, bipolar: true },
|
||||||
{ id: 178, name: 'Out_Mix_SVF_Lvl', label: 'Out SVF Lvl', defaultValue: 0, bipolar: true },
|
{ id: 178, name: 'Out_Mix_SVF_Lvl', label: 'Out SVF Lvl', defaultValue: 0, bipolar: true, safeMin: 0.15, safeMax: 0.85 },
|
||||||
{ id: 180, name: 'Out_Mix_SVF_Pan', label: 'Out SVF Pan', defaultValue: 0, bipolar: true },
|
{ id: 180, name: 'Out_Mix_SVF_Pan', label: 'Out SVF Pan', defaultValue: 0, bipolar: true },
|
||||||
{ id: 181, name: 'Out_Mix_Drive', label: 'Out Drive', defaultValue: 0, bipolar: false },
|
{ id: 181, name: 'Out_Mix_Drive', label: 'Out Drive', defaultValue: 0, bipolar: false, safeMax: 0.5 },
|
||||||
{ id: 183, name: 'Out_Mix_Fold', label: 'Out Fold', defaultValue: 0.5, bipolar: false },
|
{ id: 183, name: 'Out_Mix_Fold', label: 'Out Fold', defaultValue: 0.5, bipolar: false },
|
||||||
{ id: 184, name: 'Out_Mix_Asym', label: 'Out Asym', defaultValue: 0, bipolar: false },
|
{ id: 184, name: 'Out_Mix_Asym', label: 'Out Asym', defaultValue: 0, bipolar: false },
|
||||||
{ id: 185, name: 'Out_Mix_Lvl', label: 'Out Level', defaultValue: 0.38, bipolar: false },
|
{ id: 185, name: 'Out_Mix_Lvl', label: 'Out Level', defaultValue: 0.38, bipolar: false, safeMin: 0.15, safeMax: 0.75 },
|
||||||
{ id: 187, name: 'Out_Mix_Key_Pan', label: 'Out KeyPan', defaultValue: 0, bipolar: false },
|
{ id: 187, name: 'Out_Mix_Key_Pan', label: 'Out KeyPan', defaultValue: 0, bipolar: false },
|
||||||
{ id: 362, name: 'Out_Mix_To_FX', label: 'Out ToFX', defaultValue: 0, bipolar: false },
|
{ id: 362, name: 'Out_Mix_To_FX', label: 'Out ToFX', defaultValue: 0, bipolar: false },
|
||||||
|
|
||||||
// --- Cabinet (8) ---
|
// --- Cabinet (8) ---
|
||||||
{ id: 188, name: 'Cabinet_Drive', label: 'Cab Drive', defaultValue: 0.4, bipolar: false },
|
{ id: 188, name: 'Cabinet_Drive', label: 'Cab Drive', defaultValue: 0.4, bipolar: false, safeMax: 0.6 },
|
||||||
{ id: 190, name: 'Cabinet_Fold', label: 'Cab Fold', defaultValue: 0.25, bipolar: false },
|
{ id: 190, name: 'Cabinet_Fold', label: 'Cab Fold', defaultValue: 0.25, bipolar: false },
|
||||||
{ id: 191, name: 'Cabinet_Asym', label: 'Cab Asym', defaultValue: 0.25, bipolar: false },
|
{ id: 191, name: 'Cabinet_Asym', label: 'Cab Asym', defaultValue: 0.25, bipolar: false },
|
||||||
{ id: 192, name: 'Cabinet_Tilt', label: 'Cab Tilt', defaultValue: 0.5, bipolar: true },
|
{ id: 192, name: 'Cabinet_Tilt', label: 'Cab Tilt', defaultValue: 0.5, bipolar: true },
|
||||||
|
|
@ -149,14 +156,14 @@ export const SYNTH_PARAM_MAP = [
|
||||||
// --- Echo (7) ---
|
// --- Echo (7) ---
|
||||||
{ id: 225, name: 'Echo_Time', label: 'Echo Time', defaultValue: 0.433, bipolar: false },
|
{ id: 225, name: 'Echo_Time', label: 'Echo Time', defaultValue: 0.433, bipolar: false },
|
||||||
{ id: 227, name: 'Echo_Stereo', label: 'Echo Stereo', defaultValue: 0, bipolar: true },
|
{ id: 227, name: 'Echo_Stereo', label: 'Echo Stereo', defaultValue: 0, bipolar: true },
|
||||||
{ id: 229, name: 'Echo_Feedback', label: 'Echo FB', defaultValue: 0.5, bipolar: false },
|
{ id: 229, name: 'Echo_Feedback', label: 'Echo FB', defaultValue: 0.5, bipolar: false, safeMax: 0.75 },
|
||||||
{ id: 231, name: 'Echo_Cross_FB', label: 'Echo XFB', defaultValue: 0.5, bipolar: false },
|
{ id: 231, name: 'Echo_Cross_FB', label: 'Echo XFB', defaultValue: 0.5, bipolar: false },
|
||||||
{ id: 232, name: 'Echo_Hi_Cut', label: 'Echo HiCut', defaultValue: 0.75, bipolar: false },
|
{ id: 232, name: 'Echo_Hi_Cut', label: 'Echo HiCut', defaultValue: 0.75, bipolar: false },
|
||||||
{ id: 233, name: 'Echo_Mix', label: 'Echo Mix', defaultValue: 0, bipolar: false },
|
{ id: 233, name: 'Echo_Mix', label: 'Echo Mix', defaultValue: 0, bipolar: false },
|
||||||
{ id: 342, name: 'Echo_Send', label: 'Echo Send', defaultValue: 1, bipolar: false },
|
{ id: 342, name: 'Echo_Send', label: 'Echo Send', defaultValue: 1, bipolar: false },
|
||||||
|
|
||||||
// --- Reverb (6) ---
|
// --- Reverb (6) ---
|
||||||
{ id: 235, name: 'Reverb_Size', label: 'Verb Size', defaultValue: 0.33, bipolar: false },
|
{ id: 235, name: 'Reverb_Size', label: 'Verb Size', defaultValue: 0.33, bipolar: false, safeMax: 0.7 },
|
||||||
{ id: 237, name: 'Reverb_Pre_Dly', label: 'Verb PreDly', defaultValue: 0.33, bipolar: false },
|
{ id: 237, name: 'Reverb_Pre_Dly', label: 'Verb PreDly', defaultValue: 0.33, bipolar: false },
|
||||||
{ id: 238, name: 'Reverb_Color', label: 'Verb Color', defaultValue: 0.5, bipolar: false },
|
{ id: 238, name: 'Reverb_Color', label: 'Verb Color', defaultValue: 0.5, bipolar: false },
|
||||||
{ id: 240, name: 'Reverb_Chorus', label: 'Verb Chorus', defaultValue: 0.25, bipolar: false },
|
{ id: 240, name: 'Reverb_Chorus', label: 'Verb Chorus', defaultValue: 0.25, bipolar: false },
|
||||||
|
|
@ -196,8 +203,8 @@ function generateParamColors() {
|
||||||
...hslRange(320, 75, 55, 6),
|
...hslRange(320, 75, 55, 6),
|
||||||
// Shaper B (6) - rose
|
// Shaper B (6) - rose
|
||||||
...hslRange(340, 70, 55, 6),
|
...hslRange(340, 70, 55, 6),
|
||||||
// Comb Filter (9) - cyan
|
// Comb Filter (8) - cyan
|
||||||
...hslRange(180, 70, 50, 9, true),
|
...hslRange(180, 70, 50, 8, true),
|
||||||
// State Variable Filter (9) - blue
|
// State Variable Filter (9) - blue
|
||||||
...hslRange(210, 75, 55, 9, true),
|
...hslRange(210, 75, 55, 9, true),
|
||||||
// Gap Filter (6) - teal
|
// Gap Filter (6) - teal
|
||||||
|
|
@ -235,3 +242,27 @@ function hslRange(hue, sat, lightBase, count, varyHue = false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SYNTH_PARAM_COLORS = generateParamColors();
|
export const SYNTH_PARAM_COLORS = generateParamColors();
|
||||||
|
|
||||||
|
// --- Tame parameter: constrain ML outputs to safe ranges ---
|
||||||
|
// tameLevel 0 = no constraint (full 0-1), 1 = maximum constraint (safeMin-safeMax)
|
||||||
|
// Params without safeMin/safeMax are unaffected.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply tame-level range constraining to a raw ML output value.
|
||||||
|
* @param {number} rawValue - ML output in [0, 1]
|
||||||
|
* @param {object} paramEntry - entry from SYNTH_PARAM_MAP
|
||||||
|
* @param {number} tameLevel - 0 (no mitigation) to 1 (strongest)
|
||||||
|
* @returns {number} constrained value in [0, 1]
|
||||||
|
*/
|
||||||
|
export function applyTame(rawValue, paramEntry, tameLevel) {
|
||||||
|
if (tameLevel <= 0) return rawValue;
|
||||||
|
const lo = paramEntry.safeMin;
|
||||||
|
const hi = paramEntry.safeMax;
|
||||||
|
if (lo === undefined && hi === undefined) return rawValue;
|
||||||
|
|
||||||
|
// Interpolate range bounds toward safe limits based on tameLevel
|
||||||
|
const effectiveMin = lo !== undefined ? lo * tameLevel : 0;
|
||||||
|
const effectiveMax = hi !== undefined ? 1 - (1 - hi) * tameLevel : 1;
|
||||||
|
|
||||||
|
return effectiveMin + rawValue * (effectiveMax - effectiveMin);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue