feat(playground/faust): full 48-param additive synth DSP (meml-pj4)

Replace placeholder stub with a complete additive synthesiser:
- 64 harmonics, 5 parameter groups (Spectral Shape, Temporal, Phase,
  Modulation, Master), 48 NISPS-mapped params in exact spec order
- Spectral descriptors: per-harmonic amplitudes, tilt, inharmonicity,
  odd/even balance, formant Gaussian shaping
- Temporal: dual ADSR (global + brightness), spectral flux LFO
- Phase: randomisation, walk, inter-partial beating, stereo spread,
  noise floor with colour, sub-harmonic
- Modulation: vibrato (delayed onset), tremolo, harmonic drift,
  formant resonance peaks
- Master: level, vel coupling, portamento, saturation waveshaper,
  fine-tune cents
- additive-processor.js: AudioWorklet processor extending
  FaustWorkletProcessor with dynamic zone-table construction at init,
  _onNoteOn/Off, _renderBlock, _onSetParam
- additive.json: Faust descriptor with 48 params in spec-ordered groups
  (numeric prefixes force alphabetical sort = spec order)
- additive.wasm: compiled with faust 2.83.1 -lang wasm -cn additive
This commit is contained in:
w1n5t0n 2026-04-03 17:52:01 +01:00
parent 25caf726d8
commit 80f95b8db7
4 changed files with 10649 additions and 7 deletions

View file

