Engine/MusicGen.Config.cs

Configuration container nested in MusicGen. Declares runtime-adjustable synthesis and mix parameters (genre, per-instrument volumes, filters, drive, stereo/width, drum/kit balances, advanced mix tuning and randomness) used by the music generator and exposed across the JS/wasm boundary.

File Access
using System;
using System.Collections.Generic;

namespace Skafinity;

// Config — every knob the composer and synth read. Nested in MusicGen so `MusicGen.Config`
// still resolves. Fields here are either vibe knobs (VibeCodec grids / GlobalFields) or
// house-mix values (VibeCodec.AdvancedFields); either way a new field must also be added to
// Cfg.To/Cfg.From in wasm/Exports.cs or it will not survive the JS boundary.
//
// Part of the MusicGen engine — see MusicGen.cs.

public sealed partial class MusicGen
{
	public sealed class Config
	{
		// Genre — selects the instrument set + arrangement. 0 = Ska-Punk (bass/skank/organ/lead/
		// horns/drums), 1 = Rock (drums/bass/keys/lead-gtr/rhythm-gtr), 2 = Country, 3 = Metal,
		// 4 = Punk (lean power-pop), 5 = Pop (synth/dance). New genres append here.
		public int Genre = 0;

		// Output. Song length follows the structure (see BuildStructure), so there is no
		// length knob here.
		public int SampleRate = 44100;

		// Tempo. The BAND is the genre's, not a knob — a country song and a metal song sharing
		// one 130–185 range was most of why the genres sounded alike (see GenreProfile). What
		// stays a knob is the listener's preference on top of it:
		// Tempo is the GENRE's and nothing else reaches it — see GenreProfile's band block and
		// the two reserved slots in VibeCodec where the knobs used to be.
		// Swing is not a Config value either: per-genre character drawn per song (GenreProfile).

		// Mix (per-voice gain pre-master) — the six "volume" sliders are normalized:
		// same 0..1.5 range and the same 1.0 default (flat mix), tune from there.
		public float BassVol = 1.00f;
		public float SkankVol = 1.00f;
		public float OrganVol = 1.00f;
		public float MelodyVol = 1.00f;
		public float HornVol = 1.00f;
		// Per-part kit trims. The kit is balanced for EQUAL PEAK LEVEL in the file internally
		// (see *Balance consts below), so these knobs all share a 1.0 default — every piece
		// peaks at the same level out of the box and these are pure user trims around that.
		public float KickVol = 1.00f;
		public float SnareVol = 1.00f;
		public float TomVol = 1.00f;
		public float HatVol = 1.00f;
		public float CrashVol = 1.00f;
		public float DrumVol = 1.00f;         // master gain over the whole kit
		// Drum "tone" — toms↔cymbals bias for the PART. 0 = boom (fills/decoration lean to
		// toms, cymbals pulled back), 0.5 = neutral, 1 = bright (fills/decoration lean to
		// cymbals, toms pulled back). Drives both what's played and a gentle per-voice gain lean.
		public float DrumTone = 0.5f;
		// Drum "drive" — pull↔push timing feel (replaces the old DrumPush magnitude roll).
		// 0 = lay back behind the beat, 0.5 = dead-on, 1 = push ahead of the beat.
		public float DrumDrive = 0.5f;

		// Rock instruments (Genre 1). Bass + drums reuse the shared knobs above.
		// KEYS — the offbeat-chord comp (was labelled "rhythm guitar", but it always read as a
		// keyboard, so it's named for what it sounds like). Power-chord comping on every eighth.
		public float KeysVol = 1.00f;
		public float KeysCutoff = 1700f;      // Hz low-pass (darker wall)
		public float KeysDrive = 3.2f;        // distortion amount (tanh drive)
		public float KeysChug = 0.5f;         // 0 = ringing chords, 1 = tight palm-mute chug
		// RHYTHM GTR — twangy distorted power chords. Shares the lead guitar's voice but strums
		// chords at a lower base distortion than the lead.
		public float RhythmGtrVol = 1.00f;
		public float RhythmGtrCutoff = 2600f; // Hz low-pass on the rhythm guitar
		public float RhythmGtrDrive = 2.8f;   // distortion amount (tanh drive) — under the lead
		public float RhythmGtrChug = 0.5f;    // 0 = ringing chords, 1 = tight palm-mute chug
		// LEAD GTR — twangy, heavily distorted single-note lead.
		public float LeadGtrVol = 1.00f;
		public float LeadGtrCutoff = 2600f;   // Hz low-pass on the lead guitar
		public float LeadGtrDrive = 5.0f;     // distortion amount (tanh drive) — new floor of the DISTORTION knob
		public float LeadGtrBend = 0.30f;     // rock lead "bendiness" 0..1 — propensity to bend up into notes and scoop