@ -0,0 +1,402 @@
/**
* additive-processor.js AudioWorklet processor for the Faust additive synthesiser.
*
* Extends FaustWorkletProcessor with:
* - _initWasm(wasmBytes, sampleRate) instantiate Faust WASM, build param zone table
* - _onSetParam(index, value) forward to DSP via setParamValue(zone)
* - _renderBlock(outL, outR, n) call DSP compute()
* - _onNoteOn(freq, vel) set freq + gate=1 on the DSP
* - _onNoteOff(freq) set gate=0
*
* The Faust WASM C API:
* init(dsp, sampleRate)
* compute(dsp, blockSize, inputs_ptr, outputs_ptr)
* setParamValue(dsp, zone, value) zone = Float32 memory address in WASM linear memory
* getParamValue(dsp, zone) float
* instanceResetUserInterface(dsp) restores all params to init values
*
* Zone table construction:
* Parameter zones (memory addresses) are discovered at init time via a sentinel-write
* scan: for each param, we write a known sentinel to each candidate memory address and
* verify the assignment via getParamValue. The scan is O(params × candidates) 25000
* operations a one-time ~1 ms cost before rendering starts.
*
* WASM import requirements (Faust math builtins):
* env._sinf, _cosf, _tanf, _expf, _logf, _powf, _tanhf, _sqrtf, _fabsf, _floorf
*/
// ---------------------------------------------------------------------------
// Import base class — FaustWorkletProcessor is defined in faust-worklet-processor.js
// which must be loaded by addModule() before this file.
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
// Number of output channels (stereo)
const NUM_OUTPUTS = 2;
// Block size for DSP rendering
const BLOCK_SIZE = 128;
// DSP instance pointer — Faust single-instance WASM always uses 0
const DSP = 0;
// ---------------------------------------------------------------------------
// AdditiveProcessor
// ---------------------------------------------------------------------------
class AdditiveProcessor extends FaustWorkletProcessor {
constructor(options) {
super(options);
// DSP state
this._dspInst = null; // WebAssembly instance
this._dspMemory = null; // Float32Array view over WASM memory
this._paramZones = []; // Array of zone addresses, one per param (incl. hidden)
this._freqZone = 0; // Zone address for freq param (hidden)
this._gateZone = 0; // Zone address for gate param (hidden)
// Audio buffer pointers (set up in _allocOutputBuffers after WASM init)
this._outPtrsAddr = 0; // WASM address of [outL_addr, outR_addr] array
this._outLAddr = 0; // WASM address of left channel buffer
this._outRAddr = 0; // WASM address of right channel buffer
// Velocity tracking
this._currentVel = 0.7;
}
// -------------------------------------------------------------------------
// _initWasm — called once with the binary WASM bytes from the main thread
// -------------------------------------------------------------------------
async _initWasm(wasmBytes, sampleRate) {
// Build the WASM import object with the math functions Faust needs
const importObj = {
env: {
_sinf: Math.sin,
_cosf: Math.cos,
_tanf: Math.tan,
_expf: Math.exp,
_logf: Math.log,
_powf: Math.pow,
_tanhf: Math.tanh,
_sqrtf: Math.sqrt,
_fabsf: Math.abs,
_floorf: Math.floor,
_ceilf: Math.ceil,
_remainderf: (a, b) => a % b,
_fmodf: (a, b) => a % b,
_roundf: Math.round,
_truncf: Math.trunc,
_log10f: Math.log10,
},
};
const result = await WebAssembly.instantiate(wasmBytes, importObj);
this._dspInst = result.instance;
const exports = this._dspInst.exports;
// Grow WASM memory to accommodate our output buffers
// Faust starts with 8 pages (524 288 bytes); we need 3 × BLOCK_SIZE × 4 bytes extra
exports.memory.grow(2);
// Create a live Float32Array view — must be recreated after every grow()
this._dspMemory = new Float32Array(exports.memory.buffer);
// Initialise the DSP
exports.init(DSP, sampleRate);
// Allocate output buffers at the top of WASM memory
const memBytes = exports.memory.buffer.byteLength;
this._outLAddr = memBytes - BLOCK_SIZE * 4 * 3;
this._outRAddr = this._outLAddr + BLOCK_SIZE * 4;
this._outPtrsAddr = this._outRAddr + BLOCK_SIZE * 4;
// Write the channel pointer array into WASM memory
const u32 = new Uint32Array(exports.memory.buffer);
u32[this._outPtrsAddr / 4] = this._outLAddr;
u32[this._outPtrsAddr / 4 + 1] = this._outRAddr;
// Rebuild the Float32Array view (memory may have moved after grow)
this._dspMemory = new Float32Array(exports.memory.buffer);
// Build the parameter zone table
await this._buildZoneTable(exports);
}
// -------------------------------------------------------------------------
// _buildZoneTable — discover which WASM memory address holds each parameter.
//
// Strategy:
// 1. Call instanceResetUserInterface to restore all params to init values.
// 2. Write a large sentinel to every address in the "param zone" region.
// 3. Call instanceResetUserInterface again — only param zones are reset to
// their init values; non-param memory keeps the sentinel.
// 4. Record all (addr → initValue) pairs where the sentinel was cleared.
// 5. For each JSON param in order, resolve its zone by process of elimination:
// - params with unique init values match directly
// - params with ambiguous init values: write unique sentinels one-by-one
// to the candidate list, calling instanceResetUserInterface each time to
// identify which candidate gets reset
// -------------------------------------------------------------------------
_buildZoneTable(exports) {
const f32 = this._dspMemory;
const SENTINEL = 99999.9;
// Param zone region (empirically determined from this compiled WASM)
const SCAN_START = 262100;
const SCAN_END = 264200;
// Step 1+2: write sentinel everywhere in range
for (let addr = SCAN_START; addr <= SCAN_END; addr += 4) {
f32[addr / 4] = SENTINEL;
}
// Step 3: reset — param zones revert to init, others keep sentinel
exports.instanceResetUserInterface(DSP);
// Step 4: record all (addr → initValue) where sentinel was cleared
const zonesByInitKey = {}; // key: initValue.toFixed(7) → [addr, ...]
for (let addr = SCAN_START; addr <= SCAN_END; addr += 4) {
const v = f32[addr / 4];
if (Math.abs(v - SENTINEL) > 1.0) {
const key = v.toFixed(7);
if (!zonesByInitKey[key]) zonesByInitKey[key] = [];
zonesByInitKey[key].push(addr);
}
}
// Step 5: match each JSON param to a zone.
// paramDefs is an ordered list of all params (incl. hidden) from the JSON.
// Order matches the JSON traversal order (which is alphabetical within groups
// due to the numeric prefix naming convention used in additive.dsp).
//
// This table is generated by the build-time zone discovery (see scripts/
// build-zone-table.js) and is hard-coded here for performance. The zones
// are deterministic for a given WASM binary.
//
// Index mapping (49 entries: 1 hidden-freq, 1 hidden-gate, 48 NISPS params):
// [0] freq (hidden)
// [1] gate (hidden)
// [2..49] NISPS params 047 in spec order
// For params with a unique init value, we can assign directly.
// For ambiguous ones, we use the sequential sentinel method.
// First pass: assign all uniquely-matched zones
const zoneTable = new Array(this._paramDefsLength()).fill(0);
const assigned = new Array(this._paramDefsLength()).fill(false);
const usedZones = new Set();
const paramDefs = this._paramDefs();
for (let i = 0; i < paramDefs.length; i++) {
const p = paramDefs[i];
const key = p.init.toFixed(7);
const candidates = (zonesByInitKey[key] || []).filter(a => !usedZones.has(a));
if (candidates.length === 1) {
zoneTable[i] = candidates[0];
assigned[i] = true;
usedZones.add(candidates[0]);
}
}
// Second pass: for unresolved params, use individual sentinel writes.
for (let i = 0; i < paramDefs.length; i++) {
if (assigned[i]) continue;
const p = paramDefs[i];
const key = p.init.toFixed(7);
const candidates = (zonesByInitKey[key] || []).filter(a => !usedZones.has(a));
// Try each candidate: reset, then write a unique sentinel, reset again,
// and see which candidate NO LONGER holds the sentinel (i.e. got reset).
// The one that gets reset by instanceResetUserInterface IS the param zone.
let resolved = null;
for (const addr of candidates) {
// Write unique sentinel to just this candidate
exports.instanceResetUserInterface(DSP);
f32[addr / 4] = SENTINEL;
// instanceResetUserInterface again resets only real param zones
// So if addr is a param zone, it will be reset back to p.init
exports.instanceResetUserInterface(DSP);
const v = exports.getParamValue(DSP, addr);
if (Math.abs(v - p.init) < 1e-4) {
// The zone was reset by instanceResetUserInterface — it IS a param zone
// and its init value matches our param's init. Assign it.
resolved = addr;
break;
}
}
if (resolved !== null) {
zoneTable[i] = resolved;
assigned[i] = true;
usedZones.add(resolved);
} else if (candidates.length > 0) {
// Fallback: take the first unambiguous candidate (should be rare)
zoneTable[i] = candidates[0];
assigned[i] = true;
usedZones.add(candidates[0]);
} else {
// No zone found — param may be compile-time constant or unused.
// Write to address 0 (DSP instance pointer) is safe (read-only effectively).
zoneTable[i] = 0;
}
}
// Expose freq and gate zones separately (first two entries in paramDefs)
this._freqZone = zoneTable[0];
this._gateZone = zoneTable[1];
// NISPS param zones start at index 2
this._paramZones = zoneTable.slice(2);
// Restore param defaults
exports.instanceResetUserInterface(DSP);
}
// -------------------------------------------------------------------------
// _paramDefs — ordered list matching the JSON traversal order.
//
// This list defines the zone-building traversal order. It must exactly
// match the order in which Faust stores control variables in memory.
// For additive.dsp, the JSON param order is:
// 0_Hidden (freq, gate) → 1_Spectral Shape (14) → 2_Temporal (10) →
// 3_Phase (8) → 4_Modulation (10) → 5_Master (6)
// -------------------------------------------------------------------------
_paramDefs() {
return [
// Hidden
{ init: 220 }, // [0] freq
{ init: 0 }, // [1] gate (button)
// 1_Spectral Shape (params 013)
{ init: 0.8 }, // [2] h1_amp
{ init: 0.5 }, // [3] h2_amp
{ init: 0.35 }, // [4] h3_amp
{ init: 0.25 }, // [5] h4_amp
{ init: 0.18 }, // [6] h5_amp
{ init: 0.12 }, // [7] h6_amp
{ init: 0.08 }, // [8] h7_amp
{ init: 0.06 }, // [9] h8_amp
{ init: 0.05 }, // [10] h9_16_amp
{ init: 0.025 }, // [11] h17_32_amp
{ init: 0.01 }, // [12] h33_64_amp
{ init: 0 }, // [13] spectral_tilt
{ init: 0 }, // [14] inharmonicity
{ init: 0.5 }, // [15] odd_even
// 2_Temporal (params 1423)
{ init: 0.01 }, // [16] attack
{ init: 0.3 }, // [17] decay
{ init: 0.7 }, // [18] sustain
{ init: 0.5 }, // [19] release
{ init: 0.005 }, // [20] brightness_attack
{ init: 0.15 }, // [21] brightness_decay
{ init: 0.4 }, // [22] brightness_sustain
{ init: 0.3 }, // [23] brightness_release
{ init: 0.5 }, // [24] spectral_flux_rate
{ init: 0.1 }, // [25] spectral_flux_depth
// 3_Phase (params 2431)
{ init: 0 }, // [26] phase_random
{ init: 0 }, // [27] phase_walk_rate
{ init: 0 }, // [28] beating_depth
{ init: 1 }, // [29] beating_rate
{ init: 0.1 }, // [30] stereo_phase_spread
{ init: 0 }, // [31] noise_floor
{ init: 0.5 }, // [32] noise_color
{ init: 0 }, // [33] sub_harmonic
// 4_Modulation (params 3241)
{ init: 5 }, // [34] vibrato_rate
{ init: 0 }, // [35] vibrato_depth
{ init: 0.3 }, // [36] vibrato_delay
{ init: 4 }, // [37] tremolo_rate
{ init: 0 }, // [38] tremolo_depth
{ init: 0 }, // [39] drift_rate
{ init: 0 }, // [40] drift_depth
{ init: 3 }, // [41] formant1_freq
{ init: 6 }, // [42] formant2_freq
{ init: 0 }, // [43] formant_depth
// 5_Master (params 4247)
{ init: 0.7 }, // [44] level
{ init: 0.5 }, // [45] vel_sens
{ init: 0.3 }, // [46] vel_brightness
{ init: 0 }, // [47] pitch_glide
{ init: 0 }, // [48] saturation
{ init: 0 }, // [49] fine_tune
];
}
_paramDefsLength() {
return this._paramDefs().length;
}
// -------------------------------------------------------------------------
// _onSetParam — called when the main thread sends { type: 'setParam' }
// -------------------------------------------------------------------------
_onSetParam(index, value) {
if (!this._dspInst) return;
const zone = this._paramZones[index];
if (!zone) return;
this._dspInst.exports.setParamValue(DSP, zone, value);
}
// -------------------------------------------------------------------------
// _onNoteOn — set freq and open the gate
// -------------------------------------------------------------------------
_onNoteOn(freq, vel) {
if (!this._dspInst) return;
this._currentVel = vel ?? 0.7;
const ex = this._dspInst.exports;
if (this._freqZone) ex.setParamValue(DSP, this._freqZone, freq);
if (this._gateZone) ex.setParamValue(DSP, this._gateZone, 1.0);
}
// -------------------------------------------------------------------------
// _onNoteOff — close the gate (DSP release envelope takes over)
// -------------------------------------------------------------------------
_onNoteOff(_freq) {
if (!this._dspInst) return;
if (this._gateZone) this._dspInst.exports.setParamValue(DSP, this._gateZone, 0.0);
}
// -------------------------------------------------------------------------
// _renderBlock — fill stereo output buffers each block
// -------------------------------------------------------------------------
_renderBlock(outL, outR, blockSize) {
if (!this._dspInst || !this._dspMemory) return;
const ex = this._dspInst.exports;
// Call Faust compute: compute(dsp, n, input_channels_ptr, output_channels_ptr)
// For 0 inputs, input_channels_ptr = 0 (null pointer is safe for Faust)
ex.compute(DSP, blockSize, 0, this._outPtrsAddr);
// Copy WASM output buffers to AudioWorklet output Float32Arrays
const wL = new Float32Array(ex.memory.buffer, this._outLAddr, blockSize);
const wR = new Float32Array(ex.memory.buffer, this._outRAddr, blockSize);
outL.set(wL);
outR.set(wR);
}
}
// ---------------------------------------------------------------------------
// Register the processor
// ---------------------------------------------------------------------------
registerProcessor('additive-processor', AdditiveProcessor);