		// Tone — low drives + filtering for warmth; detune for width.
		// Unison spread in cents, applied SYMMETRICALLY across a patch's voices: a 3-voice patch
		// puts them at -Detune, 0, +Detune, so this is the half-spread and the total is double it.
		// 14 meant a 28-cent spread on the skank, the horns and the trumpet/trombone/organ leads —
		// a chorus, not a unison, and it stacked with double-tracking on top of every chord tone.
		public float Detune = 7f;
		public float BassCutoff = 380f;   // Hz low-pass on bass
		public float SkankCutoff = 3000f; // Hz low-pass on skank
		public float SkankHighpass = 500f;// Hz high-pass to thin the skank ("skank bite")
		public float SkankChop = 0.5f;    // skank chop length as a fraction of an eighth
		public float LeadCutoff = 3200f;  // Hz low-pass on lead
		public float OrganCutoff = 1400f; // Hz low-pass on the organ bubble
		public float OrganVibrato = 5.5f; // organ bubble vibrato depth
		public float HornCutoff = 3200f;  // Hz low-pass on the backing horns
		// SVF damping is not a Config value: it is part of a voice's timbre and is named on the
		// Patch next to the voice that renders it (see Patch.Reso).
		public float BassDrive = 1.5f;
		public float SkankDrive = 1.3f;
		public float MelodyDrive = 1.3f;
		public float HornDrive = 1.4f;
		public float MasterDrive = 1.1f;
		public float MasterPeak = 0.95f;

		// Master room reverb. The WET is drawn per song (MusicGen.ReverbMin/Max) and trimmed by the
		// genre's own mix profile; this is the house SCALE over that, not the wet itself — house
		// config rather than a vibe slider, because bone dry and swimming are both reachable on a
		// slider and neither is a thing any of these genres is. 1 = as drawn. Decay = tail (0..1).
		public float MasterReverb = 1.0f;
		public float ReverbDecay = 0.5f;

		// Feel
		public float OctavePopChance = 0.30f;
		public float OrganBubbleChance = 0.55f;
		public float KickSyncChance = 0.25f;
		public float GhostSnareChance = 0.35f;
		public float FillChance = 0.6f;       // drum fill at phrase ends
		public float DrumBusy = 0.6f;         // 0..1 overall kit activity: 16th hats, ghosts, kick syncopation
		public float TripletChance = 0.06f;   // 0..0.2 chance of triplet/16th ornament in fills / lead runs (potent)
		public float BassTriplets = 0.06f;    // 0..0.1 bass-only 16th/triplet ornament rate (own knob)
		public float MelodyRestChance = 0.30f;
		public float MelodyLeapChance = 0.18f;
		public float MelodyVibrato = 5.0f;

		// Stereo width (HOUSE CONFIG, not a vibe slider): master 0..1 stereo amount. Scales the lead/horn placement
		// AND the drum pan + double-tracking spread/decorrelation (see _widthScale). 1 = full
		// design width (the "100%" the rest of the mix is tuned for); 0 = mono.
		public float PanAmount = 1.0f;

		// Lead instrument weights (RNG picks one; ForceInstrument overrides:
		// -1 = RNG, 0=Trumpet 1=Sax 2=Organ 3=Trombone).
		public float TrumpetWeight = 1.0f;
		public float SaxWeight = 1.0f;
		public float OrganWeight = 0.8f;
		public float TromboneWeight = 0.4f;
		public int ForceInstrument = -1;

		// Backing horn section
		public float HornSectionChance = 0.5f;
		public float HornDensity = 0.35f;

		// ── Advanced peak-balance / level tuning (NOT vibe knobs) ──
		// These were hardcoded consts; they're Config fields so the house mix can be retuned at
		// runtime (web: config.json) without a rebuild. They're deliberately kept OUT of the vibe
		// seed and the per-genre slider grid (see VibeCodec.AdvancedFields) — they shape the
		// baseline mix, not a song's shareable identity. Edit these to re-balance, not the *Vol
		// defaults (which stay a flat 1.0 so the knobs read as pure user trims around this).
		//
		// KitPresence: drums are short transients fighting a sustained melodic bed; this baseline
		// boost lets the kit sit in the mix at DRUMS = 1.0 (parity with the other voice sliders).
		public float KitPresence = 2.35f;
		// GenreMix: how strongly each genre's own mix profile (GenreProfile.Mix — metal dry and
		// mid-scooped, pop wide and bright, country dry and centred, ska roomy) is applied.
		// 1 = as designed, 0 = every genre through one neutral mix, 2 = exaggerated. It is ONE
		// value rather than six genres × five trims of house config: the shape of a genre's mix is
		// character and belongs in the profile next to its tempo band, and what the house actually
		// needs to retune without a rebuild is how far to take it.
		public float GenreMix = 1.0f;
		// Kit per-voice level balance. The pieces synthesise at very different raw levels (a kick
		// is huge, a hat is thin noise). Starting point was EQUAL PRE-MASTER PEAK (kick 0.40 /
		// snare 0.476 / tom 0.496 / hat 0.582 / crash 0.515), then hand-tuned by ear: equal peak
		// buried the sparse kick/snare/toms under the constant hats, so drums were pushed back up
		// and the hats backed off.
		public float KickBalance = 0.800f;
		public float SnareBalance = 0.900f;
		public float TomBalance = 0.780f;     // tuned by ear
		public float HatBalance = 0.407f;     // equal-peak baseline (0.582) backed off 30% by ear
		// The ride used to ride on HatBalance, because it was built as the cymbal HAND replacing
		// the hat rather than as an instrument with its own bus. They are not one instrument to
		// mix: a hat is short and high and plays continuously, a ride rings — so wanting one back
		// and not the other is an ordinary request, and it was unanswerable. Seeded from
		// HatBalance's value so splitting them changed nothing on its own.
		public float RideBalance = 0.407f;
		public float CrashBalance = 0.380f;
		// Melodic-voice peak balance — the instrument analog of the kit balances above (same
		// equal-peak-then-tune workflow). Bass measured the same peak across genres, so one value.
		public float BassBalance = 0.733f;
		public float SkankBalance = 1.223f;
		public float OrganBalance = 1.237f;
		public float MelodyBalance = 0.896f;  // ska horn lead
		public float HornBalance = 1.142f;    // backing horn section
		// Re-measured when the keys stopped being double-tracked: one take instead of two summed
		// cost the voice ~2.5 dB, which is a mix change nobody asked for hiding inside a timbre fix.
		public float KeysBalance = 1.390f;    // rock offbeat keys
		public float RhythmGtrBalance = 1.053f;
		public float LeadGtrBalance = 0.896f;

		// ── Stereo double-tracking (width) ── (config-only; see VibeCodec.AdvancedFields)
		// Widen the non-drum voices the way a band double-tracks guitars (bass excepted — it
		// stays centred for a tight, mono-safe low end): each widened note is rendered
		// as TWO decorrelated "takes" panned apart — NOT one mono signal copied to both channels
		// (that just collapses to centre). The width comes entirely from the micro-differences
		// between the takes: a few cents of detune, a small constant timing offset + per-note
		// jitter, independent oscillator start phase, and per-note amplitude/cutoff variation.
		// Tunable at runtime (no rebuild). Drums are panned separately (see DrumPan).
		public float DoubleTrack = 1f;        // master enable: <0.5 = off (single centred take, as before)
		public float WidthBacking = 0.5f;     // pan spread (±) for backing voices — 50% L/R
		public float WidthLead = 1.0f;        // pan spread (±) for a doubled lead. INERT by default: the
		                                      // lead is monophonic and rendered as a single clean take
		                                      // (see RenderLeadNote) — doubling a solo line smeared pitch
		                                      // ("out of key"). Only takes effect if the lead path is
		                                      // switched back to lead:true. Chordal voices use WidthBacking.
		public float WidthDetune = 6f;        // cents BETWEEN the two takes (split ±half each).
		                                      // Kept deliberately small — a few cents is what makes
		                                      // two takes read as two performances; a wide value is
		                                      // just out of tune (see the range in AdvancedFields).
		public float WidthDelayMs = 9f;       // constant timing offset of the 2nd take (ms)
		public float WidthJitterMs = 4f;      // extra random per-note timing jitter (ms, ± per take)
		public float WidthAmpVar = 0.08f;     // per-note amplitude variation between takes (0..1)
		public float WidthCutoffVar = 0.10f;  // per-note filter-cutoff variation between takes (0..1)
	}
}