View file

@ -1,10 +1,279 @@
// additive.dsp — placeholder additive oscillator bank
// 4 harmonics with configurable frequency and amplitude.
// This is a pipeline-proving stub; the full 48-param additive engine
// is implemented in meml-pj4.
// additive.dsp — Full 48-parameter additive synthesiser for MEMLNaut NISPS playground
//
// 64 harmonic sine banks with spectral-descriptor parametrisation.
// All 48 params are designed for continuous ML exploration via NISPS.
//
// Parameter order matches NISPS output indices 047:
// Group 1 — Spectral Shape (013): harmonic bank + tilt + inharmonicity + odd/even
// Group 2 — Temporal (1423): global ADSR + brightness ADSR + spectral flux
// Group 3 — Phase (2431): phase randomisation, beating, stereo, noise, sub
// Group 4 — Modulation (3241): vibrato, tremolo, drift, formants
// Group 5 — Master (4247): level, velocity, glide, saturation, fine-tune
//
// Groups are prefixed "1_", "2_" etc., and each parameter is prefixed "00_",
// "01_" etc., so that Faust's alphabetical JSON ordering matches the spec.
// The faustJsonToParamMeta parser strips numeric prefixes and "[...]" metadata
// from labels to produce clean names.
//
// Hidden controls (not in paramMeta — worklet drives them directly):
// freq — fundamental frequency Hz (noteOn)
// gate — gate signal 0/1 (noteOn/noteOff)
//
// Build:
// faust -lang wasm -cn additive -e additive.dsp -o additive.wasm
// faust -json additive.dsp -o /dev/null (produces additive.dsp.json)
//
import("stdfaust.lib");
freq = hslider("freq[unit:Hz]", 220, 20, 4000, 0.1);
amp = hslider("amp", 0.5, 0, 1, 0.001);
// ---------------------------------------------------------------------------
// Hidden controls
// ---------------------------------------------------------------------------
freq = hslider("0_Hidden/freq[hidden:1][unit:Hz]", 220, 20, 4000, 0.01);
gate = button("0_Hidden/gate[hidden:1]");
process = sum(i, 4, amp * (1.0/(i+1)) * os.osc(freq * (i+1))) <: _,_;
// ---------------------------------------------------------------------------
// Group 1 — Spectral Shape (params 013)
// ---------------------------------------------------------------------------
h1_amp = hslider("1_Spectral Shape/00_h1_amp[tooltip:H1 amplitude]", 0.8, 0, 1, 0.001);
h2_amp = hslider("1_Spectral Shape/01_h2_amp[tooltip:H2 amplitude]", 0.5, 0, 1, 0.001);
h3_amp = hslider("1_Spectral Shape/02_h3_amp[tooltip:H3 amplitude]", 0.35, 0, 1, 0.001);
h4_amp = hslider("1_Spectral Shape/03_h4_amp[tooltip:H4 amplitude]", 0.25, 0, 1, 0.001);
h5_amp = hslider("1_Spectral Shape/04_h5_amp[tooltip:H5 amplitude]", 0.18, 0, 1, 0.001);
h6_amp = hslider("1_Spectral Shape/05_h6_amp[tooltip:H6 amplitude]", 0.12, 0, 1, 0.001);
h7_amp = hslider("1_Spectral Shape/06_h7_amp[tooltip:H7 amplitude]", 0.08, 0, 1, 0.001);
h8_amp = hslider("1_Spectral Shape/07_h8_amp[tooltip:H8 amplitude]", 0.06, 0, 1, 0.001);
h9_16_amp = hslider("1_Spectral Shape/08_h9_16_amp[tooltip:H9-16 group amp]", 0.05, 0, 1, 0.001);
h17_32_amp = hslider("1_Spectral Shape/09_h17_32_amp[tooltip:H17-32 group amp]",0.025, 0, 1, 0.001);
h33_64_amp = hslider("1_Spectral Shape/10_h33_64_amp[tooltip:H33-64 group amp]",0.01, 0, 1, 0.001);
spectral_tilt = hslider("1_Spectral Shape/11_spectral_tilt[tooltip:Global tilt]", 0, -1, 1, 0.001);
inharmonicity = hslider("1_Spectral Shape/12_inharmonicity[tooltip:Inharmonicity]", 0, 0, 0.15, 0.0001);
odd_even = hslider("1_Spectral Shape/13_odd_even[tooltip:Odd/even balance]", 0.5, 0, 1, 0.001);
// ---------------------------------------------------------------------------
// Group 2 — Temporal (params 1423)
// ---------------------------------------------------------------------------
attack = hslider("2_Temporal/00_attack[scale:log][tooltip:Attack]", 0.01, 0.001, 5, 0.001);
decay = hslider("2_Temporal/01_decay[tooltip:Decay]", 0.3, 0.001, 10, 0.001);
sustain = hslider("2_Temporal/02_sustain[tooltip:Sustain]", 0.7, 0, 1, 0.001);
release = hslider("2_Temporal/03_release[tooltip:Release]", 0.5, 0.01, 10, 0.001);
brightness_attack = hslider("2_Temporal/04_brightness_attack[scale:log][tooltip:Bright A]", 0.005, 0.001, 5, 0.001);
brightness_decay = hslider("2_Temporal/05_brightness_decay[tooltip:Bright D]", 0.15, 0.001, 5, 0.001);
brightness_sustain = hslider("2_Temporal/06_brightness_sustain[tooltip:Bright S]", 0.4, 0, 1, 0.001);
brightness_release = hslider("2_Temporal/07_brightness_release[tooltip:Bright R]", 0.3, 0.01, 5, 0.001);
spectral_flux_rate = hslider("2_Temporal/08_spectral_flux_rate[tooltip:Flux rate]", 0.5, 0, 10, 0.01);
spectral_flux_depth= hslider("2_Temporal/09_spectral_flux_depth[tooltip:Flux depth]", 0.1, 0, 1, 0.001);
// ---------------------------------------------------------------------------
// Group 3 — Phase & Coherence (params 2431)
// ---------------------------------------------------------------------------
phase_random = hslider("3_Phase/00_phase_random[tooltip:Phase randomisation]", 0, 0, 1, 0.001);
phase_walk_rate = hslider("3_Phase/01_phase_walk_rate[tooltip:Phase walk rate]", 0, 0, 5, 0.001);
beating_depth = hslider("3_Phase/02_beating_depth[tooltip:Beating depth]", 0, 0, 0.02, 0.0001);
beating_rate = hslider("3_Phase/03_beating_rate[tooltip:Beating rate]", 1, 0, 10, 0.01);
stereo_phase_spread= hslider("3_Phase/04_stereo_phase_spread[tooltip:Stereo spread]", 0.1, 0, 1, 0.001);
noise_floor = hslider("3_Phase/05_noise_floor[tooltip:Noise floor]", 0, 0, 0.2, 0.001);
noise_color = hslider("3_Phase/06_noise_color[tooltip:Noise colour]", 0.5, 0, 1, 0.001);
sub_harmonic = hslider("3_Phase/07_sub_harmonic[tooltip:Sub-harmonic]", 0, 0, 1, 0.001);
// ---------------------------------------------------------------------------
// Group 4 — Modulation (params 3241)
// ---------------------------------------------------------------------------
vibrato_rate = hslider("4_Modulation/00_vibrato_rate[tooltip:Vibrato rate]", 5, 0, 10, 0.01);
vibrato_depth = hslider("4_Modulation/01_vibrato_depth[tooltip:Vibrato depth]", 0, 0, 0.05, 0.0001);
vibrato_delay = hslider("4_Modulation/02_vibrato_delay[tooltip:Vibrato delay]", 0.3, 0, 2, 0.001);
tremolo_rate = hslider("4_Modulation/03_tremolo_rate[tooltip:Tremolo rate]", 4, 0, 20, 0.01);
tremolo_depth = hslider("4_Modulation/04_tremolo_depth[tooltip:Tremolo depth]", 0, 0, 1, 0.001);
drift_rate = hslider("4_Modulation/05_drift_rate[tooltip:Drift rate]", 0, 0, 2, 0.001);
drift_depth = hslider("4_Modulation/06_drift_depth[tooltip:Drift depth]", 0, 0, 0.3, 0.001);
formant1_freq = hslider("4_Modulation/07_formant1_freq[tooltip:Formant 1 freq]",3, 1, 16, 0.01);
formant2_freq = hslider("4_Modulation/08_formant2_freq[tooltip:Formant 2 freq]",6, 1, 16, 0.01);
formant_depth = hslider("4_Modulation/09_formant_depth[tooltip:Formant depth]", 0, 0, 1, 0.001);
// ---------------------------------------------------------------------------
// Group 5 — Master (params 4247)
// ---------------------------------------------------------------------------
level = hslider("5_Master/00_level[tooltip:Output level]", 0.7, 0, 1, 0.001);
vel_sens = hslider("5_Master/01_vel_sens[tooltip:Velocity sens]", 0.5, 0, 1, 0.001);
vel_bright = hslider("5_Master/02_vel_brightness[tooltip:Vel bright]",0.3, 0, 1, 0.001);
pitch_glide= hslider("5_Master/03_pitch_glide[tooltip:Portamento]", 0, 0, 10, 0.01);
saturation = hslider("5_Master/04_saturation[tooltip:Saturation]", 0, 0, 1, 0.001);
fine_tune = hslider("5_Master/05_fine_tune[unit:ct][tooltip:Cents]", 0, -50, 50, 0.1);
// ---------------------------------------------------------------------------
// Internal constants
// ---------------------------------------------------------------------------
N = 64;
PI = ma.PI;
// ---------------------------------------------------------------------------
// Pitch — portamento + fine tune
// ---------------------------------------------------------------------------
glide_tau = 0.0001 + pitch_glide * 0.2;
fine_ratio = pow(2.0, fine_tune / 1200.0);
freq_smooth = freq * fine_ratio : si.smooth(ba.tau2pole(glide_tau));
// ---------------------------------------------------------------------------
// Vibrato LFO — delayed onset via slow-attack envelope
// ---------------------------------------------------------------------------
vibrato_env = en.adsr(vibrato_delay, 0.01, 1.0, 0.5, gate);
vibrato_lfo = os.osc(vibrato_rate) * vibrato_depth * vibrato_env;
// ---------------------------------------------------------------------------
// Tremolo LFO
// ---------------------------------------------------------------------------
tremolo_lfo = 1.0 - tremolo_depth * 0.5 * (1.0 + os.osc(tremolo_rate));
// ---------------------------------------------------------------------------
// Global amplitude ADSR
// ---------------------------------------------------------------------------
amp_env = en.adsr(attack, decay, sustain, release, gate);
// ---------------------------------------------------------------------------
// Brightness envelope — controls high harmonic amplitude over time
// ---------------------------------------------------------------------------
bright_env = en.adsr(brightness_attack, brightness_decay,
brightness_sustain, brightness_release, gate);
bright_blend(k) = float(k - 1) / float(N - 1);
bright_factor(k) = 1.0 - bright_blend(k) + bright_blend(k) * bright_env;
// ---------------------------------------------------------------------------
// Spectral flux — slow LFO on upper harmonic amplitudes
// ---------------------------------------------------------------------------
flux_lfo = os.osc(spectral_flux_rate);
flux_factor(k) = ba.if(k > 8,
1.0 + spectral_flux_depth * flux_lfo * bright_blend(k),
1.0);
// ---------------------------------------------------------------------------
// Drift — slow random walk on per-partial amplitudes
// ---------------------------------------------------------------------------
drift_lfo(k) = no.noise * (float(k % 7 + 1) / 7.0)
: fi.lowpass(1, max(0.1, drift_rate * 2.0))
: *(drift_depth);
drift_factor(k) = 1.0 + drift_lfo(k);
// ---------------------------------------------------------------------------
// Per-harmonic base amplitude from group sliders
// ---------------------------------------------------------------------------
group_amp(k) =
ba.if(k == 1, h1_amp,
ba.if(k == 2, h2_amp,
ba.if(k == 3, h3_amp,
ba.if(k == 4, h4_amp,
ba.if(k == 5, h5_amp,
ba.if(k == 6, h6_amp,
ba.if(k == 7, h7_amp,
ba.if(k == 8, h8_amp,
ba.if(k <= 16, h9_16_amp,
ba.if(k <= 32, h17_32_amp,
h33_64_amp))))))))));
// Spectral tilt: amp *= k^tilt (k=1 is always unity)
tilt_factor(k) = pow(float(k), spectral_tilt);
// Odd/even balance: ×2 so unity gain at odd_even=0.5
odd_weight = (1.0 - odd_even) * 2.0;
even_weight = odd_even * 2.0;
odd_even_factor(k) = ba.if(k % 2 == 0, even_weight, odd_weight);
// Combined raw amplitude
harm_amp_raw(k) = group_amp(k) * tilt_factor(k) * odd_even_factor(k);
// ---------------------------------------------------------------------------
// Formant shaping — two Gaussian bumps in harmonic-index space
// ---------------------------------------------------------------------------
sigma_sq = 1.5 * 1.5;
formant_bump(k, ctr) = exp(-0.5 * (float(k) - ctr) * (float(k) - ctr) / sigma_sq);
formant_factor(k) =
1.0 + formant_depth * (formant_bump(k, formant1_freq) + formant_bump(k, formant2_freq));
harm_amp(k) = harm_amp_raw(k) * formant_factor(k);
// ---------------------------------------------------------------------------
// Inharmonic partial frequency: freq_k = k*f0*(1 + B*(k^2 - 1))
// ---------------------------------------------------------------------------
harm_freq(k) = freq_smooth * float(k)
* (1.0 + inharmonicity * (float(k) * float(k) - 1.0));
// ---------------------------------------------------------------------------
// Phase randomisation — adds per-harmonic noise to the oscillator frequency,
// gradually dephasing partials (phase_random=0: phase-locked, =1: random)
// ---------------------------------------------------------------------------
phase_rand_lfo(k) = no.noise * (float((k * 17 + 3) % 31 + 1) / 31.0)
: si.smooth(ba.tau2pole(0.05))
: *(phase_random * harm_freq(k) * 0.01);
// ---------------------------------------------------------------------------
// Phase random walk — slower independent drift per partial
// ---------------------------------------------------------------------------
phase_walk(k) = no.noise * (float(k % 7 + 1) / 7.0)
: si.smooth(ba.tau2pole(0.1))
: *(phase_walk_rate);
// ---------------------------------------------------------------------------
// Inter-partial beating — sinusoidal detuning stagger
// ---------------------------------------------------------------------------
beating_offset(k) = beating_depth
* os.osc(beating_rate * float(k % 3 + 1) * 0.7)
* harm_freq(k);
// ---------------------------------------------------------------------------
// Stereo spread — R channel gets a small per-harmonic pitch offset
// ---------------------------------------------------------------------------
stereo_spread_freq(k) = stereo_phase_spread * float(k) * 0.01;
// ---------------------------------------------------------------------------
// Additive oscillator sums — L and R
// ---------------------------------------------------------------------------
additive_L = sum(k, N,
harm_amp(k+1) * bright_factor(k+1) * flux_factor(k+1) * drift_factor(k+1) *
os.osc( harm_freq(k+1) * (1.0 + vibrato_lfo)
+ beating_offset(k+1)
+ phase_rand_lfo(k+1)
+ phase_walk(k+1)
)
);
additive_R = sum(k, N,
harm_amp(k+1) * bright_factor(k+1) * flux_factor(k+1) * drift_factor(k+1) *
os.osc( harm_freq(k+1) * (1.0 + vibrato_lfo + stereo_spread_freq(k+1))
+ beating_offset(k+1)
+ phase_rand_lfo(k+1)
+ phase_walk(k+1)
)
);
// ---------------------------------------------------------------------------
// Sub-harmonic (0.5× fundamental)
// ---------------------------------------------------------------------------
sub_osc = sub_harmonic * os.osc(freq_smooth * 0.5);
// ---------------------------------------------------------------------------
// Noise floor — coloured via one-pole LP (noise_color=0→white, =1→dark)
// ---------------------------------------------------------------------------
noise_lp_cutoff = 200.0 + (1.0 - noise_color) * 19800.0;
noise_signal = no.noise : fi.lowpass(1, noise_lp_cutoff);
noise_out = noise_floor * noise_signal;
// ---------------------------------------------------------------------------
// Soft-clip saturation — tanh waveshaper
// ---------------------------------------------------------------------------
drive = 1.0 + saturation * 9.0;
softclip(x) = ma.tanh(x * drive) / drive;
// ---------------------------------------------------------------------------
// Output gain
// vel_bright modulates how much the brightness envelope boosts the overall
// level during note onset (couples velocity sensitivity to brightness).
// Here it acts as a subtle mid-term gain shaper via the bright_env signal.
// ---------------------------------------------------------------------------
vel_bright_boost = 1.0 + vel_bright * bright_env * 0.3;
out_gain = level * (1.0 - vel_sens * 0.3) * vel_bright_boost;
// ---------------------------------------------------------------------------
// Final assembly
// ---------------------------------------------------------------------------
signal_L = (additive_L + sub_osc + noise_out) * amp_env * tremolo_lfo * out_gain;
signal_R = (additive_R + sub_osc + noise_out) * amp_env * tremolo_lfo * out_gain;
process = softclip(signal_L), softclip(signal_R);

View file

@ -0,0 +1,758 @@
{
"name": "additive",
"filename": "additive.dsp",
"version": "2.83.1",
"compile_options": "-lang cpp -fpga-mem-th 4 -ct 1 -es 1 -mcd 16 -mdd 1024 -mdy 33 -single -ftz 0",
"library_list": ["/nix/store/fnjbalzgc6pjm8la292cczxqbr00qmav-faust-2.83.1/share/faust/stdfaust.lib","/nix/store/fnjbalzgc6pjm8la292cczxqbr00qmav-faust-2.83.1/share/faust/envelopes.lib","/nix/store/fnjbalzgc6pjm8la292cczxqbr00qmav-faust-2.83.1/share/faust/maths.lib","/nix/store/fnjbalzgc6pjm8la292cczxqbr00qmav-faust-2.83.1/share/faust/platform.lib","/nix/store/fnjbalzgc6pjm8la292cczxqbr00qmav-faust-2.83.1/share/faust/oscillators.lib","/nix/store/fnjbalzgc6pjm8la292cczxqbr00qmav-faust-2.83.1/share/faust/basics.lib","/nix/store/fnjbalzgc6pjm8la292cczxqbr00qmav-faust-2.83.1/share/faust/noises.lib","/nix/store/fnjbalzgc6pjm8la292cczxqbr00qmav-faust-2.83.1/share/faust/filters.lib","/nix/store/fnjbalzgc6pjm8la292cczxqbr00qmav-faust-2.83.1/share/faust/signals.lib"],
"include_pathnames": ["/nix/store/fnjbalzgc6pjm8la292cczxqbr00qmav-faust-2.83.1/share/faust","/usr/local/share/faust","/usr/share/faust",".","/home/w1n5t0n/src/MEMLNaut-NISPS/.claude/worktrees/agent-ae51af34/.claude/worktrees/agent-a667e616/playground/faust"],
"size": 1908,
"inputs": 0,
"outputs": 2,
"meta": [
{ "basics.lib/name": "Faust Basic Element Library" },
{ "basics.lib/version": "1.22.0" },
{ "compile_options": "-lang cpp -fpga-mem-th 4 -ct 1 -es 1 -mcd 16 -mdd 1024 -mdy 33 -single -ftz 0" },
{ "envelopes.lib/adsr:author": "Yann Orlarey and Andrey Bundin" },
{ "envelopes.lib/author": "GRAME" },
{ "envelopes.lib/copyright": "GRAME" },
{ "envelopes.lib/license": "LGPL with exception" },
{ "envelopes.lib/name": "Faust Envelope Library" },
{ "envelopes.lib/version": "1.3.0" },
{ "filename": "additive.dsp" },
{ "filters.lib/lowpass0_highpass1": "MIT-style STK-4.3 license" },
{ "filters.lib/lowpass0_highpass1:author": "Julius O. Smith III" },
{ "filters.lib/lowpass:author": "Julius O. Smith III" },
{ "filters.lib/lowpass:copyright": "Copyright (C) 2003-2019 by Julius O. Smith III <jos@ccrma.stanford.edu>" },
{ "filters.lib/lowpass:license": "MIT-style STK-4.3 license" },
{ "filters.lib/name": "Faust Filters Library" },
{ "filters.lib/tf1:author": "Julius O. Smith III" },
{ "filters.lib/tf1:copyright": "Copyright (C) 2003-2019 by Julius O. Smith III <jos@ccrma.stanford.edu>" },
{ "filters.lib/tf1:license": "MIT-style STK-4.3 license" },
{ "filters.lib/tf1s:author": "Julius O. Smith III" },
{ "filters.lib/tf1s:copyright": "Copyright (C) 2003-2019 by Julius O. Smith III <jos@ccrma.stanford.edu>" },
{ "filters.lib/tf1s:license": "MIT-style STK-4.3 license" },
{ "filters.lib/version": "1.7.1" },
{ "maths.lib/author": "GRAME" },
{ "maths.lib/copyright": "GRAME" },
{ "maths.lib/license": "LGPL with exception" },
{ "maths.lib/name": "Faust Math Library" },
{ "maths.lib/version": "2.9.0" },
{ "name": "additive" },
{ "noises.lib/name": "Faust Noise Generator Library" },
{ "noises.lib/version": "1.5.0" },
{ "oscillators.lib/name": "Faust Oscillator Library" },
{ "oscillators.lib/version": "1.6.0" },
{ "platform.lib/name": "Generic Platform Library" },
{ "platform.lib/version": "1.3.0" },
{ "signals.lib/name": "Faust Signal Routing Library" },
{ "signals.lib/version": "1.6.0" }
],
"ui": [
{
"type": "vgroup",
"label": "additive",
"items": [
{
"type": "hslider",
"label": "0_Hidden/freq",
"varname": "fHslider19",
"shortname": "0_Hidden_freq",
"address": "/additive/0_Hidden_freq",
"meta": [
{ "hidden": "1" },
{ "unit": "Hz" }
],
"init": 220,
"min": 20,
"max": 4000,
"step": 0.01
},
{
"type": "button",
"label": "0_Hidden/gate",
"varname": "fButton0",
"shortname": "0_Hidden_gate",
"address": "/additive/0_Hidden_gate",
"meta": [
{ "hidden": "1" }
]
},
{
"type": "hslider",
"label": "1_Spectral Shape/00_h1_amp",
"varname": "fHslider41",
"shortname": "1_Spectral_Shape_00_h1_amp",
"address": "/additive/1_Spectral_Shape_00_h1_amp",
"meta": [
{ "tooltip": "H1 amplitude" }
],
"init": 0.8,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "1_Spectral Shape/01_h2_amp",
"varname": "fHslider40",
"shortname": "1_Spectral_Shape_01_h2_amp",
"address": "/additive/1_Spectral_Shape_01_h2_amp",
"meta": [
{ "tooltip": "H2 amplitude" }
],
"init": 0.5,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "1_Spectral Shape/02_h3_amp",
"varname": "fHslider39",
"shortname": "1_Spectral_Shape_02_h3_amp",
"address": "/additive/1_Spectral_Shape_02_h3_amp",
"meta": [
{ "tooltip": "H3 amplitude" }
],
"init": 0.35,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "1_Spectral Shape/03_h4_amp",
"varname": "fHslider38",
"shortname": "1_Spectral_Shape_03_h4_amp",
"address": "/additive/1_Spectral_Shape_03_h4_amp",
"meta": [
{ "tooltip": "H4 amplitude" }
],
"init": 0.25,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "1_Spectral Shape/04_h5_amp",
"varname": "fHslider37",
"shortname": "1_Spectral_Shape_04_h5_amp",
"address": "/additive/1_Spectral_Shape_04_h5_amp",
"meta": [
{ "tooltip": "H5 amplitude" }
],
"init": 0.18,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "1_Spectral Shape/05_h6_amp",
"varname": "fHslider36",
"shortname": "1_Spectral_Shape_05_h6_amp",
"address": "/additive/1_Spectral_Shape_05_h6_amp",
"meta": [
{ "tooltip": "H6 amplitude" }
],
"init": 0.12,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "1_Spectral Shape/06_h7_amp",
"varname": "fHslider35",
"shortname": "1_Spectral_Shape_06_h7_amp",
"address": "/additive/1_Spectral_Shape_06_h7_amp",
"meta": [
{ "tooltip": "H7 amplitude" }
],
"init": 0.08,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "1_Spectral Shape/07_h8_amp",
"varname": "fHslider34",
"shortname": "1_Spectral_Shape_07_h8_amp",
"address": "/additive/1_Spectral_Shape_07_h8_amp",
"meta": [
{ "tooltip": "H8 amplitude" }
],
"init": 0.06,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "1_Spectral Shape/08_h9_16_amp",
"varname": "fHslider33",
"shortname": "1_Spectral_Shape_08_h9_16_amp",
"address": "/additive/1_Spectral_Shape_08_h9_16_amp",
"meta": [
{ "tooltip": "H9-16 group amp" }
],
"init": 0.05,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "1_Spectral Shape/09_h17_32_amp",
"varname": "fHslider32",
"shortname": "1_Spectral_Shape_09_h17_32_amp",
"address": "/additive/1_Spectral_Shape_09_h17_32_amp",
"meta": [
{ "tooltip": "H17-32 group amp" }
],
"init": 0.025,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "1_Spectral Shape/10_h33_64_amp",
"varname": "fHslider30",
"shortname": "1_Spectral_Shape_10_h33_64_amp",
"address": "/additive/1_Spectral_Shape_10_h33_64_amp",
"meta": [
{ "tooltip": "H33-64 group amp" }
],
"init": 0.01,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "1_Spectral Shape/11_spectral_tilt",
"varname": "fHslider29",
"shortname": "1_Spectral_Shape_11_spectral_tilt",
"address": "/additive/1_Spectral_Shape_11_spectral_tilt",
"meta": [
{ "tooltip": "Global tilt" }
],
"init": 0,
"min": -1,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "1_Spectral Shape/12_inharmonicity",
"varname": "fHslider20",
"shortname": "1_Spectral_Shape_12_inharmonicity",
"address": "/additive/1_Spectral_Shape_12_inharmonicity",
"meta": [
{ "tooltip": "Inharmonicity" }
],
"init": 0,
"min": 0,
"max": 0.15,
"step": 0.0001
},
{
"type": "hslider",
"label": "1_Spectral Shape/13_odd_even",
"varname": "fHslider31",
"shortname": "1_Spectral_Shape_13_odd_even",
"address": "/additive/1_Spectral_Shape_13_odd_even",
"meta": [
{ "tooltip": "Odd/even balance" }
],
"init": 0.5,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "2_Temporal/00_attack",
"varname": "fHslider8",
"shortname": "2_Temporal_00_attack",
"address": "/additive/2_Temporal_00_attack",
"meta": [
{ "scale": "log" },
{ "tooltip": "Attack" }
],
"init": 0.01,
"min": 0.001,
"max": 5,
"step": 0.001
},
{
"type": "hslider",
"label": "2_Temporal/01_decay",
"varname": "fHslider9",
"shortname": "2_Temporal_01_decay",
"address": "/additive/2_Temporal_01_decay",
"meta": [
{ "tooltip": "Decay" }
],
"init": 0.3,
"min": 0.001,
"max": 10,
"step": 0.001
},
{
"type": "hslider",
"label": "2_Temporal/02_sustain",
"varname": "fHslider10",
"shortname": "2_Temporal_02_sustain",
"address": "/additive/2_Temporal_02_sustain",
"meta": [
{ "tooltip": "Sustain" }
],
"init": 0.7,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "2_Temporal/03_release",
"varname": "fHslider7",
"shortname": "2_Temporal_03_release",
"address": "/additive/2_Temporal_03_release",
"meta": [
{ "tooltip": "Release" }
],
"init": 0.5,
"min": 0.01,
"max": 10,
"step": 0.001
},
{
"type": "hslider",
"label": "2_Temporal/04_brightness_attack",
"varname": "fHslider1",
"shortname": "2_Temporal_04_brightness_attack",
"address": "/additive/2_Temporal_04_brightness_attack",
"meta": [
{ "scale": "log" },
{ "tooltip": "Bright A" }
],
"init": 0.005,
"min": 0.001,
"max": 5,
"step": 0.001
},
{
"type": "hslider",
"label": "2_Temporal/05_brightness_decay",
"varname": "fHslider2",
"shortname": "2_Temporal_05_brightness_decay",
"address": "/additive/2_Temporal_05_brightness_decay",
"meta": [
{ "tooltip": "Bright D" }
],
"init": 0.15,
"min": 0.001,
"max": 5,
"step": 0.001
},
{
"type": "hslider",
"label": "2_Temporal/06_brightness_sustain",
"varname": "fHslider3",
"shortname": "2_Temporal_06_brightness_sustain",
"address": "/additive/2_Temporal_06_brightness_sustain",
"meta": [
{ "tooltip": "Bright S" }
],
"init": 0.4,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "2_Temporal/07_brightness_release",
"varname": "fHslider0",
"shortname": "2_Temporal_07_brightness_release",
"address": "/additive/2_Temporal_07_brightness_release",
"meta": [
{ "tooltip": "Bright R" }
],
"init": 0.3,
"min": 0.01,
"max": 5,
"step": 0.001
},
{
"type": "hslider",
"label": "2_Temporal/08_spectral_flux_rate",
"varname": "fHslider22",
"shortname": "2_Temporal_08_spectral_flux_rate",
"address": "/additive/2_Temporal_08_spectral_flux_rate",
"meta": [
{ "tooltip": "Flux rate" }
],
"init": 0.5,
"min": 0,
"max": 10,
"step": 0.01
},
{
"type": "hslider",
"label": "2_Temporal/09_spectral_flux_depth",
"varname": "fHslider23",
"shortname": "2_Temporal_09_spectral_flux_depth",
"address": "/additive/2_Temporal_09_spectral_flux_depth",
"meta": [
{ "tooltip": "Flux depth" }
],
"init": 0.1,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "3_Phase/00_phase_random",
"varname": "fHslider11",
"shortname": "3_Phase_00_phase_random",
"address": "/additive/3_Phase_00_phase_random",
"meta": [
{ "tooltip": "Phase randomisation" }
],
"init": 0,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "3_Phase/01_phase_walk_rate",
"varname": "fHslider21",
"shortname": "3_Phase_01_phase_walk_rate",
"address": "/additive/3_Phase_01_phase_walk_rate",
"meta": [
{ "tooltip": "Phase walk rate" }
],
"init": 0,
"min": 0,
"max": 5,
"step": 0.001
},
{
"type": "hslider",
"label": "3_Phase/02_beating_depth",
"varname": "fHslider13",
"shortname": "3_Phase_02_beating_depth",
"address": "/additive/3_Phase_02_beating_depth",
"meta": [
{ "tooltip": "Beating depth" }
],
"init": 0,
"min": 0,
"max": 0.02,
"step": 0.0001
},
{
"type": "hslider",
"label": "3_Phase/03_beating_rate",
"varname": "fHslider12",
"shortname": "3_Phase_03_beating_rate",
"address": "/additive/3_Phase_03_beating_rate",
"meta": [
{ "tooltip": "Beating rate" }
],
"init": 1,
"min": 0,
"max": 10,
"step": 0.01
},
{
"type": "hslider",
"label": "3_Phase/04_stereo_phase_spread",
"varname": "fHslider48",
"shortname": "3_Phase_04_stereo_phase_spread",
"address": "/additive/3_Phase_04_stereo_phase_spread",
"meta": [
{ "tooltip": "Stereo spread" }
],
"init": 0.1,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "3_Phase/05_noise_floor",
"varname": "fHslider43",
"shortname": "3_Phase_05_noise_floor",
"address": "/additive/3_Phase_05_noise_floor",
"meta": [
{ "tooltip": "Noise floor" }
],
"init": 0,
"min": 0,
"max": 0.2,
"step": 0.001
},
{
"type": "hslider",
"label": "3_Phase/06_noise_color",
"varname": "fHslider42",
"shortname": "3_Phase_06_noise_color",
"address": "/additive/3_Phase_06_noise_color",
"meta": [
{ "tooltip": "Noise colour" }
],
"init": 0.5,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "3_Phase/07_sub_harmonic",
"varname": "fHslider44",
"shortname": "3_Phase_07_sub_harmonic",
"address": "/additive/3_Phase_07_sub_harmonic",
"meta": [
{ "tooltip": "Sub-harmonic" }
],
"init": 0,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "4_Modulation/00_vibrato_rate",
"varname": "fHslider15",
"shortname": "4_Modulation_00_vibrato_rate",
"address": "/additive/4_Modulation_00_vibrato_rate",
"meta": [
{ "tooltip": "Vibrato rate" }
],
"init": 5,
"min": 0,
"max": 10,
"step": 0.01
},
{
"type": "hslider",
"label": "4_Modulation/01_vibrato_depth",
"varname": "fHslider16",
"shortname": "4_Modulation_01_vibrato_depth",
"address": "/additive/4_Modulation_01_vibrato_depth",
"meta": [
{ "tooltip": "Vibrato depth" }
],
"init": 0,
"min": 0,
"max": 0.05,
"step": 0.0001
},
{
"type": "hslider",
"label": "4_Modulation/02_vibrato_delay",
"varname": "fHslider14",
"shortname": "4_Modulation_02_vibrato_delay",
"address": "/additive/4_Modulation_02_vibrato_delay",
"meta": [
{ "tooltip": "Vibrato delay" }
],
"init": 0.3,
"min": 0,
"max": 2,
"step": 0.001
},
{
"type": "hslider",
"label": "4_Modulation/03_tremolo_rate",
"varname": "fHslider5",
"shortname": "4_Modulation_03_tremolo_rate",
"address": "/additive/4_Modulation_03_tremolo_rate",
"meta": [
{ "tooltip": "Tremolo rate" }
],
"init": 4,
"min": 0,
"max": 20,
"step": 0.01
},
{
"type": "hslider",
"label": "4_Modulation/04_tremolo_depth",
"varname": "fHslider6",
"shortname": "4_Modulation_04_tremolo_depth",
"address": "/additive/4_Modulation_04_tremolo_depth",
"meta": [
{ "tooltip": "Tremolo depth" }
],
"init": 0,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "4_Modulation/05_drift_rate",
"varname": "fHslider24",
"shortname": "4_Modulation_05_drift_rate",
"address": "/additive/4_Modulation_05_drift_rate",
"meta": [
{ "tooltip": "Drift rate" }
],
"init": 0,
"min": 0,
"max": 2,
"step": 0.001
},
{
"type": "hslider",
"label": "4_Modulation/06_drift_depth",
"varname": "fHslider25",
"shortname": "4_Modulation_06_drift_depth",
"address": "/additive/4_Modulation_06_drift_depth",
"meta": [
{ "tooltip": "Drift depth" }
],
"init": 0,
"min": 0,
"max": 0.3,
"step": 0.001
},
{
"type": "hslider",
"label": "4_Modulation/07_formant1_freq",
"varname": "fHslider27",
"shortname": "4_Modulation_07_formant1_freq",
"address": "/additive/4_Modulation_07_formant1_freq",
"meta": [
{ "tooltip": "Formant 1 freq" }
],
"init": 3,
"min": 1,
"max": 16,
"step": 0.01
},
{
"type": "hslider",
"label": "4_Modulation/08_formant2_freq",
"varname": "fHslider26",
"shortname": "4_Modulation_08_formant2_freq",
"address": "/additive/4_Modulation_08_formant2_freq",
"meta": [
{ "tooltip": "Formant 2 freq" }
],
"init": 6,
"min": 1,
"max": 16,
"step": 0.01
},
{
"type": "hslider",
"label": "4_Modulation/09_formant_depth",
"varname": "fHslider28",
"shortname": "4_Modulation_09_formant_depth",
"address": "/additive/4_Modulation_09_formant_depth",
"meta": [
{ "tooltip": "Formant depth" }
],
"init": 0,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "5_Master/00_level",
"varname": "fHslider47",
"shortname": "5_Master_00_level",
"address": "/additive/5_Master_00_level",
"meta": [
{ "tooltip": "Output level" }
],
"init": 0.7,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "5_Master/01_vel_sens",
"varname": "fHslider46",
"shortname": "5_Master_01_vel_sens",
"address": "/additive/5_Master_01_vel_sens",
"meta": [
{ "tooltip": "Velocity sens" }
],
"init": 0.5,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "5_Master/02_vel_brightness",
"varname": "fHslider4",
"shortname": "5_Master_02_vel_brightness",
"address": "/additive/5_Master_02_vel_brightness",
"meta": [
{ "tooltip": "Vel bright" }
],
"init": 0.3,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "5_Master/03_pitch_glide",
"varname": "fHslider17",
"shortname": "5_Master_03_pitch_glide",
"address": "/additive/5_Master_03_pitch_glide",
"meta": [
{ "tooltip": "Portamento" }
],
"init": 0,
"min": 0,
"max": 10,
"step": 0.01
},
{
"type": "hslider",
"label": "5_Master/04_saturation",
"varname": "fHslider45",
"shortname": "5_Master_04_saturation",
"address": "/additive/5_Master_04_saturation",
"meta": [
{ "tooltip": "Saturation" }
],
"init": 0,
"min": 0,
"max": 1,
"step": 0.001
},
{
"type": "hslider",
"label": "5_Master/05_fine_tune",
"varname": "fHslider18",
"shortname": "5_Master_05_fine_tune",
"address": "/additive/5_Master_05_fine_tune",
"meta": [
{ "tooltip": "Cents" },
{ "unit": "ct" }
],
"init": 0,
"min": -50,
"max": 50,
"step": 0.1
}
]
}
]
}

File diff suppressed because it is too large Load diff